web-dev-qa-db-ja.com

subprocess.Popenを使用して複数のプロセスをパイプで接続するにはどうすればよいですか?

Python subprocess モジュールを使用して次のシェルコマンドを実行するにはどうすればよいですか?

echo "input data" | awk -f script.awk | sort > outfile.txt

入力データは文字列から取得されるため、実際にはechoは必要ありません。私はこれまでのところ、私もsortを介してパイプする方法を説明できますか?

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

[〜#〜] update [〜#〜]:以下の受け入れられた回答は実際に質問に答えていないが、S .Lottは正しいので、そもそもその問題を解決する必要はありません。

46
Tom

あなたは次のことで少し幸せになるでしょう。

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, Shell=True )
awk_sort.communicate( b"input data\n" )

作業の一部をシェルに委任します。 2つのプロセスをパイプラインで接続します。

'script.awk'をPythonに書き直して、awkとパイプラインを削除する方がずっと幸せです。

編集。 awkが役に立たないことを示唆する理由のいくつか。

[コメントで回答する理由は多すぎます。]

  1. Awkは重要な価値のないステップを追加しています。 Pythonが処理しないawkの処理についてユニークなものはありません。

  2. Awkからソートへのパイプライン処理により、大量のデータが処理されるため、経過処理時間が改善される場合があります。短いデータセットの場合、大きな利点はありません。 awk >file ; sort fileおよびawk | sortをすばやく測定すると、同時実行の助けがわかります。並べ替えでは、並べ替えは1回限りのフィルターではないため、ほとんど役立ちません。

  3. 「Python to sort」処理の単純さ(「Python to awk to sort」の代わり)は、ここで尋ねられる質問の正確な種類を防ぎます。

  4. Pythonはawkよりも冗長ですが、awkが初心者には不透明で、専門家でない人には混乱を招くような暗黙のルールがある場合にも明示的です。

  5. (シェルスクリプト自体と同様に)Awkは、さらに別のプログラミング言語を追加します。これらすべてを1つの言語(Python)で実行できる場合、シェルとawkプログラミングを排除すると2つのプログラミング言語が排除され、誰かがタスクの価値を生み出す部分に集中できるようになります。

結論:awkは大きな価値を追加できません。この場合、awkは正味コストです。この質問をするのに必要なほど複雑でした。 awkを削除すると、純益になります。

サイドバーパイプライン(a | b)の構築が非常に難しい理由。

シェルがa | bに直面したとき、次のことをしなければなりません。

  1. 元のシェルの子プロセスをフォークします。これは最終的にbになります。

  2. OSパイプを構築します。 (Python subprocess.PIPE)ではなく、共通バッファーを介して接続された2つの新しいファイル記述子を返すos.pipe()を呼び出します。この時点で、プロセスにはstdin、stdout、stderrがあります)その親と、「a's stdout」および「b's stdin」になるファイル。

  3. 子供をフォークします。子は、その標準出力を新しいaの標準出力に置き換えます。 aプロセスを実行します。

  4. Bの子がクローズすると、そのstdinが新しいbのstdinに置き換えられます。 bプロセスを実行します。

  5. Bの子は、aが完了するのを待ちます。

  6. 親はbが完了するのを待っています。

上記を再帰的に使用してa | b | cを生成できると思いますが、a | (b | c)のように長いパイプラインを暗黙的に括弧で囲む必要があります。

Pythonにはos.pipe()os.exec()os.fork()があり、sys.stdinsys.stdoutを置き換えることができるので、純粋なPythonで上記を行う方法実際、os.pipe()subprocess.Popenを使用して、いくつかのショートカットを作成できる場合があります。

ただし、その操作をシェルに委任する方が簡単です。

40
S.Lott
import subprocess

some_string = b'input_data'

sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                 stdin=subprocess.PIPE).communicate(some_string)
24
Cristian

シェルパイプラインをエミュレートするには:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', Shell=True)

シェルを呼び出さずに( 17.1.4.2。シェルパイプラインの置き換え を参照):

#!/usr/bin/env python
from subprocess import Popen, PIPE

a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
    with a.stdout, open("outfile.txt", "wb") as outfile:
        b = Popen(["b"], stdin=a.stdout, stdout=outfile)
    a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already

