การรันคำสั่ง Bash ใน Python


299

ในเครื่องของฉันฉันใช้สคริปต์ python ซึ่งมีบรรทัดนี้

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

ใช้งานได้ดี

จากนั้นฉันเรียกใช้รหัสเดียวกันบนเซิร์ฟเวอร์และฉันได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

ดังนั้นสิ่งที่ผมทำแล้วคือผมใส่print bashCommandที่พิมพ์ผมกว่าคำสั่งใน terminal os.system()ก่อนที่จะทำงานด้วย

แน่นอนฉันได้รับข้อผิดพลาดอีกครั้ง (เกิดจากos.system(bashCommand)) แต่ก่อนที่ข้อผิดพลาดนั้นจะพิมพ์คำสั่งใน terminal จากนั้นฉันก็แค่คัดลอกเอาท์พุทนั้นและก็คัดลอกแปะไปที่เทอร์มินัลแล้วกด Enter มันใช้งานได้

ไม่มีใครมีเงื่อนงำสิ่งที่เกิดขึ้น?


2
cwmดูเหมือนจะมีความแตกต่างกันในสภาพแวดล้อมขึ้นอยู่กับวิธีการที่คุณทำงาน บางทีคุณอาจมีการกำหนดค่าบางอย่างในการ.bashrcตั้งค่าสภาพแวดล้อมสำหรับการใช้งานทุบตีเชิงโต้ตอบ
Sven Marnach

คุณลองใช้คำสั่งจากบรรทัดคำสั่งเมื่อลงชื่อเข้าใช้บนเซิร์ฟเวอร์หรือไม่ โพสต์ของคุณเพิ่งบอกว่าคุณ "วาง [มัน] ลงในสถานี"
Sven Marnach

@Sven: ใช่ฉันหมายความว่าฉันวิ่งคำสั่งโดยตรงในขั้วของเซิร์ฟเวอร์
MKN

ดูเหมือนว่าจะมีความแตกต่างใน PYTHONPATH ขึ้นอยู่กับว่าคุณวิ่งcwmอย่างไร หรืออาจมีความแตกต่างใน PATH และเวอร์ชันที่ต่างกันcwmถูกเรียก หรือ Python เวอร์ชันอื่น เป็นเรื่องยากมากที่จะเข้าใจสิ่งนี้โดยไม่ต้องใช้เครื่อง ...
Sven Marnach

คำตอบ:


314

os.systemอย่าใช้ มันได้รับการคัดค้านในความโปรดปรานของกระบวนการย่อย จากเอกสาร : "โมดูลนี้ตั้งใจที่จะแทนที่โมดูลและฟังก์ชั่นรุ่นเก่าหลายอัน: os.system, os.spawn"

เช่นเดียวกับในกรณีของคุณ:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

8
นี่ไม่ได้ทำสิ่งที่ฉันต้องการเมื่อฉันต้องทำcd 'path\to\somewhere'ตามด้วยคำสั่ง bash อื่นที่จำเป็นต้องใช้ในบางแห่ง @ user225312
AWrightIV

36
@AWrightIV หากคุณต้องการให้ subprocess ของคุณทำงานในไดเรกทอรีทำงานเฉพาะคุณสามารถใช้cwdอาร์กิวเมนต์เพื่อ Popen:subprocess.Popen(..., cwd='path\to\somewhere')
waterproof

7
สำหรับคำสั่งของฉันฉันต้องการ shell = True เหมือนที่นี่; stackoverflow.com/questions/18962785/…
user984003

4
จะดีกว่าใช้ shlex.split () แทน string.split () ในกรณีนี้
Alexey Sviridov

4
... ( stdout=fileเปลี่ยนเส้นทางผลลัพธ์ไปยังไฟล์ในกรณีนี้ใช้> file) มันจะผิดที่จะผ่าน..., '>', 'file']ในคำสั่งสุดท้ายคาดหวังว่าการเปลี่ยนเส้นทาง (มันจะไม่ทำงานโดยไม่ต้องเปลือกและถ้าคุณใช้เปลือกที่คุณควรจะผ่านคำสั่งเป็นสตริง)
jfs

186

หากต้องการขยายคำตอบก่อนหน้านี้ให้กว้างขึ้นมีจำนวนรายละเอียดที่มักถูกมองข้าม

  • ต้องการsubprocess.run()มากกว่าsubprocess.check_call()และเพื่อนมากกว่าsubprocess.call()มากกว่าsubprocess.Popen()มากกว่าos.system()มากกว่าos.popen()
  • ทำความเข้าใจและอาจจะใช้อาคาtext=Trueuniversal_newlines=True
  • ทำความเข้าใจเกี่ยวกับความหมายshell=Trueหรือshell=Falseวิธีการเปลี่ยนแปลงการอ้างถึงและความพร้อมของสิ่งอำนวยความสะดวกของเชลล์
  • ทำความเข้าใจความแตกต่างระหว่างshและ Bash
  • ทำความเข้าใจว่ากระบวนการย่อยแยกจากหลักและไม่สามารถเปลี่ยนพาเรนต์ได้
  • หลีกเลี่ยงการใช้ Python interpreter เป็น subprocess ของ Python

