(stdin引数を使用して)subprocess.Popenに文字列を渡すにはどうすればよいですか?


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
これが削除されたことで私の回答に異議を唱える代わりに、コメントとして追加します...推奨読書:サブプロセスに関するDoug HellmannのPython Module of the Weekブログの投稿
ダリルSpitzer 2013年

3
ブログの投稿に複数のエラーが含まれている(例:最初のコード例:call(['ls', '-1'], shell=True) が間違っている)。代わりに、サブプロセスのタグの説明から一般的な質問を読むことをお勧めします。特に、argsがシーケンスの場合なぜsubprocess.Popenが機能しないのですか?なぜcall(['ls', '-1'], shell=True)間違っているのかを説明します。ブログの投稿の下にコメントを残したことを覚えていますが、なぜか今は表示されません。
jfs 2016年

新しいバージョンについては、stackoverflow.comsubprocess.run
ボリス

回答:


326

Popen.communicate() ドキュメンテーション:

プロセスのstdinにデータを送信したい場合、stdin = PIPEでPopenオブジェクトを作成する必要があることに注意してください。同様に、結果タプルでNone以外のものを取得するには、stdout = PIPEおよび/またはstderr = PIPEも指定する必要があります。

os.popen *の置き換え

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

警告他のOSパイプバッファーがいっぱいになり子プロセスがブロックされることによるデッドロックを回避するには、stdin.write()、stdout.read()、またはstderr.read()ではなく、communication()を使用します。

だからあなたの例は次のように書くことができます:

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、入力を文字列として外部コマンドに渡し、その終了ステータスを取得し、その出力を1回の呼び出しで文字列として返すことができます。

#!/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
その警告を見逃した。質問してよかった(答えはあると思っていても)。
ダリルスピッツァー

11
これは良い解決策ではありません。特に、stdout全体が到着するまで待機する必要があるため、これを行うとp.stdout.readline出力を非同期に処理できません。また、メモリ効率が悪いです。
OTZ 2010

7
@OTZより良い解決策は何ですか?
Nick T

11
@Nick T:「より良い」はコンテキストに依存します。ニュートンの法則は、適用されるドメインには適していますが、GPSを設計するには特別な相対論が必要です。pythonの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、.stderr.readではなく、communicate()を使用して、他のOSパイプバッファーがいっぱいになって子プロセスをブロックすることによるデッドロックを回避します。
ジェイソンモック2010

1
stdout / errがいっぱいにならないことを確信していて(たとえば、ファイルに送られるか、別のスレッドがそれを食べている)、無制限の量のデータがある場合、これは良い方法だと思いますstdinに送信されます。
Lucretiel、2016年

1
特に、この方法でこれを行うと、stdinが確実に閉じられるため、サブプロセスが入力を永久に消費するサブプロセスである場合communicate、パイプは閉じられ、プロセスは正常に終了します。
Lucretiel、2016年

@Lucretiel、(できない私たちは、すべてのラウンドの完全に異なる技術を必要とすると思いますので、その後、おそらくそれは永遠にまだ書き込み標準出力をすることができ、プロセスの消費は永遠に標準入力かのread()ように、それからcommunicate()も、引数なしで行います)。
Charles Duffy

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 2016年

1
それが正しいかわかりません。引用されたドキュメントは、プロセスによって作成されたパイプを使用するのが難しい理由を述べていますが、このソリューションでは、パイプを作成して渡します。プロセスがすでに開始した後にパイプを管理することの潜在的なデッドロックの問題を回避すると思います。
グラハムクリステンセン

os.popenはサブプロセスのために廃止されました
hd1

2
-1:デッドロックにつながり、データが失われる可能性があります。この機能は、サブプロセスモジュールによってすでに提供されています。不十分に再実装する代わりにそれを使用してください(OSパイプバッファーより大きい値を書き込もうとしてください)
jfs

あなたは最高の善人に値します。最も単純で巧妙な解決策をありがとう
フェリペブッチョーニ

21

Python 3.4以降を使用している場合は、美しい解決策があります。バイト引数を受け入れるinput引数の代わりに引数を使用しますstdin

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

これは、のために働くcheck_outputrunではなく、callまたはcheck_call何らかの理由で。


5
@vidstigeそうだね、変だ。私はこれをPythonのバグとして報告することを検討します。なぜ引数check_outputを持たなければならないのか、理由はわかりinputませんcall
2017年

2
これは、Python 3.4以降(Python 3.6で使用)の最良の回答です。実際には機能しませんcheck_callが、で機能しrunます。ドキュメントに従ってエンコーディング引数も渡す限り、input = 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もバイトとして返します。これを避けたい場合は、に渡しuniversal_newlines=TrueてくださいPopen。次に、入力をstrとして受け入れ、err / outもstrとして返します。

2
ただし、注意してくださいuniversal_newlines=True。改行もシステムに合わせて変換されます
Nacht-Reinstate Monica '25

1
Python 3を使用している場合、さらに便利な解決策については私の回答を参照しください。
Flimm

12

どうやらcStringIO.StringIOオブジェクトは、サブプロセスに適したファイルダックに十分近づきません。

そうではないと思います。パイプは低レベルのOSの概念であるため、OSレベルのファイル記述子で表されるファイルオブジェクトが絶対に必要です。あなたの回避策は正しいものです。


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__のコメント:一時的なファイルラッパー。特定のサイズを超えた場合、またはfilenoが必要な場合に、StringIOから実際のファイルに切り替えるように特化しています。
Doug F

5

それは注意してくださいPopen.communicate(input=s)場合は、あなたにトラブルを与える可能性がs明らかに親プロセスがそれをバッファリングするため、大きすぎる前に子サブプロセスをフォークする入れます。つまり、その時点で「少なくとも2倍」の使用済みメモリが必要です(少なくとも「内部」の説明によると)ここにあるリンクされたドキュメント)。私の特定のケースで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 2014年

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=Trueuniversal_newlines=True

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.