จะหลบหนีการเรียก os.system () ได้อย่างไร


124

เมื่อใช้ os.system () มักจำเป็นต้องหลีกเลี่ยงชื่อไฟล์และอาร์กิวเมนต์อื่น ๆ ที่ส่งผ่านเป็นพารามิเตอร์ไปยังคำสั่ง ฉันจะทำเช่นนี้ได้อย่างไร? โดยเฉพาะอย่างยิ่งสิ่งที่สามารถใช้ได้กับหลายระบบปฏิบัติการ / เชลล์ แต่โดยเฉพาะอย่างยิ่งสำหรับ bash

ฉันกำลังทำสิ่งต่อไปนี้ แต่แน่ใจว่าต้องมีฟังก์ชั่นไลบรารีสำหรับสิ่งนี้หรืออย่างน้อยก็เป็นตัวเลือกที่หรูหรา / แข็งแกร่ง / มีประสิทธิภาพมากขึ้น:

def sh_escape(s):
   return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

os.system("cat %s | grep something | sort > %s" 
          % (sh_escape(in_filename), 
             sh_escape(out_filename)))

แก้ไข:ฉันยอมรับคำตอบง่ายๆของการใช้เครื่องหมายคำพูดไม่รู้ว่าทำไมฉันถึงไม่คิดอย่างนั้น ฉันเดาว่าเป็นเพราะฉันมาจาก Windows โดยที่ "และ" ทำงานแตกต่างกันเล็กน้อย

เกี่ยวกับความปลอดภัยฉันเข้าใจข้อกังวล แต่ในกรณีนี้ฉันสนใจวิธีแก้ปัญหาที่ง่ายและรวดเร็วซึ่ง os.system () มีให้และแหล่งที่มาของสตริงนั้นไม่ได้สร้างโดยผู้ใช้หรืออย่างน้อยก็ป้อนโดย a ผู้ใช้ที่เชื่อถือได้ (ฉัน)


1
ระวังปัญหาด้านความปลอดภัย! ตัวอย่างเช่นถ้า out_filename คือ foo.txt; rm -rf / ผู้ใช้ที่ประสงค์ร้ายสามารถเพิ่มคำสั่งเพิ่มเติมที่เชลล์ตีความโดยตรง
Steve Gury

6
สิ่งนี้มีประโยชน์เช่นกันหากไม่มี os.system ในสถานการณ์ที่กระบวนการย่อยไม่ใช่ตัวเลือก เช่นการสร้างเชลล์สคริปต์

ที่เหมาะสำหรับsh_escapeฟังก์ชั่นจะหลบหนีออกมาและช่องว่างและเอาปัญหาการรักษาความปลอดภัยโดยเพียงแค่การสร้างไฟล์ที่เรียกว่าสิ่งที่ต้องการ; foo.txt\;\ rm\ -rf\ /
ทอม

ในเกือบทุกกรณีคุณควรใช้กระบวนการย่อยไม่ใช่ os.system การเรียก os.system เป็นเพียงการขอการโจมตีแบบฉีด
allyourcode

คำตอบ:


85

นี่คือสิ่งที่ฉันใช้:

def shellquote(s):
    return "'" + s.replace("'", "'\\''") + "'"

เชลล์จะยอมรับชื่อไฟล์ที่ยกมาเสมอและลบเครื่องหมายคำพูดรอบข้างก่อนที่จะส่งไปยังโปรแกรมที่มีปัญหา โดยเฉพาะอย่างยิ่งสิ่งนี้จะหลีกเลี่ยงปัญหาเกี่ยวกับชื่อไฟล์ที่มีช่องว่างหรือตัวอักษรเชลล์ที่น่ารังเกียจอื่น ๆ

อัปเดต : หากคุณใช้ Python 3.3 ขึ้นไปให้ใช้shlex.quoteแทนการหมุนของคุณเอง


7
@pixelbeat: นั่นคือสาเหตุที่ทำให้เขาปิดคำพูดเดี่ยวเพิ่มเครื่องหมายคำพูดเดี่ยวที่ไม่มีตัวอักษรจากนั้นเปิดคำพูดเดี่ยวอีกครั้ง
lhunath