หัวข้อเหล่านี้จะกล่าวถึงในรายละเอียดเพิ่มเติมด้านล่าง

ชอบsubprocess.run()หรือsubprocess.check_call()

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

นี่คือย่อหน้าจากเอกสาร :

วิธีที่แนะนำในการเรียกใช้กระบวนการย่อยคือการใช้run()ฟังก์ชั่นสำหรับทุกกรณีการใช้งานที่สามารถจัดการได้ สำหรับกรณีการใช้งานขั้นสูงเพิ่มเติมPopenสามารถใช้อินเตอร์เฟสพื้นฐานได้โดยตรง

น่าเสียดายที่ความพร้อมใช้งานของฟังก์ชั่นการห่อหุ้มเหล่านี้แตกต่างกันระหว่างรุ่น Python

  • subprocess.run()เปิดตัวเป็นทางการใน Python 3.5 มีวัตถุประสงค์เพื่อแทนที่ทั้งหมดต่อไปนี้
  • subprocess.check_output()ได้รับการแนะนำใน Python 2.7 / 3.1 มันเป็นพื้นเทียบเท่ากับsubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call()เปิดตัวใน Python 2.5 มันเป็นพื้นเทียบเท่ากับsubprocess.run(..., check=True)
  • subprocess.call()เปิดตัวใน Python 2.4 ในsubprocessโมดูลดั้งเดิม( PEP-324 ) มันเป็นพื้นเทียบเท่ากับsubprocess.run(...).returncode

API ระดับสูง subprocess.Popen()

การปรับโครงสร้างและการขยายเพิ่มเติมsubprocess.run()นั้นมีเหตุผลและหลากหลายมากกว่าฟังก์ชั่นเก่าที่มันเข้ามาแทนที่ มันส่งคืนCompletedProcessวัตถุที่มีวิธีการต่าง ๆ ที่ช่วยให้คุณสามารถเรียกคืนสถานะออกมาตรฐานผลลัพธ์และตัวบ่งชี้ผลลัพธ์และสถานะอื่น ๆ จากกระบวนการย่อยที่เสร็จสิ้นแล้ว

subprocess.run()เป็นวิธีที่จะไปหากคุณต้องการให้โปรแกรมรันและกลับไปควบคุม Python สำหรับสถานการณ์ที่เกี่ยวข้องมากขึ้น (กระบวนการพื้นหลังบางทีด้วย I / O แบบโต้ตอบกับโปรแกรมหลัก Python) คุณยังต้องใช้subprocess.Popen()และดูแลระบบประปาทั้งหมดด้วยตัวเอง สิ่งนี้ต้องการความเข้าใจที่ค่อนข้างซับซ้อนของชิ้นส่วนที่เคลื่อนไหวทั้งหมดและไม่ควรนำมาใช้อย่างเบามือ Popenวัตถุที่เรียบง่ายกว่านั้นแสดงถึงกระบวนการ (อาจยังคงทำงานอยู่) ซึ่งจำเป็นต้องได้รับการจัดการจากรหัสของคุณตลอดอายุการใช้งานของกระบวนการย่อย

บางทีควรจะเน้นว่าเพียงsubprocess.Popen()แค่สร้างกระบวนการ หากคุณปล่อยไว้ที่นั้นคุณจะมีกระบวนการย่อยทำงานพร้อมกับ Python ดังนั้นกระบวนการ "พื้นหลัง" หากไม่จำเป็นต้องป้อนข้อมูลหรือส่งออกหรือประสานงานกับคุณก็สามารถทำงานที่มีประโยชน์ควบคู่ไปกับโปรแกรม Python ของคุณ

หลีกเลี่ยงos.system()และos.popen()

ตั้งแต่เวลานิรันดร์ (ดีตั้งแต่ Python 2.5) osเอกสารโมดูลได้มีคำแนะนำที่ต้องการsubprocessมากกว่าos.system():

subprocessโมดูลให้สิ่งอำนวยความสะดวกที่มีประสิทธิภาพมากขึ้นสำหรับการวิ่งพล่านกระบวนการใหม่และเรียกผลของพวกเขา; การใช้โมดูลนั้นจะดีกว่าการใช้ฟังก์ชั่นนี้

ปัญหาที่เกิดขึ้นsystem()คือมันขึ้นอยู่กับระบบอย่างชัดเจนและไม่ได้เสนอวิธีการโต้ตอบกับกระบวนการย่อย มันทำงานเพียงแค่มีเอาต์พุตมาตรฐานและข้อผิดพลาดมาตรฐานนอกเหนือจากการเข้าถึงของ Python ข้อมูลเดียวที่ Python ได้รับกลับมาคือสถานะออกของคำสั่ง (ศูนย์หมายถึงความสำเร็จแม้ว่าความหมายของค่าที่ไม่เป็นศูนย์ก็ขึ้นอยู่กับระบบด้วยเช่นกัน)

PEP-324 (ซึ่งถูกกล่าวถึงแล้วข้างต้น) มีเหตุผลที่ละเอียดกว่านี้สำหรับสาเหตุที่os.systemเป็นปัญหาและsubprocessความพยายามแก้ไขปัญหาเหล่านั้นอย่างไร

