web-dev-qa-db-ja.com

リアルタイムサブプロセス。stdoutおよびPIPEを介したPopen

_subprocess.Popen_呼び出しからstdoutを取得しようとしていますが、次のようにすることで簡単にこれを実現できます。

_cmd = subprocess.Popen('ls -l', Shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
    print line
_

「リアルタイム」でstdoutを取得したいと思います。上記のメソッドでは、PIPEはすべてのstdoutを取得するのを待ってから、戻ります。

したがって、ロギングの目的では、これは私の要件を満たしていません(たとえば、発生中に何が起こっているかを「確認」します)。

実行中に行ごとにstdoutを取得する方法はありますか?または、これはsubprocessの制限ですか(PIPEが閉じるまで待機する必要があります)。

[〜#〜] edit [〜#〜]readlines()readline()に切り替えると、stdoutの最後の行しか取得されません(理想的ではありません):

_In [75]: cmd = Popen('ls -l', Shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....: 
t
o
t
a
l

1
0
4
_
18
alfredodeza

あなたの通訳はバッファリングしています。 printステートメントの後にsys.stdout.flush()への呼び出しを追加します。

19
Jeff

実際、実際の解決策は、サブプロセスのstdoutをプロセスのstdoutに直接リダイレクトすることです。

実際、あなたのソリューションでは、たとえば、stdoutのみを印刷でき、stderrは同時に印刷できません。

_import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()
_

communicate()は、サブプロセスが終了するまで呼び出しをブロックするためのものです。そうしないと、次の行に直接移動し、サブプロセスの前にプログラムが終了する可能性があります(ただし、stdoutへのリダイレクトは引き続き機能しますが、 pythonスクリプトが閉じた後でも、テストしました)。

そのようにして、たとえば、stdoutとstderrの両方を絶対リアルタイムでリダイレクトします。

たとえば、私の場合、このスクリプトでテストしました_slow_cmd_output.sh_:

_#!/bin/bash

for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done
_
13
Undo

「リアルタイム」で出力を取得するには、subprocessは他のプロセスのバッファリング戦略を打ち負かすことができないため、不適切です。そのため、このような「リアルタイム」の出力グラブが必要な場合は常に(スタックオーバーフローに関するよくある質問です!)、代わりに pexpect (Windows以外のすべての場所-Windowsの場合-)を使用することを常にお勧めします。 wexpect )。

11
Alex Martelli

出力を合体させているreadlines()を削除します。また、ほとんどのコマンドはパイプへの出力を内部的にバッファリングするため、ラインバッファリングを強制する必要があります。詳細については、以下を参照してください。 http://www.pixelbeat.org/programming/stdio_buffering/

3
pixelbeat

これは私が何日も答えを探していた質問なので、フォローしている人のためにここに残したいと思いました。 subprocessが他のプロセスのバッファリング戦略と戦うことができないのは事実ですが、別のPythonスクリプトをsubprocess.Popenで呼び出す場合は、バッファリングされていないPythonを起動します。

command = ["python", "-u", "python_file.py"]
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, ''):
    line = line.replace('\r', '').replace('\n', '')
    print line
    sys.stdout.flush()

また、仮引数bufsize=1universal_newlines=Trueが非表示のstdoutの公開に役立った場合もあります。

3
ochawkeye
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")
1
Roger Pate

すでに述べたように、問題は、プロセスに端末が接続されていない場合のstdioライブラリのprintflikeステートメントのバッファリングにあります。とにかく、Windowsプラットフォームでこれを回避する方法があります。他のプラットフォームでも同様の解決策があるかもしれません。

Windowsでは、プロセスの作成時に新しいコンソールを強制的に作成できます。良い点は、これを非表示のままにして、表示されないようにすることです(これは、サブプロセスモジュール内のShell = Trueによって実行されます)。

cmd = subprocess.Popen('ls -l', Shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True)
for line in cmd.stdout.readlines():
    print line

または

もう少し完全な解決策は、STARTUPINFO paramsを明示的に設定して、Shell = Trueが上記で行った新しい不要なcmd.exeシェルプロセスの起動を防ぐことです。

class PopenBackground(subprocess.Popen):
    def __init__(self, *args, **kwargs):

        si = kwargs.get('startupinfo', subprocess.STARTUPINFO())
        si.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        si.wShowWindow = _winapi.SW_HIDE

        kwargs['startupinfo'] = si
        kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE
        kwargs['bufsize'] = 1
        kwargs['universal_newlines'] = True

        super(PopenBackground, self).__init__(*args, **kwargs)

process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE)
    for line in cmd.stdout.readlines():
        print line
0
Chris

readlinesの呼び出しは、プロセスが終了するのを待っています。これをcmd.stdout.readline()(単数に注意)の周りのループに置き換えれば、すべてうまくいくはずです。

0
Jakob Borg