ความหมายที่แท้จริงของ 'shell = True' ในกระบวนการย่อย


260

ฉันกำลังเรียกกระบวนการที่แตกต่างกับsubprocessโมดูล อย่างไรก็ตามฉันมีคำถาม

ในรหัสต่อไปนี้:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

และ

callProcess = subprocess.Popen(['ls', '-l']) # without shell

ทั้งงาน หลังจากอ่านเอกสารฉันก็รู้ว่าshell=Trueหมายถึงการรันโค้ดผ่านเชลล์ ดังนั้นนั่นหมายความว่ากระบวนการจะเริ่มต้นโดยตรง

ดังนั้นสิ่งที่ฉันควรจะชอบสำหรับกรณีของฉัน - ฉันต้องเรียกใช้กระบวนการและรับเอาท์พุท ฉันจะได้ประโยชน์อะไรจากการเรียกมันจากภายในเปลือกหรือนอกมัน


21
คำสั่งแรกไม่ถูกต้อง: -lถูกส่งผ่านไป/bin/sh(เปลือก) แทนlsโปรแกรมบน Unix shell=Trueถ้า ควรใช้อาร์กิวเมนต์สตริงกับshell=Trueในกรณีส่วนใหญ่แทนที่จะเป็นรายการ
jfs

1
เรื่อง "กระบวนการเริ่มต้นโดยตรง": Wut?
allyourcode

9
คำสั่ง "ทั้งสองทำงาน" เกี่ยวกับการโทร 2 สายนั้นไม่ถูกต้องและทำให้เข้าใจผิด การโทรทำงานแตกต่างกัน เพียงสลับจากshell=Trueเป็นFalseและกลับกันเป็นข้อผิดพลาด จากdocs : "บน POSIX ด้วย shell = True, (... ) หาก args เป็นลำดับรายการแรกจะระบุสตริงคำสั่งและรายการเพิ่มเติมใด ๆ จะถือว่าเป็นอาร์กิวเมนต์เพิ่มเติมสำหรับเชลล์เอง" ใน Windows มีการแปลงอัตโนมัติซึ่งอาจไม่พึงประสงค์
mbdevpl

คำตอบ:


183

ประโยชน์ของการไม่โทรผ่านเชลล์คือคุณไม่ได้เรียกใช้ 'โปรแกรมลึกลับ' บน POSIX ตัวแปรสภาพแวดล้อมSHELLจะควบคุมไบนารีที่ถูกเรียกเป็น "เชลล์" บน Windows ไม่มีเชลล์ bourne ที่สืบทอดมาเพียง cmd.exe

ดังนั้นการเรียกใช้เชลล์จะเรียกใช้โปรแกรมการเลือกของผู้ใช้และขึ้นอยู่กับแพลตฟอร์ม โดยทั่วไปการพูดหลีกเลี่ยงการเรียกใช้ผ่านเชลล์

การเรียกใช้ผ่านเชลล์จะช่วยให้คุณสามารถขยายตัวแปรสภาพแวดล้อมและไฟล์ globs ตามกลไกปกติของเชลล์ บนระบบ POSIX เชลล์จะขยายไฟล์ globs เป็นรายการของไฟล์ บน Windows ไฟล์ glob (เช่น "*. *") จะไม่ถูกขยายโดยเชลล์อย่างไรก็ตาม (แต่ตัวแปรสภาพแวดล้อมบนบรรทัดคำสั่งจะถูกขยายโดย cmd.exe)

หากคุณคิดว่าคุณต้องการการขยายตัวแปรสภาพแวดล้อมและไฟล์มืดมนให้ค้นคว้าการILSโจมตีของ 1992-ish บนบริการเครือข่ายที่ดำเนินการเรียกใช้โปรแกรมย่อยผ่านเชลล์ ตัวอย่าง ได้แก่ ต่างๆแบ็เกี่ยวข้องกับsendmailILS

shell=Falseโดยสรุปการใช้งาน