os.popen()เคยเป็นกำลังใจอย่างยิ่งยิ่งขึ้น:

เลิกใช้แล้วตั้งแต่เวอร์ชัน 2.6:ฟังก์ชันนี้ล้าสมัย ใช้subprocessโมดูล

อย่างไรก็ตามเนื่องจากใน Python 3 บางครั้งมีการปรับใช้ใหม่เพื่อใช้งานง่ายsubprocessและเปลี่ยนเส้นทางไปยังsubprocess.Popen()เอกสารประกอบเพื่อดูรายละเอียด

ทำความเข้าใจและมักจะใช้ check=True

คุณจะสังเกตเห็นว่าsubprocess.call()มีข้อ จำกัดos.system()มากมายเช่นกัน ในการใช้งานตามปกติโดยทั่วไปคุณควรตรวจสอบว่ากระบวนการเสร็จสิ้นเรียบร้อยแล้วซึ่งsubprocess.check_call()และsubprocess.check_output()ทำ (ที่หลังยังส่งกลับออกมาตรฐานของกระบวนการย่อยสำเร็จรูป) ในทำนองเดียวกันคุณควรใช้check=Trueกับsubprocess.run()เว้นแต่คุณจะต้องอนุญาตให้กระบวนการย่อยส่งคืนสถานะข้อผิดพลาดโดยเฉพาะ

ในทางปฏิบัติด้วยcheck=Trueหรือsubprocess.check_*Python จะส่งCalledProcessErrorข้อยกเว้นหากกระบวนการย่อยส่งคืนสถานะการออกที่ไม่ใช่ศูนย์

ข้อผิดพลาดทั่วไปที่มีsubprocess.run()คือละเว้นcheck=Trueและประหลาดใจเมื่อโค้ดดาวน์สตรีมล้มเหลวหากกระบวนการย่อยล้มเหลว

ในทางกลับกันปัญหาที่พบบ่อยกับcheck_call()และcheck_output()คือผู้ใช้ที่ใช้ฟังก์ชั่นเหล่านี้สุ่มสี่สุ่มห้าประหลาดใจเมื่อข้อยกเว้นถูกยกขึ้นเช่นเมื่อgrepไม่พบการแข่งขัน (คุณควรแทนที่grepด้วยรหัสไพ ธ อนต่อไปตามที่อธิบายไว้ด้านล่าง)

คุณต้องเข้าใจว่าคำสั่งเชลล์ส่งคืนโค้ดออกและภายใต้เงื่อนไขใดที่พวกเขาจะส่งคืนโค้ดออกที่ไม่ใช่ศูนย์ (ข้อผิดพลาด) และทำการตัดสินใจอย่างมีสติว่าควรจัดการอย่างไร

เข้าใจและอาจใช้text=Trueอาคาuniversal_newlines=True

ตั้งแต่ Python 3 สตริงภายในของ Python เป็นสตริง Unicode แต่ไม่รับประกันว่ากระบวนการย่อยจะสร้างเอาต์พุต Unicode หรือสตริงเลย

(หากความแตกต่างไม่ชัดเจนในทันทีเราแนะนำให้ใช้ Pragmatic Unicodeของ Ned Batchelder ถ้าไม่จำเป็นต้องอ่านทันทีมีการนำเสนอวิดีโอ 36 นาทีหลังลิงค์หากคุณต้องการ แต่การอ่านหน้าตัวเองอาจใช้เวลาน้อยลงอย่างมาก )

ไพ ธ อนต้องดึงข้อมูลbytesบัฟเฟอร์และตีความอย่างใด ถ้ามันมีข้อมูลไบนารี่จำนวนมากมันไม่ควรถูกถอดรหัสลงในสตริง Unicode เพราะนั่นเป็นพฤติกรรมที่เกิดข้อผิดพลาดและทำให้เกิดข้อผิดพลาดซึ่งเป็นพฤติกรรมที่น่ารำคาญที่ทำให้สคริปต์ Python 2 จำนวนมากถูกต้องก่อนที่จะมีวิธี แยกความแตกต่างระหว่างข้อความที่เข้ารหัสและข้อมูลไบนารีอย่างถูกต้อง

ด้วยtext=Trueคุณบอกว่างูหลามที่คุณในความเป็นจริงคาดหวังว่าข้อมูลกลับต้นฉบับเดิมในการเข้ารหัสเริ่มต้นของระบบและว่ามันควรจะถอดรหัสเป็นงูหลาม (Unicode) สตริงที่ดีที่สุดของความสามารถของงูใหญ่ (ปกติ UTF-8 ที่ใด ๆ ในระดับปานกลางขึ้นไป ระบบวันที่ยกเว้น Windows)

ถ้านั่นไม่ใช่สิ่งที่คุณร้องขอกลับ Python จะให้bytesสตริงกับคุณstdoutและstderrสตริง บางทีในบางจุดหลังจากนั้นคุณจะรู้ว่าพวกเขาเป็นสตริงข้อความหลังจากทั้งหมดและคุณจะรู้ว่าการเข้ารหัสของพวกเขา จากนั้นคุณสามารถถอดรหัสได้

