ฉันจะส่งผ่านสตริงไปยัง subprocess.Popen (โดยใช้อาร์กิวเมนต์ stdin) ได้อย่างไร


280

ถ้าฉันทำต่อไปนี้:

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0]

ฉันเข้าใจ:

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 533, in __init__
    (p2cread, p2cwrite,
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 830, in _get_handles
    p2cread = stdin.fileno()
AttributeError: 'cStringIO.StringI' object has no attribute 'fileno'

เห็นได้ชัดว่าวัตถุ cStringIO.StringIO นั้นไม่ได้อยู่ใกล้กับเป็ดพอที่จะให้เหมาะสมกับ subprocess.Popen ฉันจะแก้ไขสิ่งนี้ได้อย่างไร


3
แทนที่จะโต้แย้งคำตอบของฉันมีความเป็นอยู่นี้ลบฉันเพิ่มนั้นเป็นความคิดเห็น ... อ่านที่แนะนำ: ดั๊ก Hellmann ของงูหลามโมดูลของสัปดาห์บล็อกโพสต์ในกระบวนการย่อย
Daryl Spitzer

3
โพสต์บล็อกมีข้อผิดพลาดหลายอย่างเช่นตัวอย่างรหัสแรก:call(['ls', '-1'], shell=True) ไม่ถูกต้อง ฉันแนะนำให้อ่านคำถามทั่วไปจากคำอธิบายแท็กของกระบวนการย่อยแทน โดยเฉพาะทำไม subprocess.Popen ไม่ทำงานเมื่อ args เป็นลำดับ? อธิบายว่าทำไมcall(['ls', '-1'], shell=True)ผิด ฉันจำการแสดงความคิดเห็นใต้โพสต์บล็อก แต่ฉันไม่เห็นพวกเขาตอนนี้ด้วยเหตุผลบางอย่าง
jfs

สำหรับรุ่นใหม่subprocess.runดูstackoverflow.com/questions/48752152/…
Boris

คำตอบ:


326

Popen.communicate() เอกสาร:

โปรดทราบว่าถ้าคุณต้องการส่งข้อมูลไปยัง stdin ของกระบวนการคุณต้องสร้างวัตถุ Popen ด้วย stdin = PIPE ในการรับสิ่งอื่นที่ไม่ใช่ None ในผลลัพธ์ tuple คุณต้องให้ stdout = PIPE และ / หรือ stderr = PIPE ด้วย

แทนที่ os.popen *

    pipe = os.popen(cmd, 'w', bufsize)
    # ==>
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

คำเตือนใช้การสื่อสาร () แทน stdin.write (), stdout.read () หรือ stderr.read () เพื่อหลีกเลี่ยงการหยุดชะงักเนื่องจากบัฟเฟอร์ท่อระบบปฏิบัติการอื่นใดที่เติมและบล็อกกระบวนการลูก

ดังนั้นตัวอย่างของคุณสามารถเขียนได้ดังนี้:

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout.decode())
# -> four
# -> five
# ->

ในเวอร์ชัน Python 3 ปัจจุบันคุณสามารถใช้subprocess.runเพื่อส่งผ่านอินพุตเป็นสตริงไปยังคำสั่งภายนอกและรับสถานะทางออกและเอาต์พุตเป็นสตริงกลับในการโทรครั้งเดียว:

#!/usr/bin/env python3
from subprocess import run, PIPE

p = run(['grep', 'f'], stdout=PIPE,
        input='one\ntwo\nthree\nfour\nfive\nsix\n', encoding='ascii')
print(p.returncode)
# -> 0
print(p.stdout)
# -> four
# -> five
# -> 

3
ฉันพลาดคำเตือนนั้น ฉันดีใจที่ฉันถาม (แม้ว่าฉันคิดว่าฉันมีคำตอบ)
Daryl Spitzer