plumbum は、いくつかの構文シュガーを提供します。

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

アナログ:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

は:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
17
jfs

http://www.python.org/doc/2.5.2/lib/node535.html はこれをかなりよくカバーしていました。あなたが理解していなかったこの部分がありますか?

プログラムはかなり似ていますが、2番目のPopenにはファイルへのstdout =があり、その.communicate()の出力は必要ありません。

3
geocar

受け入れられた答えは、問題を回避することです。複数のプロセスの出力を連結するスニペットを次に示します。また、実行することで出力が正しいことを確認できるように、(同等の)同等のシェルコマンドも出力することに注意してください。

#!/usr/bin/env python3

from subprocess import Popen, PIPE

# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")

p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)

print("Output from last process : " + (p3.communicate()[0]).decode())

# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)
2
Omry Yadan

@Cristianの回答に触発されました。同じ問題に出会ったが、コマンドは異なる。そこで、テスト済みの例を紹介します。これは役立つと思われます。

grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()

これはテスト済みです。

何がされた

  • パイプからの標準入力で遅延grep実行を宣言しました。このコマンドは、パイプがpsのstdoutで満たされるときに、psコマンドの実行時に実行されます。
  • psコマンドで使用されるパイプに向けられたstdoutを使用して、プライマリコマンドgrepを呼び出しました。
  • Grepはパイプからstdoutを取得するために通信しました。

subprocessインターフェイスで優しくラップされた自然なパイプの概念なので、この方法が好きです。

2
I159

前の答えは重要な点を逃しました。 シェルパイプラインの置き換え は、geocarが指摘したように、基本的に正しいです。パイプの最後の要素でcommunicateを実行するには、almostで十分です。

残りの問題は、入力データをパイプラインに渡すことです。複数のサブプロセスでは、最後の要素の単純なcommunicate(input_data)は機能しません-永久にハングします。次のように、手動でパイプラインと子を作成する必要があります。

import os
import subprocess

input = """\
input data
more input
""" * 10

rd, wr = os.pipe()
if os.fork() != 0: # parent
    os.close(wr)
else:              # child
    os.close(rd)
    os.write(wr, input)
    os.close(wr)
    exit()

p_awk = subprocess.Popen(["awk", "{ print $2; }"],
                         stdin=rd,
                         stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"], 
                          stdin=p_awk.stdout,
                          stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())

これで、子はパイプを介して入力を提供し、親は予想どおりに動作するCommunicate()を呼び出します。このアプローチを使用すると、「作業の一部をシェルに委任する」ことなく、任意の長いパイプラインを作成できます。残念ながら subprocess documentation ではこれについて言及していません。

パイプなしで同じ効果を達成する方法があります:

from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)

stdin=tfp_awkを使用します。それは好みの好みの問題です。

上記は、信号処理が異なるため、bashパイプラインと100%同等ではありません。 sortの出力を切り捨てる別のパイプ要素を追加すると、これを確認できます。 head -n 10。上記のコードでは、sortは「壊れたパイプ」エラーメッセージをstderrに出力します。シェルで同じパイプラインを実行すると、このメッセージは表示されません。 (それが唯一の違いですが、stdoutの結果は同じです)。理由は、PythonのPopenSIGPIPEに対してSIG_IGNを設定しているのに対し、シェルはSIG_DFLでそれを残し、sortのシグナル処理が異なるためと思われますこれら2つの場合。

1
uncleremus

EDIT:pipesはWindowsで使用できますが、実際にはworkWindowsの場合。以下のコメントを参照してください。

Python標準ライブラリには、これを処理するためのpipesモジュールが含まれています。

https://docs.python.org/2/library/pipes.htmlhttps://docs.python.org/3.4/library/pipes.html

このモジュールがどのくらいの期間使用されているかはわかりませんが、このアプローチはsubprocessをいじくり回すよりもはるかに簡単なようです。

1
Kyle Strand

私にとって、以下のアプローチは最もきれいで読みやすいです

from subprocess import Popen, PIPE

def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
    with open(output_filename, 'wb') as out_f:
        p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
        p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
        p1.communicate(input=bytes(input_s))
        p1.wait()
        p2.stdin.close()
        p2.wait()

次のように呼び出すことができます:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')
0
mwag