normal = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True,
    text=True)
print(normal.stdout)

convoluted = subprocess.run([external, arg],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE,
    check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))

งูหลาม 3.7 แนะนำนามแฝงสั้นและอธิบายเพิ่มเติมและเข้าใจสำหรับการโต้แย้งคำหลักซึ่งก่อนหน้านี้ค่อนข้างเรียกว่าทำให้เข้าใจผิดtextuniversal_newlines

ทำความเข้าใจshell=Trueกับshell=False

เมื่อshell=Trueคุณส่งสตริงเดียวไปยังเชลล์ของคุณและเชลล์นำมาจากที่นั่น

เมื่อshell=Falseคุณส่งรายการอาร์กิวเมนต์ไปยัง OS ให้ข้ามเชลล์

เมื่อคุณไม่มีเชลล์คุณจะบันทึกกระบวนการและกำจัดความซับซ้อนที่ซ่อนอยู่ซึ่งค่อนข้างมากซึ่งอาจหรืออาจจะไม่ได้ดักจับข้อบกพร่องหรือแม้แต่ปัญหาด้านความปลอดภัย

ในทางกลับกันเมื่อคุณไม่มีเชลล์คุณไม่มีการเปลี่ยนเส้นทางการขยายสัญลักษณ์การควบคุมงานและคุณลักษณะเชลล์อื่น ๆ จำนวนมาก

ข้อผิดพลาดทั่วไปคือการใช้shell=Trueแล้วยังส่ง Python รายการโทเค็นหรือในทางกลับกัน สิ่งนี้เกิดขึ้นกับการทำงานในบางกรณี แต่ไม่ชัดเจนและอาจแตกต่างกันในวิธีที่น่าสนใจ

# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')

# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    shell=True)

# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
    shell=True)

correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
    # Probably don't forget these, too
    check=True, text=True)

# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
    shell=True,
    # Probably don't forget these, too
    check=True, text=True)

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

ตัวอย่างการสร้างใหม่

บ่อยครั้งที่คุณสมบัติของเชลล์สามารถถูกแทนที่ด้วยโค้ด Python ดั้งเดิม ง่าย ๆ หรือsedสคริปต์ควรแปลเป็น Python แทน

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

cmd = '''while read -r x;
   do ping -c 3 "$x" | grep 'round-trip min/avg/max'
   done <hosts.txt'''

# Trivial but horrible
results = subprocess.run(
    cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)

# Reimplement with shell=False
with open('hosts.txt') as hosts:
    for host in hosts:
        host = host.rstrip('\n')  # drop newline
        ping = subprocess.run(
             ['ping', '-c', '3', host],
             text=True,
             stdout=subprocess.PIPE,
             check=True)
        for line in ping.stdout.split('\n'):
             if 'round-trip min/avg/max' in line:
                 print('{}: {}'.format(host, line))

บางสิ่งที่ควรทราบที่นี่:

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

รหัส refactored ยังแสดงให้เห็นว่าเชลล์มีประโยชน์ต่อคุณมากเพียงใดด้วยซินแท็กซ์ที่สั้นมาก - ดีขึ้นหรือแย่ลง Python กล่าวอย่างชัดเจนดีกว่าโดยนัยแต่รหัส Python คือค่อนข้างละเอียดและเนื้อหาที่มีลักษณะที่ซับซ้อนมากขึ้นกว่านี้จริงๆ ในทางกลับกันมันมีหลายจุดที่คุณสามารถควบคุมได้ตรงกลางของสิ่งอื่น ๆ ดังที่ยกตัวอย่างเล็กน้อยโดยการปรับปรุงที่เราสามารถรวมชื่อโฮสต์พร้อมกับเอาต์พุตคำสั่งเชลล์ได้อย่างง่ายดาย (สิ่งนี้ไม่ได้เป็นการท้าทายที่จะทำในเชลล์เช่นกัน แต่ด้วยค่าใช้จ่ายในการเบี่ยงเบนความสนใจและอาจเป็นกระบวนการอื่น)

โครงสร้างเชลล์สามัญ