2
ขอบคุณสำหรับคำตอบ. แม้ว่าฉันจะไม่ได้อยู่ในขั้นตอนที่ฉันควรกังวลเกี่ยวกับการหาประโยชน์ แต่ฉันเข้าใจสิ่งที่คุณได้รับ
user225312

55
หากคุณไม่ระมัดระวังในการเริ่มต้นไม่มีความกังวลใด ๆ ที่จะช่วยคุณติดตามในภายหลัง ;)
Heath Hunnicutt

ถ้าคุณต้องการ จำกัด หน่วยความจำสูงสุดของกระบวนการย่อย stackoverflow.com/questions/3172470/…
Pramod

8
คำสั่งเกี่ยวกับ$SHELLไม่ถูกต้อง ในการอ้างอิง subprocess.html: "บน Unix ด้วยshell=Trueค่าเริ่มต้นของเชลล์/bin/shคือ" (ไม่ใช่$SHELL)
marcin

1
@ user2428107: ใช่ถ้าคุณใช้ backtick invocation บน Perl คุณกำลังใช้การเรียกใช้เชลล์และเปิดปัญหาเดียวกัน ใช้ 3+ arg openหากคุณต้องการวิธีที่ปลอดภัยในการเรียกใช้โปรแกรมและบันทึกผลลัพธ์
ShadowRanger

137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

การตั้งค่าอาร์กิวเมนต์ของเชลล์เป็นค่าจริงทำให้กระบวนการย่อยวางกระบวนการเชลล์ขั้นกลางและบอกให้เรียกใช้คำสั่ง กล่าวอีกนัยหนึ่งการใช้เชลล์ระดับกลางหมายความว่าตัวแปรรูปแบบ glob และคุณสมบัติเชลล์พิเศษอื่น ๆ ในสตริงคำสั่งจะถูกประมวลผลก่อนที่คำสั่งจะทำงาน ในตัวอย่างนี้มีการประมวลผล $ HOME ก่อนคำสั่ง echo จริงๆแล้วนี่เป็นกรณีของคำสั่งที่มีการขยายเชลล์ในขณะที่คำสั่ง ls -l ถือเป็นคำสั่งง่าย ๆ

แหล่งที่มา: โมดูลย่อย


16
ไม่ทราบว่าทำไมถึงไม่ใช่คำตอบที่เลือก เท่าที่จริงที่ตรงกับคำถาม
Rodrigo Lopez Guerra

1
ตกลง. นี่เป็นตัวอย่างที่ดีสำหรับฉันที่จะเข้าใจว่า shell = True หมายถึงอะไร
user389955

2
การตั้งค่าอาร์กิวเมนต์ของเชลล์เป็นค่าจริงทำให้กระบวนการย่อยวางกระบวนการเชลล์ระดับกลางและบอกให้เรียกใช้คำสั่ง โอ้สิ่งนี้บอกทุกอย่าง ทำไมคำตอบนี้ไม่ได้รับการยอมรับ ??? ทำไม?
pouya

ฉันคิดว่าปัญหาคืออาร์กิวเมนต์แรกที่เรียกเป็นรายการไม่ใช่สตริง แต่นั่นทำให้เกิดข้อผิดพลาดหากเชลล์เป็นเท็จ การเปลี่ยนคำสั่งเป็นรายการจะทำให้งานนี้
Lincoln Randall McFarland

ขออภัยความคิดเห็นก่อนหน้าของฉันไปก่อนที่ฉันจะทำ เพื่อความชัดเจน: ฉันมักจะเห็น subprocess ใช้กับ shell = True และคำสั่งเป็นสตริงเช่น 'ls -l', (ฉันคาดว่าจะหลีกเลี่ยงข้อผิดพลาดนี้) แต่ subprocess รับรายการ (และสตริงเป็นรายการองค์ประกอบหนึ่งรายการ) . ในการรันโดยไม่ต้องเรียกใช้เชลล์ (และปัญหาด้านความปลอดภัยที่มี ) ให้ใช้รายการ subprocess.call (['ls', '-l'])
Lincoln Randall McFarland

42

ตัวอย่างที่สิ่งต่าง ๆ อาจผิดปกติกับ Shell = True แสดงไว้ที่นี่

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

