Вы можете сделать это с subprocess
, но это не тривиально. Если вы посмотрите на Frequently Used Arguments в документах, вы увидите, что вы можете передать PIPE
как аргумент stderr
, который создает новый канал, передает одну сторону канала дочернему процессу и делает другую сторону доступной для использования в качестве атрибут stderr
. *
Таким образом, вам необходимо будет обслуживать этот канал, записывая на экран и в файл. В общем, получение информации для этого очень сложно. ** В вашем случае есть только одна труба, и вы планируете ее синхронно обслуживать, так что это не так уж плохо.
import subprocess
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
for line in proc.stderr:
sys.stdout.write(line)
log_file.write(line)
proc.wait()
(Обратите внимание, что есть некоторые проблемы с использованием for line in proc.stderr:
-в основном, если то, что вы читаете, оказывается, не быть буферизацией строк по любой причине, вы можете сидеть в ожидании новой строки, даже если есть на самом деле половина вы можете читать фрагменты за раз, скажем, read(128)
или даже read(1)
, чтобы получить данные более плавно, если необходимо. Если вам нужно получить каждый байт, как только он поступит, и может Не позволяйте стоимость read(1)
, вам нужно будет поместить трубу в неблокирующий режим и читать асинхронно.)
Но если вы работаете в Unix, может быть проще использовать команду tee
, чтобы сделать это за вас.
Для быстрого грязного решения можно использовать оболочку для ее прокрутки. Что-то вроде этого:
subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
stdout=file_out)
Но я не хочу отлаживать оболочки трубопроводов; давайте делать это в Python, как показано на рисунке in the docs:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr)
tool.stderr.close()
tee.communicate()
Наконец, есть десяток или более оберток высокоуровневые вокруг подпроцессов и/или оболочки на PyPI- sh
, shell
, shell_command
, shellout
, iterpipes
, sarge
, cmd_utils
, commandwrapper
и т. Д. Поиск «оболочки», «подпроцесса», «процесса», «командной строки» и т. Д. И поиска того, что вам нравится, делает проблему тривиальной.
Что делать, если вам нужно собрать как stderr, так и stdout?
Простой способ сделать это - просто перенаправить одно на другое, как предлагает Свен Марнах в комментарии. Просто измените Popen
параметры следующим образом:.
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
А потом везде вы использовали tool.stderr
, используйте tool.stdout
вместо-например, для последнего примера:
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
tool.stdout.close()
tee.communicate()
Но это имеет некоторые компромиссы. Наиболее очевидно, что смешивание двух потоков вместе означает, что вы не можете записывать stdout в file_out и stderr в log_file или копировать stdout на ваш stdout и stderr на ваш stderr. Но это также означает, что упорядочение может быть недетерминированным - если подпроцесс всегда записывает две строки в stderr перед тем, как писать что-либо в stdout, вы можете получить кучу stdout между этими двумя строками, как только вы смешиваете потоки. И это означает, что они должны использовать режим буферизации stdout, поэтому, если вы полагаетесь на то, что linux/glibc гарантирует, что stderr будет буферизованным по строке (если подпроцесс явно не изменит его), это может быть больше недействительным.
Если вам необходимо обрабатывать два процесса отдельно, это становится сложнее. Ранее я сказал, что обслуживание трубы «на лету» легко, пока у вас есть только одна труба и может обслуживать ее синхронно. Если у вас две трубы, это, очевидно, уже не так. Представьте, вы ожидаете, что на tool.stdout.read()
, а новые данные поступают от tool.stderr
. Если данных слишком много, это может привести к переполнению канала и блокировке подпроцесса. Но даже если этого не произойдет, вы, очевидно, не сможете читать и записывать данные stderr, пока что-то не поступит из stdout.
Если вы используете решение pipe-through-tee
, что позволяет избежать первоначальной проблемы ... но только путем создания нового проекта, который так же плох. У вас есть два экземпляра tee
, и пока вы звоните communicate
на одном, другой сидит в ожидании навсегда.
Итак, в любом случае вам нужен какой-то асинхронный механизм. Вы можете сделать это с помощью нитей, реактора select
, что-то вроде gevent
и т. Д.
Вот быстрый и грязный пример:
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def tee_pipe(pipe, f1, f2):
for line in pipe:
f1.write(line)
f2.write(line)
t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout))
t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr))
t3 = threading.Thread(proc.wait)
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
Однако, есть некоторые крайние случаи, когда это не будет работать. (Проблема заключается в том, как поступают SIGCHLD и SIGPIPE/EPIPE/EOF. Я не думаю, что что-то из этого повлияет на нас здесь, так как мы не отправляем какие-либо данные ... но не доверяйте мне, не думая об этом через и/или тестирование.) Функция subprocess.communicate
от 3.3+ получает все правдоподобные данные. Но вы можете найти гораздо проще использовать одну из реализаций оболочки асинхронного подпроцесса, которую вы можете найти в PyPI и ActiveState, или даже материал подпроцесса из полноценной структуры async, такой как Twisted.
* Документы действительно не объясняет, что трубы, почти как если бы они ожидают от вас старый Unix C рука ... Но некоторые из примеров, особенно в разделе Replacing Older Functions with the subprocess
Module, показать, как они используется, и это довольно просто.
** Жесткая часть - это последовательность двух или более труб надлежащим образом. Если вы будете ждать на одной трубе, другая может переполняться и блокироваться, не дожидаясь вашего ожидания на другом, когда-либо заканчивая. Единственный простой способ обойти это - создать поток для обслуживания каждого канала. (На большинстве платформ nix вы можете использовать вместо этого select
или poll
, но сделать эту кросс-платформу удивительно сложно.) к модулю, особенно communicate
и его помощникам, показывает, как это сделать. (Я связан с 3.3, потому что в более ранних версиях communicate
сам по себе некоторые важные вещи ошибаются ...) Вот почему, когда это возможно, вы хотите использовать communicate
, если вам нужно больше одного канала. В вашем случае вы не можете использовать communicate
, но, к счастью, вам не нужно больше одной трубы.
ли ваш код должен работать на Windows (или других не POSIXy платформ)? Если нет, есть более простой ответ. – abarnert
Этого не нужно! –
Связанный: [Подпроцесс Python получает выход для детей в файл и терминал?] (Http://stackoverflow.com/q/4984428/4279) – jfs