เพื่อความสมบูรณ์นี่คือคำอธิบายสั้น ๆ ของคุณสมบัติเชลล์เหล่านี้บางส่วนและหมายเหตุบางประการเกี่ยวกับวิธีที่พวกเขาสามารถถูกแทนที่ด้วยอุปกรณ์อำนวยความสะดวกดั้งเดิมของ Python

  • globbing ขยายตัวสัญลักษณ์แทน aka สามารถถูกแทนที่ด้วยหรือมากมักจะมีการเปรียบเทียบสตริงหลามง่ายๆเช่นglob.glob() for file in os.listdir('.'): if not file.endswith('.png'): continueBash มีสิ่งอำนวยความสะดวกการขยายอื่น ๆ เช่น.{png,jpg}การขยายรั้งและการขยายตัว{1..100}หนอน ( ~ขยายไปยังโฮมไดเร็กตอรี่ของคุณ, และโดยทั่วไป~accountไปยังโฮมไดเร็กตอรี่ของผู้ใช้รายอื่น)
  • ตัวแปรเชลล์เช่น$SHELLหรือ$my_exported_varบางครั้งสามารถถูกแทนที่ด้วยตัวแปร Python ตัวแปรเปลือกส่งออกมีอยู่เป็นเช่นos.environ['SHELL'](ความหมายของการexportที่จะทำให้ตัวแปรที่มีให้กระบวนการย่อย -. ตัวแปรที่ไม่สามารถใช้ได้กับกระบวนการย่อยจะเห็นได้ชัดว่าไม่สามารถใช้ได้กับงูหลามทำงานตามกระบวนการย่อยของเปลือกหรือในทางกลับกันการให้env=คำหลัก การโต้แย้งกับsubprocessวิธีการช่วยให้คุณสามารถกำหนดสภาพแวดล้อมของกระบวนการย่อยเป็นพจนานุกรมดังนั้นจึงเป็นวิธีหนึ่งที่จะทำให้ตัวแปร Python สามารถมองเห็นได้ในกระบวนการย่อย) ด้วยshell=Falseคุณจะต้องเข้าใจวิธีการลบคำพูดใด ๆ ; ตัวอย่างเช่นcd "$HOME"เทียบเท่าos.chdir(os.environ['HOME'])ไม่มีเครื่องหมายคำพูดล้อมรอบชื่อไดเรกทอรี (บ่อยมากcdไม่มีประโยชน์หรือจำเป็นต่อไปและผู้เริ่มต้นหลายคนไม่ใส่เครื่องหมายคำพูดคู่รอบ ๆ ตัวแปรและออกไปจนกว่าจะถึงหนึ่งวัน ... )
  • การเปลี่ยนเส้นทางช่วยให้คุณอ่านจากไฟล์เป็นอินพุตมาตรฐานของคุณและเขียนเอาต์พุตมาตรฐานของคุณไปยังไฟล์ grep 'foo' <inputfile >outputfileเปิดoutputfileสำหรับการเขียนและinputfileสำหรับการอ่านและส่งผ่านเนื้อหาเป็นอินพุตมาตรฐานไปgrepที่ซึ่งเอาต์พุตมาตรฐานจะตกลงoutputfileมา ซึ่งโดยทั่วไปจะไม่ยากที่จะแทนที่ด้วยรหัส Python ดั้งเดิม
  • ท่อเป็นรูปแบบของการเปลี่ยนเส้นทาง echo foo | nlรันสองกระบวนการย่อยโดยที่เอาต์พุตมาตรฐานของechoคืออินพุตมาตรฐานของnl(บนระดับระบบปฏิบัติการในระบบ Unix-like นี้เป็นตัวจัดการไฟล์เดียว) ถ้าคุณไม่สามารถแทนที่หนึ่งหรือทั้งสองด้านของท่อด้วยรหัสหลามพื้นเมืองอาจจะคิดเกี่ยวกับการใช้เปลือกหลังจากทั้งหมดโดยเฉพาะอย่างยิ่งถ้าท่อมีมากกว่าสองหรือสามกระบวนการ ( แต่ดูที่pipesโมดูลในห้องสมุดมาตรฐานงูใหญ่หรือจำนวน ของคู่แข่งบุคคลที่สามที่ทันสมัยและหลากหลายมากขึ้น)
  • การควบคุมงานช่วยให้คุณสามารถขัดจังหวะงาน, เรียกใช้งานในพื้นหลัง, ส่งคืนงานเบื้องหน้า, ฯลฯ สัญญาณ Unix ขั้นพื้นฐานเพื่อหยุดและดำเนินการกระบวนการต่อได้แน่นอนจาก Python เช่นกัน แต่งานเป็นสิ่งที่เป็นนามธรรมในระดับที่สูงขึ้นในเชลล์ซึ่งเกี่ยวข้องกับกลุ่มกระบวนการ ฯลฯ ซึ่งคุณต้องเข้าใจถ้าคุณต้องการทำสิ่งนี้จาก Python
  • การอ้างถึงเชลล์อาจทำให้คุณสับสนจนกว่าคุณจะเข้าใจว่าทุกอย่างนั้นเป็นสตริง ดังนั้นจึงls -l /เทียบเท่ากับ'ls' '-l' '/'แต่การอ้างอิงรอบตัวอักษรนั้นเป็นตัวเลือกที่สมบูรณ์ สตริงที่ไม่ได้ใส่เครื่องหมายคำพูดที่มีอักขระเชลล์ตัวอักขระผ่านการขยายพารามิเตอร์การทำโทเค็นช่องว่างและการขยายสัญลักษณ์ เครื่องหมายอัญประกาศคู่ป้องกันการโทเค็นช่องว่างและการขยาย wildcard แต่อนุญาตให้มีการขยายพารามิเตอร์ (การทดแทนตัวแปรการทดแทนคำสั่งและการประมวลผลแบ็กสแลช) นี่เป็นทฤษฎีง่ายๆ แต่สามารถทำให้สับสนโดยเฉพาะอย่างยิ่งเมื่อมีการตีความหลายชั้น (เช่นคำสั่งเชลล์ระยะไกล)

ทำความเข้าใจความแตกต่างระหว่างshและ Bash