11
นี่ไม่ใช่ทางออกที่ดี โดยเฉพาะอย่างยิ่งคุณไม่สามารถประมวลผลเอาต์พุต p.stdout.readline แบบอะซิงโครนัสหากคุณทำเช่นนี้เนื่องจากคุณต้องรอให้ stdout ทั้งหมดมาถึง นอกจากนี้ยังเป็นหน่วยความจำที่ไม่มีประสิทธิภาพ
OTZ

7
@OTZ ทางออกที่ดีกว่าคืออะไร
Nick T

11
@Nick T: " ดีกว่า " ขึ้นอยู่กับบริบท กฎของนิวตันนั้นดีสำหรับโดเมนที่ใช้ได้ แต่คุณต้องมีความสัมพันธ์พิเศษในการออกแบบ GPS ดูไม่ปิดกั้นการอ่านบน subprocess.PIPE ในหลาม
jfs

9
แต่โปรดทราบหมายเหตุสำหรับการสื่อสาร : "อย่าใช้วิธีนี้หากขนาดข้อมูลมีขนาดใหญ่หรือไม่ จำกัด "
Owen

44

ฉันพบวิธีแก้ปัญหานี้:

>>> p = subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
>>> p.stdin.write(b'one\ntwo\nthree\nfour\nfive\nsix\n') #expects a bytes type object
>>> p.communicate()[0]
'four\nfive\n'
>>> p.stdin.close()

มีดีกว่าไหม


25
@Moe: stdin.write()การใช้งานหมดกำลังใจp.communicate()ควรใช้ ดูคำตอบของฉัน
jfs

11
ตามเอกสารประกอบของกระบวนการย่อย: คำเตือน - ใช้การสื่อสาร () แทน. stdin.write, .stdout.read หรือ. strr.read เพื่อหลีกเลี่ยงการหยุดชะงักเนื่องจากบัฟเฟอร์ไปป์ OS อื่น ๆ ที่บรรจุและบล็อกกระบวนการลูก
Jason จำลอง

1
ฉันคิดว่านี่เป็นวิธีที่ดีในการทำหากคุณมั่นใจว่า stdout / err ของคุณจะไม่เติมเต็ม (เช่นจะไปที่ไฟล์หรือเธรดอื่นกำลังกินอยู่) และคุณมีจำนวนข้อมูลที่ไม่ จำกัด จะถูกส่งไปยัง stdin
Lucretiel

1
โดยเฉพาะอย่างยิ่งการทำด้วยวิธีนี้ยังช่วยให้มั่นใจได้ว่า stdin ถูกปิดดังนั้นหากกระบวนการย่อยนั้นใช้การป้อนข้อมูลตลอดไปกระบวนการcommunicateจะปิดไปป์และอนุญาตให้กระบวนการสิ้นสุดอย่างสง่างาม
Lucretiel

@Lucretiel ถ้ากินกระบวนการ stdin ตลอดไปแล้วสันนิษฐานว่ามันยังสามารถเขียน stdout ตลอดไปดังนั้นเราจะต้องใช้เทคนิคที่แตกต่างกันอย่างสมบูรณ์ทุกรอบ (ไม่สามารถread()จากมันเป็นcommunicate()ไม่ได้แม้จะมีข้อโต้แย้งไม่ได้)
ชาร์ลส์ดัฟฟี่

25

ฉันประหลาดใจเล็กน้อยที่ไม่มีใครแนะนำให้สร้างไพพ์ซึ่งฉันคิดว่าเป็นวิธีที่ง่ายที่สุดในการส่งสตริงไปยัง stdin ของกระบวนการย่อย:

read, write = os.pipe()
os.write(write, "stdin input here")
os.close(write)

subprocess.check_call(['your-command'], stdin=read)

2
osและsubprocessเอกสารทั้งสองยอมรับว่าคุณควรจะเลือกอย่างหลังมากกว่าอดีต นี่คือโซลูชันแบบดั้งเดิมที่มีการแทนที่มาตรฐาน (น้อยลงเล็กน้อยกระชับ) คำตอบที่ยอมรับหมายถึงเอกสารที่เกี่ยวข้อง
tripleee

