Làm cách nào để tôi chuyển một chuỗi vào quy trình con.Popen (sử dụng đối số stdin)?


280

Nếu tôi làm như sau:

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

Tôi có:

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'

Rõ ràng là một đối tượng cStringIO.StringIO không quấy đủ gần với một con vịt tập tin để phù hợp với quy trình con.Popen. Làm thế nào để tôi làm việc xung quanh này?


3
Thay vì tranh luận câu trả lời của tôi với việc bị xóa, tôi đang thêm nó dưới dạng một bình luận ... Đọc khuyến nghị: Mô-đun Python của Tuần của Doug Hellmann đăng bài về quy trình con .
Daryl Spitzer

3
bài đăng trên blog chứa nhiều lỗi, ví dụ, ví dụ mã đầu tiên:call(['ls', '-1'], shell=True) không chính xác. Thay vào đó, tôi khuyên bạn nên đọc các câu hỏi phổ biến từ mô tả thẻ của quy trình con . Cụ thể, tại sao sub process.Popen không hoạt động khi args là chuỗi? giải thích tại sao call(['ls', '-1'], shell=True)sai Tôi nhớ để lại bình luận dưới bài viết trên blog nhưng tôi không thấy chúng bây giờ vì một số lý do.
jfs

Để biết thông tin mới hơn, subprocess.runhãy xem stackoverflow.com/questions/48752152/ Kẻ
Boris

Câu trả lời:


326

Popen.communicate() tài liệu:

Lưu ý rằng nếu bạn muốn gửi dữ liệu đến stdin của tiến trình, bạn cần tạo đối tượng Popen với stdin = PIPE. Tương tự, để có được bất cứ thứ gì khác ngoài Không có trong bộ kết quả, bạn cần cung cấp stdout = PIPE và / hoặc stderr = PIPE.

Thay thế os.popen *

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

Cảnh báo Sử dụng giao tiếp () thay vì stdin.write (), stdout.read () hoặc stderr.read () để tránh bế tắc do bất kỳ bộ đệm ống hệ điều hành nào khác lấp đầy và chặn quá trình con.

Vì vậy, ví dụ của bạn có thể được viết như sau:

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
# ->

Trên phiên bản Python 3 hiện tại, bạn có thể sử dụng subprocess.run, để chuyển đầu vào dưới dạng chuỗi sang lệnh bên ngoài và nhận trạng thái thoát của nó và đầu ra của chuỗi dưới dạng chuỗi trở lại trong một cuộc gọi:

#!/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
Tôi đã bỏ lỡ cảnh báo đó. Tôi vui vì tôi đã hỏi (mặc dù tôi nghĩ rằng tôi đã có câu trả lời).
Daryl Spitzer

11
Đây KHÔNG phải là một giải pháp tốt. Cụ thể, bạn không thể xử lý đầu ra p.stdout.readline một cách không đồng bộ nếu bạn làm điều này vì bạn phải đợi toàn bộ thiết bị xuất chuẩn xuất hiện. Đó cũng là bộ nhớ không hiệu quả.
OTZ

7
@OTZ Giải pháp nào tốt hơn?
Nick T

11
@Nick T: " tốt hơn " tùy thuộc vào ngữ cảnh. Định luật của Newton rất tốt cho miền mà chúng được áp dụng nhưng bạn cần có tính tương đối đặc biệt để thiết kế GPS. Xem phần Không chặn đọc trên một quy trình con.PIPE trong python .
jfs

9
Nhưng lưu ý LƯU Ý để liên lạc : "không sử dụng phương pháp này nếu kích thước dữ liệu lớn hoặc không giới hạn"
Owen

44

Tôi đã tìm ra cách giải quyết này:

>>> 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()

Có một cái tốt hơn?


25
@Moe: stdin.write()sử dụng không được khuyến khích, p.communicate()nên được sử dụng. Xem câu trả lời của tôi.
jfs

11
Theo tài liệu quy trình con: Cảnh báo - Sử dụng giao tiếp () thay vì .stdin.write, .stdout.read hoặc .stderr.read để tránh bế tắc do bất kỳ bộ đệm ống hệ điều hành nào khác lấp đầy và chặn quá trình con.
Jason Mock

1
Tôi nghĩ rằng đây là cách tốt để làm điều đó nếu bạn tự tin rằng thiết bị xuất chuẩn / lỗi của bạn sẽ không bao giờ bị lấp đầy (ví dụ: nó sẽ chuyển đến một tệp hoặc một luồng khác đang ăn nó) và bạn có một lượng dữ liệu không giới hạn để được gửi đến stdin.
Lucretiel

1
Cụ thể, thực hiện theo cách này vẫn đảm bảo rằng stdin được đóng, do đó, nếu các quy trình con là tiêu thụ đầu vào mãi mãi, thì communicatesẽ đóng ống và cho phép quá trình kết thúc một cách duyên dáng.
Lucretiel

@Lucretiel, nếu quá trình tiêu thụ stdin mãi mãi, thì có lẽ nó vẫn có thể viết stdout mãi mãi, vì vậy chúng ta cần các kỹ thuật hoàn toàn khác nhau (không thể read()từ đó, communicate()thậm chí không có đối số).
Charles Duffy

25

Tôi hơi ngạc nhiên khi không ai đề xuất việc tạo một đường ống, theo ý kiến ​​của tôi là cách đơn giản nhất để truyền một chuỗi cho stdin của một quy trình con:

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

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