4
แม้ว่าฟังก์ชันนี้จะไม่ได้เป็นความรับผิดชอบของฟังก์ชัน shellquote แต่ก็น่าสนใจที่จะทราบว่าการดำเนินการนี้จะยังคงล้มเหลวหากแบ็กสแลชที่ไม่ได้ใส่เครื่องหมายคำพูดปรากฏก่อนค่าส่งคืนของฟังก์ชันนี้ ขวัญกำลังใจ: ตรวจสอบให้แน่ใจว่าคุณใช้สิ่งนี้ในรหัสที่คุณสามารถเชื่อถือได้ว่าปลอดภัย - (เช่นเป็นส่วนหนึ่งของคำสั่งที่เข้ารหัส) - อย่าผนวกเข้ากับข้อมูลอื่น ๆ ของผู้ใช้ที่ไม่ได้ใส่เครื่องหมาย
lhunath

10
โปรดทราบว่าหากคุณไม่ต้องการคุณสมบัติของเชลล์อย่างแท้จริงคุณควรใช้คำแนะนำของ Jamie แทน
lhunath

6
บางสิ่งบางอย่างที่คล้ายกันนี้อยู่ในขณะนี้อย่างเป็นทางการพร้อมใช้งานเป็นshlex.quote
Janus Troelsen

3
ฟังก์ชั่นที่ให้ไว้ในคำตอบนี้ไม่ได้งานที่ดีขึ้นของเปลือก quoting กว่าหรือshlex pipesผู้โมดูลหลามไม่สมควรคิดว่าตัวอักษรพิเศษเป็นสิ่งเดียวที่จะต้องมีการยกมาซึ่งหมายความว่าเปลือกคำหลัก (เช่นtime, caseหรือwhile) จะถูกแยกเมื่อพฤติกรรมที่ไม่คาดว่า ด้วยเหตุนี้ฉันจึงขอแนะนำให้ใช้รูทีน quoting แบบ single-quoting ในคำตอบนี้เนื่องจากไม่ได้พยายามที่จะ "ฉลาด" ดังนั้นจึงไม่มีกรณีที่ไร้สาระเหล่านั้น
user3035772

157

shlex.quote() ทำในสิ่งที่คุณต้องการตั้งแต่ python 3

(ใช้pipes.quoteรองรับทั้ง python 2 และ python 3)


commands.mkargนอกจากนี้ยังมี นอกจากนี้ยังเพิ่มช่องว่างชั้นนำ (นอกเครื่องหมายคำพูด) ซึ่งอาจเป็นที่ต้องการหรือไม่ก็ได้เป็นที่น่าสนใจว่าการใช้งานของพวกเขาแตกต่างกันอย่างไรและยังซับซ้อนกว่าคำตอบของ Greg Hewgill มาก
Laurence Gonsalves

3
ด้วยเหตุผลบางประการpipes.quoteไม่ได้กล่าวถึงในเอกสารห้องสมุดมาตรฐานสำหรับโมดูลท่อ
วันที่

1
ทั้งสองไม่มีเอกสาร; command.mkargเลิกใช้แล้วและนำออกใน 3.x ในขณะที่ pip.quote ยังคงอยู่
Beni Cherniavsky-Paskin

