web-dev-qa-db-ja.com

1つのsubprocess.Popenコマンドから複数のコマンドを同期的に実行する方法は?

同じサブプロセスコマンドを使用して、任意の数のコマンドを順番に実行することは可能ですか?

実行する前に、前のコマンドが完了するのを待つ各コマンドが必要です。また、すべてのコマンドを同じセッション/シェルで実行する必要があります。 Python 2.6、Python 3.5。ここでは例としてechoコマンドを使用しています)。

以下の非機能コードを参照してください:

import sys
import subprocess

cmds = ['echo start', 'echo mid', 'echo end']

p = subprocess.Popen(cmd=Tuple([item for item in cmds]),
                     stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, b''):
    sys.stdout.flush()
    print(">>> " + line.rstrip())

これが不可能な場合、同じセッション/シェル内でコマンドを同期シーケンスで実行するには、どのアプローチを採用すればよいですか?

15
fredrik

同じsession/Shellで多くのコマンドを次々に実行したい場合は、シェルを起動して、すべてのコマンドを1つずつ追加し、その後に改行を追加して閉じる必要があります。最後にパイプ。一部のコマンドが真のプロセスではなく、たとえばシェル環境を変更する可能性のあるシェルコマンドである場合は、理にかなっています。

WindowsでPython 2.7を使用した例:

encoding = 'latin1'
p = subprocess.Popen('cmd.exe', stdin=subprocess.PIPE,
             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for cmd in cmds:
    p.stdin.write(cmd + "\n")
p.stdin.close()
print p.stdout.read()

このコードをLinuxで実行するには、cmd.exe/bin/bashに置き換え、エンコーディングをutf8に変更する必要があります。

Python 3の場合、コマンドをエンコードし、おそらくそれらの出力をデコードし、printで括弧を使用する必要があります。

注意:これはほとんど出力しない場合にのみ機能します。 stdinパイプを閉じる前にパイプバッファーを満たすのに十分な出力がある場合、このコードはデッドロックします。より堅牢な方法は、その問題を回避するためにコマンドの出力を読み取る2番目のスレッドを用意することです。

4
Serge Ballesta

1つの可能な解決策は、同じシェルで実行されているように見えます。

subprocess.Popen('echo start;echo mid;echo end', Shell=True)

注-コマンドを文字列として渡す場合、シェルはTrueである必要があります注-これはLinuxでのみ機能するため、Windowsでも同様の方法を見つける必要がある場合があります。

それがお役に立てば幸いです。

python doc-

Shell = TrueのUnixでは、シェルのデフォルトは/ bin/shです。 argsが文字列の場合、文字列はシェルを介して実行するコマンドを指定します。つまり、文字列は、シェルプロンプトで入力したときとまったく同じ形式にする必要があります。

3
AlokThakur

これは、Serge Ballestaが投稿した回答に似ていますが、完全ではありません。結果を気にしない非同期実行に彼を使用してください。同期処理と結果収集にはmineを使用してください。彼の答えのように、ここではWindowsソリューションを示しています。WindowsではcmdではなくLinuxでbashプロセスを実行します。

from subprocess import Popen, PIPE
process = Popen( "cmd.exe", Shell=False, universal_newlines=True,
                  stdin=PIPE, stdout=PIPE, stderr=PIPE )                             
out, err = process.communicate( commands ) 

SAGE DETAILS:ここでprocess.communicateメソッドに渡されるcommands引数は改行区切り文字列です。たとえば、バッチファイルの内容を文字列に読み取るだけの場合、すでに改行が含まれているため、この方法で実行できます。 重要:文字列は改行"\n"で終了する必要があります。そうでない場合、その最後のコマンドは実行に失敗します。コマンドプロンプトに入力したが、最後にenterを押しなかった場合と同じです。ただし、返されるstdoutの最後に、不可解なMore?行が表示されます。 (これが発生した場合、それが原因です)。

process.communicateは定義により同期的に実行され、stdoutおよびstderrメッセージを返します(Popenコンストラクターでsubprocess.PIPEにメッセージを送信した場合)。

この方法でcmd.exeプロセスを作成し、それに文字列を渡すと、コマンドプロンプトウィンドウを開いてコマンドを入力した場合とまったく同じ結果になります。そして、私はそれを文字通りかなり意味します。これをテストすると、返されるstdoutにコマンドが含まれていることがわかります。 (バッチファイルを実行する場合のように@echo offを含めても問題ありません)。

「クリーン」な標準出力の結果を気にする人のためのヒント:

  • @echo offは、コマンドがこの返された文字列に表示されるのを抑制しませんが、他の方法でそこにある余分な改行を削除します。 (universal_newlines = Trueはそれらの別のセットを取り除きます)

  • コマンドに@シンボルプレフィックスを含めると、コマンドを引き続き実行できます。コマンドを「隠す」ための行ごとの方法である「通常の」バッチプロセス。このコンテキストでは、削除したいstdout行を見つけることができる安全で簡単なマーカーです。 (もしそうなら)

  • Cmd.exe「ヘッダー」が出力に表示されます(Windowsのバージョンなどを示します)。コマンドのセットを@echo offで始めて余分な改行を削除する必要があるので、ヘッダー行が停止し、コマンド/結果が始まった場所を見つけるための優れた方法でもあります。

最後に、パイプを満たし、問題を引き起こす「大きな」出力に関する懸念に対処するには、最初に、問題になるには大量のデータが必要になると思います。ほとんどの人がユースケースで遭遇するよりも多くのデータが必要です。次に、本当に問題がある場合は、書き込み用にファイルを開き、そのファイルハンドル(ファイルオブジェクトへの参照)をPIPEではなくstdout/errに渡します。次に、作成したファイルを使用して必要な操作を行います。

2
BuvinJ

これはpython 2.7で動作し、Windowsでも動作するはずです。おそらくpython> 3。

生成される出力は次のとおりです(日付とスリープを使用すると、コマンドが連続して実行されることが簡単にわかります)。

>>>Die Sep 27 12:47:52 CEST 2016
>>>
>>>Die Sep 27 12:47:54 CEST 2016

ご覧のとおり、コマンドは続けて実行されます。

    import sys
    import subprocess
    import shlex

    cmds = ['date', 'sleep 2', 'date']

    cmds = [shlex.split(x) for x in cmds]

    outputs =[]
    for cmd in cmds:
        outputs.append(subprocess.Popen(cmd,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate())


    for line in outputs:
        print ">>>" + line[0].strip()

これは私が@Marichyasanaの回答とマージして得たものです:

import sys
import os


def run_win_cmds(cmds):

    @Marichyasana code (+/-)

def run_unix_cmds(cmds):

    import subprocess
    import shlex


    cmds = [shlex.split(x) for x in cmds]

    outputs =[]
    for cmd in cmds:
        outputs.append(subprocess.Popen(cmd,
                                        stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate())


    rc = ''
    for line in outputs:
        rc +=  line[0].strip()+'\n'

    return rc


cmds = ['date', 'sleep 2', 'date']

if os.name == 'nt':
     run_win_cmds(cmds)
Elif os.name == 'posix':
    run_unix_cmds(cmds)

これはあなたのニーズに合いませんか? ;)

1

これが私が使用する関数(そしてそれを実行するためのメイン)です。あなたはあなたの問題のためにそれを使うことができると私は言うでしょう。そして、それは柔軟です。

# processJobsInAList.py
# 2016-09-27   7:00:00 AM   Central Daylight Time 

import win32process, win32event

def CreateMyProcess2(cmd):
    ''' create process width no window that runs a command with arguments
    and returns the process handle'''
    si   = win32process.STARTUPINFO()
    info = win32process.CreateProcess(
        None,      # AppName
        cmd,       # Command line
        None,      # Process Security
        None,      # Thread Security
        0,         # inherit Handles?
        win32process.NORMAL_PRIORITY_CLASS,
        None,      # New environment
        None,      # Current directory
        si)        # startup info
    # info is Tuple (hProcess, hThread, processId, threadId)
    return info[0]

if __== '__main__' :
    ''' create/run a process for each list element in "cmds"
    output may be out of order because processes run concurrently '''

    cmds=["echo my","echo heart","echo belongs","echo to","echo daddy"]
    handles    = []
    for i in range(len(cmds)):
        cmd    = 'cmd /c ' + cmds[i]
        handle = CreateMyProcess2(cmd)
        handles.append(handle)

    rc = win32event.WaitForMultipleObjects( handles, 1, -1)  # 1 wait for all, -1 wait infinite
    print 'return code ',rc

出力:
心臓
俺の
所属

パパ
戻りコード0

更新:物事をシリアル化する同じプロセスを実行したい場合:
1)行を削除:handles.append(handle)
2)「WaitFor」行のリスト「handles」の代わりに変数「handle」を置き換えます
3)WaitForMultipleObjectsの代わりにWaitForSingleObjectを使用

1
Marichyasana