subprocessรันคำสั่งเชลล์ของคุณด้วย/bin/shเว้นแต่คุณจะร้องขอเป็นอย่างอื่นโดยเฉพาะ (ยกเว้นแน่นอนบน Windows ซึ่งมันใช้ค่าของCOMSPECตัวแปร) ซึ่งหมายความว่าคุณสมบัติต่างๆของ Bash-only เท่านั้นเช่นอาร์เรย์[[ฯลฯจะไม่สามารถใช้งานได้

หากคุณต้องการใช้ไวยากรณ์ของ Bash เท่านั้นคุณสามารถส่งผ่านพา ธ ไปที่เชลล์เป็นexecutable='/bin/bash'(ซึ่งแน่นอนว่าถ้าติดตั้ง Bash ของคุณไว้ที่อื่นคุณต้องปรับพา ธ )

subprocess.run('''
    # This for loop syntax is Bash only
    for((i=1;i<=$#;i++)); do
        # Arrays are Bash-only
        array[i]+=123
    done''',
    shell=True, check=True,
    executable='/bin/bash')

A subprocessแยกต่างหากจากพาเรนต์และไม่สามารถเปลี่ยนได้

ข้อผิดพลาดที่ค่อนข้างบ่อยคือทำอะไรบางอย่างเช่น

subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True)  # Doesn't work

ซึ่งนอกเหนือจากการขาดความสง่างามยังทรยศต่อการขาดพื้นฐานของความเข้าใจในส่วน "ย่อย" ของชื่อ "subprocess"

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

การแก้ไขทันทีในกรณีนี้คือการรันทั้งสองคำสั่งในกระบวนการย่อยเดียว

subprocess.run('foo=bar; echo "$foo"', shell=True)

แม้ว่าจะเห็นได้ชัดว่ากรณีการใช้งานเฉพาะนี้ไม่จำเป็นต้องใช้เชลล์เลย จำไว้ว่าคุณสามารถจัดการสภาพแวดล้อมของกระบวนการปัจจุบัน (และทำให้ลูกของมัน) ผ่านทาง

os.environ['foo'] = 'bar'

หรือส่งการตั้งค่าสภาพแวดล้อมไปยังกระบวนการลูกด้วย

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(ไม่ต้องพูดถึง refactoring ที่เห็นได้ชัดsubprocess.run(['echo', 'bar'])แต่echoเป็นตัวอย่างที่ดีของบางสิ่งบางอย่างที่จะเรียกใช้ในกระบวนการย่อยในตอนแรกแน่นอน)

อย่าเรียกใช้ Python จาก Python

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

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

หากคุณต้องการความเท่าเทียมคุณสามารถเรียกใช้ฟังก์ชั่น Python ในกระบวนการย่อยด้วยmultiprocessingโมดูล นอกจากนี้ยังมีการthreadingเรียกใช้งานหลายงานในกระบวนการเดียว (ซึ่งมีน้ำหนักเบากว่าและให้การควบคุมมากขึ้น แต่ยังมีข้อ จำกัด มากขึ้นในเธรดภายในกระบวนการที่มีการเชื่อมโยงอย่างแน่นหนาและผูกพันกับGILเดียว)


2
สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีที่คุณอาจหลีกเลี่ยงการเรียก Python เป็น subprocess ดูคำตอบนี้ในคำถามที่คล้ายกัน
tripleee

4
มันรบกวนความคิดของฉันว่าฉันต้องโพสต์คำตอบใหม่สำหรับคำถามพื้นฐานเพื่อแสดงวิธีเรียกใช้คำสั่งจากคำถามที่เป็นไปในทางที่ผิด คำตอบของคุณยาว แต่ฉันไม่เห็นตัวอย่างนี้ ไม่เกี่ยวข้อง: หลีกเลี่ยงการบรรทุกสินค้า ถ้า check_call () ใช้งานได้ในกรณีของคุณให้ใช้มัน ฉันต้องแก้ไขรหัสที่ใช้แบบrun()สุ่มสี่สุ่มห้า การขาดหายไปcheck=Trueทำให้เกิดข้อผิดพลาดซึ่งจะหลีกเลี่ยงหากใช้ check_call - "ตรวจสอบ" อยู่ในชื่อคุณจะไม่สามารถสูญเสียมันได้ - เป็นค่าเริ่มต้นที่ถูกต้อง: อย่าเพิกเฉยข้อผิดพลาดอย่างเงียบ ๆ ฉันไม่ได้อ่านเพิ่มเติม
jfs

1
@jfs ขอบคุณสำหรับคำติชมฉันจริง ๆ แล้วก็วางแผนที่จะเพิ่มหัวข้อเกี่ยวกับ Bash vs shแต่คุณเอาชนะฉันได้ ฉันพยายามที่จะระบุรายละเอียดเฉพาะอย่างเพียงพอเพื่อช่วยผู้เริ่มต้นที่ข้อผิดพลาดเหล่านี้ไม่ชัดเจนเพื่อที่จะได้รับลมแรงเล็กน้อย คุณควรจะเพียงพออย่างอื่น; +1
tripleee

ไม่stderr/stdout = subprocess.PIPEต้องมีค่าใช้จ่ายที่มีประสิทธิภาพสูงกว่าการตั้งค่าเริ่มต้นได้อย่างไร
Stringers

1
@ ผู้สกัดกั้นฉันไม่ได้ทดสอบ แต่ฉันไม่เห็นว่าทำไมจึงควรทำ หากคุณเชื่อมต่อท่อเหล่านั้นเข้ากับสิ่งที่ใช้ในการประมวลผลแน่นอนว่าการประมวลผลนั้นจำเป็นต้องใช้ แต่มันไม่ได้เกิดขึ้นในท่อ เริ่มต้นคือการไม่จับ stdout หรือ stderr os.system()ที่ทุกคนคือสิ่งที่ได้รับการตีพิมพ์ออกมามีการมองเห็นงูใหญ่และการควบคุมเช่นเดียวกับ
tripleee

41

เรียกมันว่า subprocess

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

ดูเหมือนว่าข้อผิดพลาดที่คุณได้รับเนื่องจากไม่มีโมดูล swap บนเซิร์ฟเวอร์คุณควรติดตั้ง swap บนเซิร์ฟเวอร์จากนั้นเรียกใช้สคริปต์อีกครั้ง


3
swapโมดูลจะเห็นได้ชัดนั่นเพราะใช้คำสั่งจากเปลือกผลงาน
Sven Marnach

2
ไม่ได้อยู่บนเซิร์ฟเวอร์เมื่อเขารันบนเซิร์ฟเวอร์มีข้อผิดพลาดในการนำเข้า
Jakob Bowyer

@mkn: "จากนั้นฉันเพิ่งคัดลอกเอาท์พุทและคัดลอกแปะไปที่เครื่องเทอร์มินัลแล้วกด Enter มันใช้งานได้ ... " - คุณลองมันบนเซิร์ฟเวอร์หรือบนเครื่องของคุณหรือเปล่า?
Sven Marnach

คุณใช้งานคอมพิวเตอร์แบบสแตนด์อโลน แต่มันไม่ทำงานเมื่อคุณใช้งานบนเซิร์ฟเวอร์ของคุณหรือไม่? หรือคุณสามารถเรียกใช้งานบนเซิร์ฟเวอร์เทอร์มินัล แต่ไม่ใช่เซิร์ฟเวอร์เอง
Jakob Bowyer

1
มันเป็นเรื่องที่ไม่ถูกต้องหากคุณไม่ได้ใช้shell=Trueแล้วคุณควรจะใช้รายการที่จะผ่านการขัดแย้งหลายเช่นใช้แทน['a', 'b', 'c'] 'a b c'แม้ว่าการแยกแบบไร้เดียงสาจะไม่ทำงานเนื่องจาก> file(การเปลี่ยนเส้นทางเชลล์) ในคำสั่ง รายละเอียดเพิ่มเติม
jfs

18

เป็นไปได้ที่คุณจะใช้โปรแกรม bash พร้อมกับพารามิเตอร์ -c สำหรับดำเนินการคำสั่ง:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])