2
Cả tài liệu ossubprocesstài liệu đều đồng ý rằng bạn nên thích cái sau hơn cái trước. Đây là một giải pháp kế thừa có sự thay thế tiêu chuẩn (hơi ít súc tích); câu trả lời được chấp nhận trích dẫn các tài liệu thích hợp.
tripleee

1
Tôi không chắc đó là chính xác, tripleee. Tài liệu được trích dẫn nói rằng tại sao khó sử dụng các đường ống được tạo ra bởi quy trình, nhưng trong giải pháp này, nó tạo ra một đường ống và chuyển nó vào. Tôi tin rằng nó tránh được các vấn đề bế tắc tiềm ẩn trong việc quản lý các đường ống sau khi quá trình đã bắt đầu.
Graham Christensen

os.popen không được ủng hộ trong quy trình con
hd1

2
-1: nó dẫn đến bế tắc, nó có thể mất dữ liệu. Chức năng này đã được cung cấp bởi mô-đun quy trình con. Sử dụng nó thay vì thực hiện nó kém (cố gắng viết một giá trị lớn hơn bộ đệm ống hệ điều hành)
jfs

Bạn xứng đáng là người đàn ông tốt nhất, cảm ơn bạn vì giải pháp đơn giản và thông minh nhất
Felipe Buccioni

21

Có một giải pháp tuyệt vời nếu bạn đang sử dụng Python 3.4 trở lên. Sử dụng inputđối số thay vì stdinđối số chấp nhận đối số byte:

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

Điều này làm việc cho check_outputrun, nhưng không callhoặc check_callvì một số lý do.


5
@vidstige Bạn nói đúng, thật lạ. Tôi sẽ xem xét việc gửi nó như là một lỗi Python, tôi không thấy bất kỳ lý do chính đáng nào tại sao check_outputnên có một inputđối số, nhưng không call.
Flimm

2
Đây là câu trả lời tốt nhất cho Python 3.4+ (sử dụng nó trong Python 3.6). Nó thực sự không hoạt động với check_callnhưng nó hoạt động cho run. Nó cũng hoạt động với input = string miễn là bạn chuyển một đối số mã hóa theo tài liệu.
Nikolaos Georgiou

13

Tôi đang sử dụng python3 và phát hiện ra rằng bạn cần mã hóa chuỗi của mình trước khi bạn có thể chuyển nó vào 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ạn đặc biệt không cần mã hóa đầu vào, nó chỉ muốn một đối tượng giống như byte (ví dụ b'something'). Nó sẽ trả về err và out như byte. Nếu bạn muốn tránh điều này, bạn có thể vượt qua universal_newlines=Trueđể Popen. Sau đó, nó sẽ chấp nhận đầu vào là str và sẽ trả về err / out như str.
Sáu

2
Nhưng hãy cẩn thận, universal_newlines=Truecũng sẽ chuyển đổi các dòng mới của bạn để phù hợp với hệ thống của bạn
Nacht - Tái lập Monica

1
Nếu bạn đang sử dụng Python 3, hãy xem câu trả lời của tôi để có giải pháp thuận tiện hơn nữa.
Flimm

12

Rõ ràng một đối tượng cStringIO.StringIO không quấy đủ gần với một con vịt tập tin để phù hợp với quy trình con.Popen

Tôi không sợ. Đường ống là một khái niệm hệ điều hành cấp thấp, vì vậy nó hoàn toàn yêu cầu một đối tượng tệp được đại diện bởi một bộ mô tả tệp cấp độ hệ điều hành. Cách giải quyết của bạn là đúng.


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.SpooledT tạm thời .__ doc__ nói: Trình bao bọc tệp tạm thời, chuyên biệt để chuyển từ StringIO sang tệp thực khi vượt quá kích thước nhất định hoặc khi cần fileno.
Doug F

5

Coi chừng điều đó Popen.communicate(input=s)có thể gây rắc rối cho bạn nếu squá lớn, vì rõ ràng quy trình cha mẹ sẽ đệm nó trước khi hủy bỏ quy trình con, nghĩa là nó cần bộ nhớ được sử dụng "gấp đôi" tại thời điểm đó (ít nhất là theo giải thích "dưới mui xe" và tài liệu liên kết được tìm thấy ở đây ). Trong trường hợp cụ thể của tôi, slà một trình tạo đầu tiên được mở rộng hoàn toàn và chỉ sau đó được viết cho stdinnên quá trình cha mẹ là rất lớn ngay trước khi đứa trẻ được sinh ra, và không còn bộ nhớ nào để rẽ nhánh:

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
Bởi vì shell=Truenó thường được sử dụng mà không có lý do chính đáng và đây là một câu hỏi phổ biến, tôi xin chỉ ra rằng có rất nhiều tình huống Popen(['cmd', 'with', 'args'])quyết định tốt hơn Popen('cmd with args', shell=True)và có vỏ phá vỡ lệnh và lập luận thành mã thông báo, nhưng không cung cấp bất cứ điều gì hữu ích, trong khi thêm một lượng phức tạp đáng kể và do đó cũng tấn công bề mặt.
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

Trên Python 3.7+ làm điều này:

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

và có lẽ bạn sẽ muốn thêm capture_output=Trueđể có được đầu ra của việc chạy lệnh dưới dạng một chuỗi.

Trên các phiên bản cũ hơn của Python, thay thế text=Truebằng universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.