1
ฉันไม่แน่ใจว่าถูกต้อง tripleee เอกสารที่ยกมาบอกว่าทำไมมันยากที่จะใช้ท่อที่สร้างขึ้นโดยกระบวนการ แต่ในการแก้ปัญหานี้มันจะสร้างท่อและผ่านมันมาฉันเชื่อว่ามันหลีกเลี่ยงปัญหาการหยุดชะงักที่อาจเกิดขึ้นจากการจัดการท่อหลังจากกระบวนการได้เริ่มขึ้นแล้ว
Graham Christensen

os.popen ถูกคัดค้าน subprocess
hd1

2
-1: มันนำไปสู่การหยุดชะงักมันอาจทำให้ข้อมูลหลวม ฟังก์ชั่นนี้มีให้แล้วโดยโมดูลย่อย ใช้แทนการนำมาใช้ซ้ำอย่างไม่ดี (พยายามเขียนค่าที่มีขนาดใหญ่กว่าบัฟเฟอร์ท่อของระบบปฏิบัติการ)
jfs

คุณสมควรได้รับคนดีที่สุดขอบคุณสำหรับการแก้ปัญหาที่ง่ายและฉลาดที่สุด
Felipe Buccioni

21

มีวิธีแก้ปัญหาที่สวยงามถ้าคุณใช้ Python 3.4 หรือดีกว่า ใช้inputอาร์กิวเมนต์แทนstdinอาร์กิวเมนต์ซึ่งยอมรับอาร์กิวเมนต์ไบต์:

output = subprocess.check_output(
    ["sed", "s/foo/bar/"],
    input=b"foo",
)

มันใช้งานได้check_outputและrunแต่ไม่ใช่callหรือcheck_callด้วยเหตุผลบางอย่าง


5
@ vidstige คุณพูดถูกแปลก ฉันจะพิจารณายื่นนี้เป็นข้อผิดพลาดหลามผมไม่เห็นเหตุผลที่ดีใด ๆ ในเหตุผลที่check_outputควรจะมีinputการโต้แย้ง callแต่ไม่
Flimm

2
นี่คือคำตอบที่ดีที่สุดสำหรับ Python 3.4+ (ใช้ใน Python 3.6) แน่นอนมันไม่ได้ทำงานกับแต่การทำงานสำหรับcheck_call runนอกจากนี้ยังทำงานร่วมกับอินพุต = string ได้ตราบใดที่คุณผ่านอาร์กิวเมนต์การเข้ารหัสตามเอกสาร
Nikolaos Georgiou

13

ฉันใช้ python3 และพบว่าคุณต้องเข้ารหัสสตริงของคุณก่อนที่จะส่งผ่านไปยัง stdin:

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
out, err = p.communicate(input='one\ntwo\nthree\nfour\nfive\nsix\n'.encode())
print(out)

5
คุณไม่จำเป็นต้องเข้ารหัสอินพุตโดยเฉพาะมันแค่ต้องการวัตถุที่มีลักษณะคล้ายไบต์ (เช่นb'something') มันจะส่งคืนค่า err และ out ตามจำนวนไบต์เช่นกัน หากคุณต้องการหลีกเลี่ยงปัญหานี้คุณสามารถผ่านuniversal_newlines=TrueไปPopenได้ จากนั้นจะรับอินพุตเป็น str และจะส่งคืน err / out เป็น str ด้วย
หก

2
แต่ระวังuniversal_newlines=Trueจะแปลงบรรทัดใหม่ของคุณให้ตรงกับระบบของคุณ
Nacht - Reinstate Monica

1
หากคุณใช้ Python 3 ดูคำตอบของฉันเพื่อหาวิธีแก้ปัญหาที่สะดวกยิ่งขึ้น
Flimm

12

เห็นได้ชัดว่าวัตถุ cStringIO.StringIO นั้นไม่ได้อยู่ใกล้กับเป็ดพอที่จะให้เหมาะสมกับกระบวนการย่อยเปิด

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


