web-dev-qa-db-ja.com

Python subprocess .check_call vs .check_output

My python script(python 3.4.3)は、サブプロセスを介してbashスクリプトを呼び出します。

import subprocess as sp
res = sp.check_output("bashscript", Shell=True)

bashscriptには次の行が含まれます。

ssh -MNf somehost

これにより、リモートホストへの共有マスター接続が開かれ、後続の操作が許可されます。

pythonスクリプトを実行すると、ssh行のパスワードを求めるプロンプトが表示されますが、パスワードが入力された後はブロックされ、戻ることはありません。スクリプト、接続が適切に確立されたことがわかります(そのためssh行が正常に実行されました)。

check_callの代わりにcheck_outputを使用する場合、このブロッキングの問題はありませんが、check_callはstdoutを取得しません。 check_outputのブロック動作の原因を正確に理解したいのですが、おそらくssh -MNfの微妙な問題に関連しています。

17
Kevin S.

check_call()は、/bin/shプロセスが子孫プロセスを待たずに終了するとすぐに戻ります。

check_output()は、すべての出力が読み取られるまで待機します。 sshがパイプを継承する場合、check_output()は終了するまで待機します(継承されたパイプが終了するまで)。

check_call()コード例:

#!/usr/bin/env python
import subprocess
import sys
import time

start = time.time()
cmd = sys.executable + " -c 'import time; time.sleep(2)' &"
subprocess.check_call(cmd, Shell=True)
assert (time.time() - start) < 1

出力は読み込まれません。 check_call()は、孫の背景pythonプロセスを待たずにすぐに戻ります。

check_call()は、単にPopen().wait()です。 Popen()は外部プロセスを開始し、終了を待たずにすぐに戻ります。 .wait()はプロセスの終了ステータスを収集します。他の(孫)プロセスを待機しません。

出力が読み取られた場合(リダイレクトされ、孫pythonプロセスはstdoutパイプを継承します):

start = time.time()
subprocess.check_output(cmd, Shell=True)
assert (time.time() - start) > 2

その後、バックグラウンドpythonパイプを継承したプロセスが終了するまで待機します。

check_output()Popen().communicate()を呼び出して、出力を取得します。 .communicate().wait()を内部的に呼び出します。つまり、check_output()はシェルの終了を待ち、check_output()はEOFを待ちます。

孫がパイプを継承しない場合、check_output()はパイプを待機しません。

start = time.time()
cmd = sys.executable + " -c 'import time; time.sleep(2)' >/dev/null &"
subprocess.check_output(cmd, Shell=True)
assert (time.time() - start) < 1

孫の出力は/dev/nullにリダイレクトされます。つまり、親のパイプを継承しないため、check_output()は待機せずに終了する場合があります。

注:孫pythonプロセスをバックグラウンドに配置する最後の&。デフォルトではShell=Truecmd.exeを開始するWindowsでは動作しません。

40
jfs