9
การแก้ไข: จัดทำเป็นเอกสารอย่างเป็นทางการshlex.quote()ใน 3.3 pipes.quote()เก็บรักษาไว้เพื่อความเข้ากันได้ [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin

7
ไปป์ไม่ทำงานบน Windows - เพิ่มเครื่องหมายคำพูดเดี่ยวที่ใส่เครื่องหมายคำพูดคู่
Nux

58

os.system()บางทีคุณอาจมีเหตุผลที่เฉพาะเจาะจงสำหรับการใช้ แต่ถ้าไม่คุณควรใช้subprocessโมดูลนี้ คุณสามารถระบุท่อได้โดยตรงและหลีกเลี่ยงการใช้เปลือก

ต่อไปนี้มาจากPEP324 :

Replacing shell pipe line
-------------------------

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

6
subprocess(โดยเฉพาะอย่างยิ่งกับcheck_callฯลฯ ) มักจะเหนือกว่าอย่างมาก แต่ก็มีบางกรณีที่การหลบกระสุนยังคงมีประโยชน์ สิ่งสำคัญที่ฉันพบคือเมื่อฉันต้องเรียกใช้คำสั่งระยะไกล ssh
Craig Ringer

@CraigRinger, yup, ssh remoting คือสิ่งที่พาฉันมาที่นี่ : PI หวังว่า ssh มีอะไรให้ช่วยที่นี่
Jürgen A. Erhard

@ JürgenA.Erhardดูเหมือนจะแปลกที่ไม่มีตัวเลือก --execvp-remote (หรือทำงานตามค่าเริ่มต้น) การทำทุกอย่างผ่านเปลือกดูเหมือนเงอะงะและมีความเสี่ยง OTOH, ssh เต็มไปด้วยความแปลกประหลาดซึ่งมักจะทำในมุมมองที่แคบของ "ความปลอดภัย" ซึ่งทำให้ผู้คนต้องหาวิธีแก้ปัญหาที่ไม่ปลอดภัยมากขึ้น
Craig Ringer

10

อาจsubprocess.list2cmdlineจะเป็นภาพที่ดีกว่า?


นั่นดูดีทีเดียว สิ่งที่น่าสนใจไม่ได้บันทึกไว้ ... (ในdocs.python.org/library/subprocess.html เป็นอย่างน้อย)
ทอม

4
มันไม่หนีอย่างถูกต้อง \: subprocess.list2cmdline(["'",'',"\\",'"'])ให้' "" \ \"
Tino

มันไม่หนีสัญลักษณ์การขยายเชลล์
grep

subprocess.list2cmdline () มีไว้สำหรับ Windows เท่านั้นหรือไม่
ส.

@JS ใช่list2cmdlineสอดคล้องกับไวยากรณ์ของ Windows cmd.exe ( ดูฟังก์ชัน docstring ในซอร์สโค้ด Python ) shlex.quoteเป็นไปตามไวยากรณ์เชลล์ของ Unix bourne แต่โดยปกติแล้วไม่จำเป็นเนื่องจาก Unix มีการสนับสนุนที่ดีสำหรับการส่งผ่านอาร์กิวเมนต์โดยตรง Windows ต้องการให้คุณส่งสตริงเดี่ยวพร้อมอาร์กิวเมนต์ทั้งหมดของคุณ (ดังนั้นจึงจำเป็นต้องมีการหลีกเลี่ยงที่เหมาะสม)
eestrada

7

โปรดทราบว่า pip.quote เสียจริงใน Python 2.5 และ Python 3.1 และไม่ปลอดภัยที่จะใช้ - ไม่จัดการอาร์กิวเมนต์ที่มีความยาวเป็นศูนย์

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

ดูPython ฉบับ 7476 ; ได้รับการแก้ไขแล้วใน Python 2.6 และ 3.2 และใหม่กว่า


4
คุณใช้ Python เวอร์ชันใด เวอร์ชัน 2.6 ดูเหมือนว่าจะสร้างผลลัพธ์ที่ถูกต้อง: mycommand arg1 '' arg3 (ซึ่งเป็นเครื่องหมายคำพูดเดี่ยวสองตัวด้วยกันแม้ว่าแบบอักษรบน Stack Overflow จะทำให้ยากที่จะบอก!)
Brandon Rhodes

4

หมายเหตุ : นี่คือคำตอบสำหรับ Python 2.7.x

อ้างอิงถึงแหล่งที่มา , pipes.quote()เป็นวิธีที่จะเป็น " เชื่อถือได้พูดสตริงเป็นอาร์กิวเมนต์เดียวสำหรับ/ bin / ดวลจุดโทษ " (แม้ว่าจะเลิกใช้งานตั้งแต่เวอร์ชัน 2.7และในที่สุดก็เปิดเผยต่อสาธารณะใน Python 3.3 ในฐานะshlex.quote()ฟังก์ชัน)

บนมืออื่น ๆ , subprocess.list2cmdline()เป็นวิธีการ " แปลเป็นลำดับของการขัดแย้งเป็นสตริงบรรทัดคำสั่งโดยใช้กฎเดียวกับรันไทม์ MS C "

นี่คือวิธีที่เป็นอิสระจากแพลตฟอร์มในการอ้างถึงสตริงสำหรับบรรทัดคำสั่ง

import sys
mswindows = (sys.platform == "win32")

if mswindows:
    from subprocess import list2cmdline
    quote_args = list2cmdline
else:
    # POSIX
    from pipes import quote

    def quote_args(seq):
        return ' '.join(quote(arg) for arg in seq)

การใช้งาน:

# Quote a single argument
print quote_args(['my argument'])

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)

3

