บทสรุปผู้บริหาร (หรือรุ่น "tl; dr"): มันง่ายเมื่อมีมากที่สุดsubprocess.PIPE
มิฉะนั้นมันก็ยาก
อาจถึงเวลาอธิบายเล็กน้อยเกี่ยวกับsubprocess.Popen
สิ่งที่เกิดขึ้น
(Caveat: นี่สำหรับ Python 2.x แม้ว่า 3.x จะคล้ายกันและฉันค่อนข้างคลุมเครือกับตัวแปร Windows ฉันเข้าใจ POSIX ดีกว่ามาก)
Popen
ฟังก์ชั่นความต้องการที่จะจัดการกับศูนย์ถึงสาม I / O ลำธารค่อนข้างพร้อมกัน เหล่านี้จะแสดงstdin
, stdout
และstderr
ตามปกติ
คุณสามารถให้:
None
ระบุว่าคุณไม่ต้องการเปลี่ยนเส้นทาง มันจะสืบทอดสิ่งเหล่านี้ตามปกติแทน โปรดทราบว่าในระบบ POSIX อย่างน้อยนี่ไม่ได้หมายความว่ามันจะใช้ Python sys.stdout
เพียงแค่stdout จริงของ Python ; ดูการสาธิตที่ส่วนท้าย
int
ค่า นี่เป็นตัวอธิบายไฟล์ "raw" (ใน POSIX อย่างน้อย) (หมายเหตุด้านข้าง: PIPE
และSTDOUT
แท้จริงแล้วเป็นการint
ภายใน แต่เป็นคำอธิบายที่ "เป็นไปไม่ได้" -1 และ -2)
- กระแส - จริง ๆ วัตถุใด ๆ ที่มี
fileno
วิธีการ Popen
จะค้นหา descriptor สำหรับสตรีมนั้นโดยใช้stream.fileno()
จากนั้นดำเนินการตามint
ค่า
subprocess.PIPE
ระบุว่า Python ควรสร้างไพพ์
subprocess.STDOUT
(สำหรับstderr
เท่านั้น): stdout
บอกงูหลามที่จะใช้อธิบายเช่นเดียวกับ สิ่งนี้เหมาะสมเท่านั้นหากคุณให้ค่า (ไม่ใช่ - None
) สำหรับstdout
และถึงตอนนั้นคุณจำเป็นต้องตั้งค่าstdout=subprocess.PIPE
เท่านั้น (มิฉะนั้นคุณสามารถระบุอาร์กิวเมนต์เดียวกับที่คุณให้ไว้สำหรับstdout
เช่นPopen(..., stdout=stream, stderr=stream)
)
กรณีที่ง่ายที่สุด (ไม่มีท่อ)
หากคุณไม่เปลี่ยนเส้นทาง (ปล่อยให้ทั้งสามเป็นค่าเริ่มต้นNone
หรือระบุอย่างชัดเจนNone
) คุณPipe
สามารถทำได้อย่างง่ายดาย เพียงแค่ต้องหมุนกระบวนการย่อยและปล่อยให้มันทำงาน หรือหากคุณเปลี่ยนเส้นทางไปยังผู้ที่ไม่ใช่PIPE
- int
หรือสตรีมfileno()
- มันยังง่ายเพราะระบบปฏิบัติการทำงานได้ทั้งหมด Python เพียงแค่ต้องการแยกโพรเซสซิงออกเชื่อมต่อ stdin, stdout และ / หรือ stderr กับ file descriptors ที่มีให้
กรณีที่ยังคงง่าย: หนึ่งท่อ
หากคุณเปลี่ยนเส้นทางสตรีมเพียงรายการเดียวPipe
ก็ยังมีสิ่งที่ค่อนข้างง่าย ลองเลือกสตรีมทีละรายการแล้วดู
สมมติว่าคุณต้องการจัดหาบางอย่างstdin
แต่ปล่อยstdout
และstderr
ไปที่ไม่ได้เปลี่ยนเส้นทางหรือไปที่ตัวให้คำอธิบายไฟล์ ในฐานะที่เป็นกระบวนการหลักโปรแกรม Python ของคุณเพียงแค่ต้องใช้write()
ในการส่งข้อมูลลงในไพพ์ คุณสามารถทำได้ด้วยตัวเองเช่น:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
หรือคุณสามารถส่งผ่านข้อมูล stdin ไปยังproc.communicate()
ซึ่งจะstdin.write
แสดงด้านบน ไม่มีการส่งออกกลับมาดังนั้นจึงcommunicate()
มีงานจริงเพียงงานเดียวเท่านั้นมันปิดไปป์ไลน์สำหรับคุณ (ถ้าคุณไม่โทรproc.communicate()
คุณต้องโทรproc.stdin.close()
เพื่อปิดไพพ์เพื่อให้กระบวนการย่อยรู้ว่าไม่มีข้อมูลผ่านเข้ามาอีก)
สมมติว่าคุณต้องการที่จะจับstdout
แต่ออกไปstdin
และstderr
อยู่คนเดียว อีกครั้งง่าย: เพียงแค่โทรproc.stdout.read()
(หรือเทียบเท่า) จนกว่าจะไม่มีเอาต์พุตอีกต่อไป เนื่องจากproc.stdout()
เป็นสตรีม Python I / O ปกติคุณสามารถใช้โครงสร้างปกติทั้งหมดบนมันได้เช่น:
for line in proc.stdout:
หรืออีกครั้งคุณสามารถใช้proc.communicate()
ซึ่งก็ทำread()
เพื่อคุณ
หากคุณต้องการที่จะจับเท่านั้นก็ทำงานเช่นเดียวกับกับstderr
stdout
มีอีกหนึ่งเคล็ดลับก่อนที่สิ่งต่าง ๆ จะยากขึ้น สมมติว่าคุณต้องการที่จะจับstdout
และยังจับstderr
แต่ในไปป์เดียวกับ stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
ในกรณีนี้subprocess
"กลโกง"! มันจะต้องทำสิ่งนี้ดังนั้นจึงไม่ใช่การโกงจริง ๆ : มันเริ่มต้นกระบวนการย่อยด้วย stdout และ stderr ที่นำไปสู่ (descriptor ไปป์เดียว) ที่ป้อนกลับไปสู่กระบวนการหลัก (Python) ในด้านผู้ปกครองนั้นมีเพียงตัวบ่งชี้ไพพ์ตัวเดียวสำหรับการอ่านเอาต์พุตอีกครั้ง เอาต์พุต "stderr" ทั้งหมดจะปรากฏขึ้นproc.stdout
และถ้าคุณเรียกproc.communicate()
ผลลัพธ์ stderr (ค่าที่สองใน tuple) จะNone
ไม่ใช่สตริง
กรณียาก: สองหรือมากกว่าท่อ
ปัญหาทั้งหมดเกิดขึ้นเมื่อคุณต้องการใช้อย่างน้อยสองท่อ ในความเป็นจริงsubprocess
รหัสตัวเองมีบิตนี้:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
แต่อนิจจาที่นี่เราทำอย่างน้อยสองและอาจจะสามท่อที่แตกต่างกันดังนั้นcount(None)
ผลตอบแทนทั้ง 1 หรือ 0 เราต้องทำสิ่งที่ยาก
บน Windows สิ่งนี้ใช้threading.Thread
เพื่อรวบรวมผลลัพธ์สำหรับself.stdout
และself.stderr
และมีเธรดหลักส่งself.stdin
ข้อมูลอินพุต (และจากนั้นปิดไพพ์)
บน POSIX สิ่งนี้ใช้poll
ถ้ามีมิฉะนั้นselect
จะสะสมเอาท์พุทและส่งมอบอินพุต stdin ทั้งหมดนี้ทำงานในกระบวนการ / เธรดหลัก (เดี่ยว)
จำเป็นต้องมีเธรดหรือแบบสำรวจ / เลือกที่นี่เพื่อหลีกเลี่ยงการหยุดชะงัก ตัวอย่างเช่นสมมติว่าเราได้เปลี่ยนเส้นทางทั้งสามสตรีมเป็นสามท่อแยก สมมติว่ามีข้อ จำกัด เล็กน้อยเกี่ยวกับจำนวนข้อมูลที่สามารถยัดลงในไพพ์ก่อนที่กระบวนการเขียนจะหยุดชั่วคราวรอกระบวนการอ่านเพื่อ "ล้าง" ไพพ์จากปลายอีกด้าน มาตั้งค่าขีด จำกัด เล็ก ๆ นี้ไว้ที่ไบต์เดียวเพื่อเป็นภาพประกอบ (นี่คือความจริงแล้วสิ่งต่าง ๆ ทำงานอย่างไรยกเว้นว่าขีด จำกัด นั้นใหญ่กว่าหนึ่งไบต์)
หากกระบวนการหลัก (Python) พยายามเขียนหลายไบต์เช่น, 'go\n'
ถึงproc.stdin
, ไบต์แรกเข้าและจากนั้นกระบวนการที่สองทำให้กระบวนการ Python หยุดชั่วคราว, รอให้กระบวนการย่อยอ่านไบต์แรก, ล้างไพพ์
ในขณะเดียวกันสมมติว่ากระบวนการย่อยตัดสินใจพิมพ์ "Hello! Don't Panic!" ที่เป็นมิตร ทักทาย การH
เข้าไปใน stdout pipe แต่e
สาเหตุที่ทำให้หยุดชั่วคราวรอให้พาเรนต์อ่านนั่นH
ทำให้ตะกอน stdout หายไป
ตอนนี้เรากำลังติดอยู่: กระบวนการ Python กำลังหลับรอที่จะเสร็จสิ้นการพูดว่า "ไป" และกระบวนการย่อยยังหลับรอที่จะเสร็จสิ้นการพูดว่า "Hello! Don't Panic!"
subprocess.Popen
รหัสหลีกเลี่ยงปัญหานี้กับเกลียวหรือเลือก / การสำรวจความคิดเห็น เมื่อไบต์สามารถผ่านไปป์พวกเขาไป เมื่อไม่สามารถทำได้มีเพียงเธรด (ไม่ใช่กระบวนการทั้งหมด) ที่ต้องพัก - หรือในกรณีของการเลือก / การสำรวจโพรเซสของ Python จะรอพร้อมกันสำหรับ "สามารถเขียน" หรือ "data available", เขียนไปยัง stdin ของกระบวนการ เมื่อมีที่ว่างเท่านั้นและอ่าน stdout และ / หรือ stderr เฉพาะเมื่อข้อมูลพร้อม proc.communicate()
รหัส (ที่จริง_communicate
ที่กรณีที่มีขนดกได้รับการจัดการ) ผลตอบแทนเมื่อมีข้อมูล stdin ทั้งหมด (ถ้ามี) ได้รับการส่งและ stdout และ / หรือ stderr ข้อมูลทั้งหมดได้รับการสะสม
หากคุณต้องการอ่านทั้งสองstdout
และstderr
ในสองท่อที่แตกต่างกัน (โดยไม่คำนึงถึงการstdin
เปลี่ยนเส้นทางใด ๆ) คุณจะต้องหลีกเลี่ยงการหยุดชะงักด้วย สถานการณ์การหยุดชะงักที่นี่แตกต่างกัน - มันเกิดขึ้นเมื่อหน่วยประมวลผลย่อยเขียนอะไรบางอย่างนานstderr
ในขณะที่คุณกำลังดึงข้อมูลstdout
หรือในทางกลับกัน - แต่มันยังอยู่ที่นั่น
ตัวอย่าง
ฉันสัญญาว่าจะแสดงให้เห็นว่ายกเลิกการเปลี่ยนเส้นทาง, Python subprocess
ES เขียนไป stdout sys.stdout
พื้นฐานไม่ ดังนั้นนี่คือรหัสบางส่วน:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
เมื่อทำงาน:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
หมายเหตุว่ากิจวัตรประจำวันแรกจะล้มเหลวถ้าคุณเพิ่มstdout=sys.stdout
เป็นวัตถุที่ไม่เคยมีใครStringIO
fileno
ที่สองจะละเว้นhello
ถ้าคุณเพิ่มstdout=sys.stdout
ตั้งแต่ได้รับการเปลี่ยนเส้นทางไปยังsys.stdout
os.devnull
(หากคุณเปลี่ยนเส้นทาง file-descriptor-1 ของ Python ระบบย่อยจะติดตามการเปลี่ยนเส้นทางนั้นการopen(os.devnull, 'w')
โทรจะสร้างสตรีมที่มีfileno()
ค่ามากกว่า 2)
Popen.poll
ในขณะที่ก่อนหน้านี้คำถามกองมากเกิน