Подпроцесс Python.check_call vs .check_output

Мой python script (python 3.4.3) вызывает bash script через подпроцесс:

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

bashscript содержит следующую строку:

ssh -MNf somehost

который открывает общее мастер-соединение с некоторым удаленным хостом, чтобы разрешить некоторые последующие операции.

При выполнении python script он запрашивает пароль для строки ssh, но затем блокируется после ввода пароля и никогда не возвращается. Когда я ctrl-C завершает script, я вижу, что соединение было правильно установлено (поэтому строка ssh была успешно выполнена).

У меня нет этой проблемы с блокировкой при использовании check_call вместо check_output, но check_call не извлекает stdout. Я хотел бы понять, что именно вызывает поведение блокировки для check_output, вероятно, связано с некоторой тонкостью с ssh -MNf.

Ответы

Ответ 1

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

Выход Grandchild перенаправляется на /dev/null, т.е. он не наследует родительский канал, и поэтому check_output() может выйти, не дожидаясь его.

Примечание: & в конце, который переводит процесс python внука в фоновый режим. Он не будет работать в Windows, где shell=True запускает cmd.exe по умолчанию.