ตรวจสอบเอกสารที่นี่: subprocess.call ()


6
ลิงค์มีประโยชน์มาก ตามลิงก์ที่ระบุไว้: การประมวลผลคำสั่งเชลล์ที่รวมอินพุตที่ไม่ได้รับอนุญาตจากแหล่งที่ไม่น่าเชื่อถือทำให้โปรแกรมมีความเสี่ยงต่อการฉีดเชลล์ซึ่งเป็นข้อบกพร่องด้านความปลอดภัยที่ร้ายแรงซึ่งอาจส่งผลให้เกิดการเรียกใช้คำสั่ง ด้วยเหตุผลนี้การใช้ shell = True จึงไม่สนับสนุนอย่างยิ่งในกรณีที่สตริงคำสั่งถูกสร้างขึ้นจากอินพุตภายนอก
jtuki

39

การรันโปรแกรมผ่านเชลล์หมายความว่าอินพุตผู้ใช้ทั้งหมดที่ส่งไปยังโปรแกรมถูกตีความตามไวยากรณ์และกฎความหมายของเชลล์ที่เรียกใช้ ที่ดีที่สุดจะทำให้ผู้ใช้ไม่สะดวกเท่านั้นเนื่องจากผู้ใช้จะต้องปฏิบัติตามกฎเหล่านี้ ตัวอย่างเช่นพา ธ ที่มีอักขระเชลล์พิเศษเช่นเครื่องหมายคำพูดหรือช่องว่างต้องถูกหลีกเลี่ยง ที่เลวร้ายที่สุดมันทำให้เกิดการรั่วไหลของการรักษาความปลอดภัยเพราะผู้ใช้สามารถรันโปรแกรมโดยพลการ

shell=Trueบางครั้งสะดวกในการใช้ประโยชน์จากคุณสมบัติของเชลล์เช่นการแยกคำหรือการขยายพารามิเตอร์ อย่างไรก็ตามหากจำเป็นต้องใช้คุณสมบัติดังกล่าวให้ใช้โมดูลอื่น ๆ กับคุณ (เช่นos.path.expandvars()สำหรับการขยายพารามิเตอร์หรือshlexการแยกคำ) นี่หมายถึงทำงานได้มากกว่า แต่หลีกเลี่ยงปัญหาอื่น ๆ

กล่าวโดยย่อ: หลีกเลี่ยงshell=Trueทุกวิถีทาง


16

คำตอบอื่น ๆ ที่นี่อย่างเพียงพออธิบายคำเตือนความปลอดภัยที่กล่าวถึงในsubprocessเอกสารประกอบ แต่นอกเหนือจากนั้นค่าใช้จ่ายในการเริ่มเชลล์เพื่อเริ่มโปรแกรมที่คุณต้องการเรียกใช้มักไม่จำเป็นและโง่มากสำหรับสถานการณ์ที่คุณไม่ได้ใช้ฟังก์ชั่นของเชลล์จริงๆ ยิ่งกว่านั้นความซับซ้อนที่ซ่อนอยู่เพิ่มเติมจะทำให้คุณกลัวโดยเฉพาะหากคุณไม่คุ้นเคยกับเชลล์หรือบริการที่ให้

ในกรณีที่การโต้ตอบกับเชลล์นั้นไม่ใช่เรื่องสำคัญตอนนี้คุณต้องมีผู้อ่านและผู้ดูแลของสคริปต์ Python (ซึ่งอาจเป็นหรืออาจไม่ใช่ตัวคุณเองในอนาคต) เพื่อทำความเข้าใจทั้ง Python และเชลล์สคริปต์ โปรดจำไว้ว่าคำขวัญ Python "ชัดเจนดีกว่าโดยนัย"; แม้ว่ารหัส Python จะค่อนข้างซับซ้อนกว่าเชลล์สคริปต์ที่เทียบเท่ากัน (และบ่อยครั้งมาก) คุณอาจจะดีกว่าที่จะลบเชลล์และแทนที่ฟังก์ชันด้วยการสร้าง Python ดั้งเดิม การลดงานที่ทำในกระบวนการภายนอกและควบคุมรหัสของคุณให้ไกลที่สุดมักเป็นความคิดที่ดีเพราะช่วยปรับปรุงการมองเห็นและลดความเสี่ยงของผลข้างเคียงที่ต้องการหรือไม่พึงประสงค์