2
subprocess.check_output(bashCommand, shell=True)ทำสิ่งเดียวกัน หากคำสั่งของคุณเป็นสตริงคงพยายามแยกมันลงในรายการตัวเองและหลีกเลี่ยงการshell=True; แต่ในกรณีนี้คุณจะต้องเปลือกสำหรับการเปลี่ยนเส้นทางต่อไปหรืออื่น ๆ คุณจะต้อง refactor มันหลามบริสุทธิ์ -with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)
tripleee

@tripleee note: /bin/sh(ใช้โดย subprocess) ไม่จำเป็นbash(คุณไม่สามารถใช้ bashisms) แม้ว่าหนึ่งสามารถใช้executable='/bin/bashถ้าต้องการ นี่คือตัวอย่างรหัส
jfs

2
มันเป็นคำตอบแรกที่คำสั่งควรเริ่มต้นที่ประสบความสำเร็จ (ได้รับการยอมรับและคำตอบที่ได้รับความนิยมที่ 2 มีความผิดเพียงเล่นลิ้นเล็ก ๆ น้อย ๆ :. check_output()ไม่มีประโยชน์ที่นี่ (output อยู่เสมอว่างเนื่องจากการ> fileเปลี่ยนเส้นทาง; ใช้check_call(). แทน
jfs

16

คุณสามารถใช้subprocessแต่ฉันมักจะรู้สึกว่ามันไม่ใช่วิธีการ 'Pythonic' ในการทำเช่นนั้น ดังนั้นฉันจึงสร้างสุลต่าน (ปลั๊กไร้ยางอาย) ซึ่งทำให้ง่ายต่อการเรียกใช้ฟังก์ชันบรรทัดคำสั่ง

https://github.com/aeroxis/sultan


3
ทำได้ดี! ทำความสะอาดและใช้งานง่ายกว่ากระบวนการย่อยมาก
mjd2

ขอบคุณมาก! ฉันดีใจที่ได้ยินเช่นนั้น!
ดาเนีย

2
สิ่งนี้ควรนำไปใช้จริงกับห้องสมุดมาตรฐาน
Joshua Detwiler

1
มีวิธีการจับเอาท์พุทจากเครื่องโดยใช้ Sultan หรือไม่?
alvas

ใช่คุณสามารถ @alvas ... นี่คือเอกสารเกี่ยวกับวิธีการทำ: sultan.readthedocs.io/en/latest/…
David Daniel

7

ตามข้อผิดพลาดคุณไม่มีแพ็คเกจชื่อswapบนเซิร์ฟเวอร์ สิ่งนี้/usr/bin/cwmต้องการมัน หากคุณใช้ Ubuntu / Debian ให้ติดตั้งpython-swapโดยใช้ความถนัด


แต่มันใช้งานได้เมื่อฉันรันมันโดยตรงในเทอร์มินัล ...
mkn

มีสองตัวเลือก ไม่สามารถค้นหาได้swapหรือไม่ควรนำเข้ามาตั้งแต่แรก คุณสามารถทำได้import swapด้วยตัวเอง? มันทำงานอย่างไร
kichik

อืมฉันทำไม่ได้ ถ้าฉันเริ่มต้นหลามด้วยการพิมพ์หลามในเทอร์มินัลจากนั้นฉันพิมพ์นำเข้าสลับแล้วฉันได้รับข้อผิดพลาด "ImportError: ไม่มีโมดูลชื่อ swap" สิ่งประหลาดยังคงทำงานได้เมื่อฉันเรียกใช้คำสั่ง cwm โดยตรงในเทอร์มินัลเซิร์ฟเวอร์
mkn

ลองพิมพ์ในsys.pathที่ทำงานและที่ไม่เหมาะสม จากนั้นลองค้นหาโฟลเดอร์ swap หรือ swap.py ในโฟลเดอร์ที่พิมพ์ ดังที่สเวนกล่าวว่าอาจมีปัญหากับเส้นทางเหล่านั้นและสิ่งนี้จะช่วยให้คุณเข้าใจได้
kichik

4

นอกจากนี้คุณสามารถใช้ 'os.popen' ตัวอย่าง:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

เอาท์พุท:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None

1
เอกสารประกอบประกอบด้วยกล่องสีแดงขนาดใหญ่: " เลิกใช้แล้วตั้งแต่รุ่น 2.6:ฟังก์ชั่นนี้ล้าสมัยใช้subprocessโมดูล"
tripleee

1
ในความเป็นธรรมos.popenไม่มีคำเตือนนี้อีกต่อไปและเป็นเพียงเสื้อคลุมบาง ๆ ในsubprocess.Popen()ขณะนี้
tripleee

4

ในการรันคำสั่งโดยไม่มีเชลล์ให้ส่งคำสั่งเป็นรายการและใช้การเปลี่ยนเส้นทางใน Python โดยใช้[subprocess]:

#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

หมายเหตุ: ไม่มี> test.ntที่สิ้นสุด stdout=fileดำเนินการเปลี่ยนเส้นทาง


ในการรันคำสั่งโดยใช้เชลล์ใน Python ให้ส่งคำสั่งเป็นสตริงและเปิดใช้งานshell=True:

#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

นี่คือเชลล์ที่รับผิดชอบการเปลี่ยนเส้นทางเอาต์พุต ( > test.ntอยู่ในคำสั่ง)


ในการรันคำสั่ง bash ที่ใช้ bashisms ให้ระบุ bash executable อย่างชัดเจนเช่นเพื่อจำลองการทดแทนกระบวนการ bash :

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')

อาจพูดถึงว่า.split()ไม่เพียงพอเมื่อมีสตริงที่ยกมา ฯลฯ มีรูทีนแยกต่างหากshlex.split()ซึ่ง copes กับไวยากรณ์เชลล์ที่ซับซ้อนโดยพลการ
tripleee

@tripleee .split()งานในกรณีนี้ shlex.split()อาจมีประโยชน์ในบางครั้ง แต่อาจล้มเหลวในบางกรณีด้วย มีหลายสิ่งที่ยอดเยี่ยมที่สามารถกล่าวถึงได้ คุณสามารถเริ่มต้นด้วยลิงก์ไปยังคำอธิบายแท็กกระบวนการย่อยที่ให้ไว้ข้างต้น
jfs

0

วิธีการทำสิ่งต่อไปนี้ใช้ subprocess.Popen

subprocess.Popen ใช้รายการที่องค์ประกอบแรกคือคำสั่งที่จะเรียกใช้ตามด้วยอาร์กิวเมนต์บรรทัดคำสั่งใด ๆ

ตัวอย่างเช่น:

import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line

ไม่ตัวอย่างสุดท้ายเหมือนกับการรันecho -v '"Hello Again!"'ด้วยเครื่องหมายคำพูดเดี่ยวรอบเครื่องหมายคำพูดคู่
tripleee

นอกจากนี้ในการใช้อย่างถูกต้องsubprocesss.Popenคุณจะต้องจัดการวัตถุกระบวนการที่เกิดขึ้น (อย่างน้อยให้ทำ a wait()เพื่อป้องกันไม่ให้กลายเป็นกระบวนการซอมบี้)
tripleee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.