7
from subprocess import Popen, PIPE
from tempfile import SpooledTemporaryFile as tempfile
f = tempfile()
f.write('one\ntwo\nthree\nfour\nfive\nsix\n')
f.seek(0)
print Popen(['/bin/grep','f'],stdout=PIPE,stdin=f).stdout.read()
f.close()

3
fyi, tempfile.SpooledTemporaryFile .__ doc__ พูดว่า: wrapper ไฟล์ชั่วคราวโดยเฉพาะเพื่อเปลี่ยนจาก StringIO เป็นไฟล์จริงเมื่อมันเกินขนาดที่กำหนดหรือเมื่อจำเป็นต้องใช้ fileno
Doug F

5

ระวังที่Popen.communicate(input=s)อาจทำให้คุณมีปัญหาหากsมีขนาดใหญ่เกินไปเพราะเห็นได้ชัดว่ากระบวนการหลักจะบัฟเฟอร์มันก่อนฟอร์ก subprocess เด็กซึ่งหมายความว่าต้องมีหน่วยความจำที่ใช้ "สองเท่า" ที่จุดนั้น (อย่างน้อยตามคำอธิบาย "ภายใต้ประทุน") และเอกสารที่เชื่อมโยงพบได้ที่นี่ ) ในกรณีเฉพาะของฉันsเป็นเครื่องกำเนิดไฟฟ้าที่ถูกขยายอย่างเต็มที่ก่อนแล้วจึงเขียนถึงstdinกระบวนการหลักนั้นถูกต้องอย่างมากก่อนที่เด็กจะเกิดและไม่มีหน่วยความจำเหลืออยู่เพื่อแยกมัน:

File "/opt/local/stow/python-2.7.2/lib/python2.7/subprocess.py", line 1130, in _execute_child self.pid = os.fork() OSError: [Errno 12] Cannot allocate memory


5
"""
Ex: Dialog (2-way) with a Popen()
"""

p = subprocess.Popen('Your Command Here',
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
                 stdin=PIPE,
                 shell=True,
                 bufsize=0)
p.stdin.write('START\n')
out = p.stdout.readline()
while out:
  line = out
  line = line.rstrip("\n")

  if "WHATEVER1" in line:
      pr = 1
      p.stdin.write('DO 1\n')
      out = p.stdout.readline()
      continue

  if "WHATEVER2" in line:
      pr = 2
      p.stdin.write('DO 2\n')
      out = p.stdout.readline()
      continue
"""
..........
"""

out = p.stdout.readline()

p.wait()

4
เพราะshell=Trueโดยทั่วไปมักใช้กันโดยไม่มีเหตุผลและนี่เป็นคำถามที่ได้รับความนิยมให้ฉันชี้ให้เห็นว่ามีหลายสถานการณ์ที่Popen(['cmd', 'with', 'args'])ดีกว่าPopen('cmd with args', shell=True)และมีการแบ่งคำสั่งและข้อโต้แย้งเป็นโทเค็น แต่ไม่ได้ให้อะไรเลย มีประโยชน์ในขณะที่เพิ่มความซับซ้อนจำนวนมากและยังโจมตีพื้นผิว
tripleee

2
p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
p.stdin.write('one\n')
time.sleep(0.5)
p.stdin.write('two\n')
time.sleep(0.5)
p.stdin.write('three\n')
time.sleep(0.5)
testresult = p.communicate()[0]
time.sleep(0.5)
print(testresult)

1

บน Python 3.7+ ทำสิ่งนี้:

my_data = "whatever you want\nshould match this f"
subprocess.run(["grep", "f"], text=True, input=my_data)

และคุณอาจต้องการเพิ่ม capture_output=Trueเพื่อให้ได้ผลลัพธ์ของการรันคำสั่งเป็นสตริง

ใน Python เวอร์ชันเก่าให้แทนที่text=Trueด้วยuniversal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.