ฉันเชื่อว่า os.system จะเรียกใช้เชลล์คำสั่งใดก็ตามที่กำหนดค่าไว้สำหรับผู้ใช้ดังนั้นฉันไม่คิดว่าคุณจะทำได้ในรูปแบบที่เป็นอิสระจากแพลตฟอร์ม เชลล์คำสั่งของฉันอาจเป็นอะไรก็ได้ตั้งแต่ bash, emacs, Ruby หรือแม้แต่ quake3 โปรแกรมเหล่านี้บางโปรแกรมไม่ได้คาดหวังว่าจะมีข้อโต้แย้งที่คุณส่งผ่านถึงพวกเขาและแม้ว่าพวกเขาจะทำก็ไม่มีการรับประกันว่าพวกเขาจะหลบหนีในลักษณะเดียวกัน


2
ไม่ใช่เรื่องไร้เหตุผลที่จะคาดหวังเชลล์ที่สอดคล้องกับ POSIX เป็นส่วนใหญ่หรือทั้งหมด (อย่างน้อยก็ทุกที่ยกเว้นใน Windows และคุณก็รู้ว่าคุณมี "เชลล์" อะไรอยู่แล้ว) os.system ไม่ใช้ $ SHELL อย่างน้อยก็ไม่ใช่ที่นี่

2

ฟังก์ชันที่ฉันใช้คือ:

def quote_argument(argument):
    return '"%s"' % (
        argument
        .replace('\\', '\\\\')
        .replace('"', '\\"')
        .replace('$', '\\$')
        .replace('`', '\\`')
    )

นั่นคือ: ฉันใส่อาร์กิวเมนต์ไว้ในเครื่องหมายคำพูดคู่เสมอจากนั้นแบ็กสแลช - อ้างอิงเฉพาะอักขระพิเศษภายในเครื่องหมายคำพูดคู่


โปรดทราบว่าคุณควรใช้ '\\ "', '\\ $' และ '\`' ไม่เช่นนั้นการหลบหนีจะไม่เกิดขึ้น
JanKanis

1
นอกจากนี้ยังมีปัญหาเกี่ยวกับการใช้คำพูดสองในสถานที่บางคน (แปลก) ; การแก้ไขที่แนะนำใช้pipes.quoteซึ่ง @JohnWiseman ชี้ให้เห็นก็ใช้งานไม่ได้เช่นกัน คำตอบของ Greg Hewgill จึงเป็นคำตอบที่น่าใช้ (นอกจากนี้ยังเป็นกระสุนที่ใช้ภายในสำหรับเคสทั่วไป)
mirabilos

-3

หากคุณใช้คำสั่งระบบฉันจะลองและรายการที่อนุญาตในการเรียกใช้ os.system () ตัวอย่างเช่น ..

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

โมดูลกระบวนการย่อยเป็นตัวเลือกที่ดีกว่าและฉันขอแนะนำให้พยายามหลีกเลี่ยงการใช้สิ่งใด ๆ เช่น os.system / subprocess หากทำได้


-3

คำตอบที่แท้จริงคืออย่าใช้os.system()ตั้งแต่แรก ใช้subprocess.callแทนและระบุอาร์กิวเมนต์ที่ไม่ใช้ Escape


6
คำถามประกอบด้วยตัวอย่างที่กระบวนการย่อยล้มเหลว หากคุณสามารถใช้กระบวนการย่อยได้คุณควรแน่ใจ แต่ถ้าคุณไม่สามารถ ... กระบวนการย่อยจะไม่แก้ปัญหาสำหรับทุกอย่าง โอ้และคำตอบของคุณไม่ตอบคำถามเลย
Jürgen A. Erhard

@ JürgenAเออร์ฮาร์ดตัวอย่างของ OP ไม่ล้มเหลวเพราะต้องการใช้เชลล์ไปป์ใช่หรือไม่? คุณควรใช้กระบวนการย่อยเสมอเพราะไม่ได้ใช้เชลล์ นี่เป็นตัวอย่างที่ค่อนข้างเงอะงะแต่คุณสามารถทำไพพ์ในกระบวนการย่อยดั้งเดิมได้มีแพ็คเกจ pypi บางส่วนที่พยายามทำให้ง่ายขึ้น ฉันมักจะทำหลังการประมวลผลที่ฉันต้องการใน python ให้มากที่สุดคุณสามารถสร้างบัฟเฟอร์ StringIO ของคุณเองและควบคุมสิ่งต่าง ๆ ได้อย่างสมบูรณ์ด้วยกระบวนการย่อย
ThorSummoner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.