การขยายสัญลักษณ์การแก้ไขตัวแปรและการเปลี่ยนเส้นทางทั้งหมดนั้นง่ายต่อการแทนที่ด้วยโครงสร้าง Python ดั้งเดิม ไพพ์ไลน์เชลล์ที่ซับซ้อนที่บางส่วนหรือทั้งหมดไม่สามารถเขียนใหม่ได้อย่างสมเหตุสมผลใน Python จะเป็นสถานการณ์หนึ่งที่บางทีคุณอาจพิจารณาใช้เชลล์ คุณควรตรวจสอบให้แน่ใจว่าคุณเข้าใจถึงประสิทธิภาพและความปลอดภัย

ในกรณีที่เล็กน้อยเพื่อหลีกเลี่ยงshell=Trueเพียงแทนที่

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

กับ

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

ขอให้สังเกตว่าอาร์กิวเมนต์แรกคือรายการของสตริงที่จะส่งผ่านexecvp()และวิธีการอ้างถึงสตริงและอักขระเมตาอักขระเปลือก backslash-escaping โดยทั่วไปไม่จำเป็น (หรือมีประโยชน์หรือถูกต้อง) อาจจะเห็นคำสั่งล้อมรอบตัวแปรเชลล์เมื่อใด

นอกจากนี้คุณมักจะต้องการหลีกเลี่ยงPopenหากหนึ่งในสิ่งห่อหุ้มที่ง่ายกว่าในsubprocessแพ็คเกจทำสิ่งที่คุณต้องการ subprocess.runหากคุณมีมากพอที่ล่าสุดหลามคุณอาจจะใช้

  • ด้วยcheck=Trueมันจะล้มเหลวหากคำสั่งของคุณล้มเหลว
  • ด้วยstdout=subprocess.PIPEมันจะจับเอาท์พุทของคำสั่ง
  • ค่อนข้างจะคลุมเครือuniversal_newlines=Trueเพราะมันจะถอดรหัสเอาท์พุทเป็น Unicode string ที่เหมาะสม (เป็นเพียงbytesการเข้ารหัสระบบมิฉะนั้นบน Python 3)

หากไม่ใช่สำหรับหลาย ๆ งานคุณต้องการcheck_outputรับเอาท์พุทจากคำสั่งขณะตรวจสอบว่ามันสำเร็จหรือcheck_callไม่มีการรวบรวมผลลัพธ์

ฉันจะปิดด้วยคำพูดจาก David Korn: "มันง่ายกว่าที่จะเขียนเชลล์แบบพกพาได้ง่ายกว่าสคริปเชลล์แบบพกพา" แม้subprocess.run('echo "$HOME"', shell=True)จะไม่พกพาไปที่ Windows


ฉันคิดว่าข้อความมาจาก Larry Wall แต่ Google บอกฉันเป็นอย่างอื่น
tripleee

นั่นเป็นการพูดคุยที่สูง - แต่ไม่มีคำแนะนำด้านเทคนิคสำหรับการทดแทน: ที่นี่ฉันบน OS-X พยายามรับ pid ของ Mac App ที่ฉันเปิดตัวผ่านทาง 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- แต่มันใช้งานไม่ได้ยกเว้นว่าฉันจะใช้ shell = True ตอนนี้คืออะไร
Motti Shneor

มีคำถามมากมายเกี่ยวกับวิธีหลีกเลี่ยงshell=Trueส่วนใหญ่มีคำตอบที่ยอดเยี่ยม คุณเกิดขึ้นเพื่อเลือกอันไหนที่เกี่ยวกับสาเหตุแทน
สามคน

@ MottiShneor ขอบคุณสำหรับข้อเสนอแนะ; เพิ่มตัวอย่างง่ายๆ
tripleee

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.