Как реплицировать поведение tee в Python при использовании подпроцесса?
Я ищу решение Python, которое позволит мне сохранить вывод команды в файле, не скрывая ее от консоли.
FYI: Я спрашиваю о tee (как утилита командной строки Unix), а не о функции с тем же именем из Python intertools.
Подробнее
- Решение Python (не вызывающее
tee
, оно не доступно под Windows)
- Мне не нужно предоставлять какие-либо данные для stdin для вызываемого процесса
- У меня нет контроля над вызываемой программой. Все, что я знаю, это вывод чего-то в stdout и stderr и возврат с кодом выхода.
- Работа при вызове внешних программ (подпроцесс)
- Чтобы работать как для
stderr
, так и stdout
- Возможность различать stdout и stderr, потому что я могу отображать только одну из них в консоли, или я мог бы попытаться вывести stderr с использованием другого цвета - это означает, что
stderr = subprocess.STDOUT
не будет работать.
- Live output (progressive) - процесс может работать в течение длительного времени, и я не могу дождаться его завершения.
- Python 3 совместимый код (важно)
Ссылки
Вот некоторые неполные решения, которые я нашел до сих пор:
Диаграмма http://blog.i18n.ro/wp-content/uploads/2010/06/Drawing_tee_py.png
Текущий код (вторая попытка)
#!/usr/bin/python
from __future__ import print_function
import sys, os, time, subprocess, io, threading
cmd = "python -E test_output.py"
from threading import Thread
class StreamThread ( Thread ):
def __init__(self, buffer):
Thread.__init__(self)
self.buffer = buffer
def run ( self ):
while 1:
line = self.buffer.readline()
print(line,end="")
sys.stdout.flush()
if line == '':
break
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdoutThread = StreamThread(io.TextIOWrapper(proc.stdout))
stderrThread = StreamThread(io.TextIOWrapper(proc.stderr))
stdoutThread.start()
stderrThread.start()
proc.communicate()
stdoutThread.join()
stderrThread.join()
print("--done--")
#### test_output.py ####
#!/usr/bin/python
from __future__ import print_function
import sys, os, time
for i in range(0, 10):
if i%2:
print("stderr %s" % i, file=sys.stderr)
else:
print("stdout %s" % i, file=sys.stdout)
time.sleep(0.1)
Реальный выход
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--
Ожидаемый результат состоял в том, чтобы упорядочить строки. Замечание, изменение Popen для использования только одной PIPE не допускается, потому что в реальной жизни я хочу делать разные вещи с помощью stderr и stdout.
Также даже во втором случае мне не удалось получить в реальном времени, как, например, все результаты были получены, когда процесс завершился. По умолчанию Popen не должен использовать никаких буферов (bufsize = 0).
Ответы
Ответ 1
Наконец, мне пришлось самому реализовать команду tee()
в Python.
Вы можете получить его здесь http://github.com/pycontribs/tendo/blob/master/tendo/tee.py
В настоящее время это позволяет вам делать такие вещи, как:
tee("python --v") # works just like os.system()
tee("python --v", "log.txt") # file names
tee("python --v", file_handle)
import logging
tee("python --v", logging.info) # receive a method
Единственным текущим ограничением является то, что он не может различать stderr
и stdout
, что означает, что он объединит их обоих.
Ответ 2
Я вижу, что это довольно старый пост, но на всякий случай кто-то все еще ищет способ сделать это:
proc = subprocess.Popen(["ping", "localhost"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
with open("logfile.txt", "w") as log_file:
while proc.poll() is None:
line = proc.stderr.readline()
if line:
print "err: " + line.strip()
log_file.write(line)
line = proc.stdout.readline()
if line:
print "out: " + line.strip()
log_file.write(line)
Ответ 3
Это простой порт tee
на Python.
import sys
sinks = sys.argv[1:]
sinks = [open(sink, "w") for sink in sinks]
sinks.append(sys.stderr)
while True:
input = sys.stdin.read(1024)
if input:
for sink in sinks:
sink.write(input)
else:
break
Я сейчас работаю на Linux, но это должно работать на большинстве платформ.
Что касается части subprocess
, я не знаю, как вы хотите подключить подпроцесс stdin
, stdout
и stderr
к вашим stdin
, stdout
, stderr
и file, но я знаю, что вы можете сделать это:
import subprocess
callee = subprocess.Popen( ["python", "-i"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
Теперь вы можете обращаться к callee.stdin
, callee.stdout
и callee.stderr
как к обычным файлам, что позволяет работать вышеупомянутому "решению". Если вы хотите получить callee.returncode
, вам нужно будет сделать дополнительный вызов callee.poll()
.
Будьте осторожны с записью в callee.stdin
: если процесс callee.stdin
когда вы это делаете, может возникнуть ошибка (в Linux я получаю IOError: [Errno 32] Broken pipe
).
Ответ 4
Если вы не хотите взаимодействовать с процессом, вы можете просто использовать модуль подпроцесса.
Пример:
tester.py
import os
import sys
for file in os.listdir('.'):
print file
sys.stderr.write("Oh noes, a shrubbery!")
sys.stderr.flush()
sys.stderr.close()
testing.py
import subprocess
p = subprocess.Popen(['python', 'tester.py'], stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
print stdout, stderr
В вашей ситуации вы можете просто написать stdout/stderr в файл в первую очередь. Вы также можете отправлять аргументы в свой процесс с помощью связи, хотя я не мог понять, как постоянно взаимодействовать с подпроцессом.
Ответ 5
Есть тонкие проблемы/ошибки в python, связанные с subprocess.PIPE:
http://bugs.python.org/issue1652
По-видимому, это было исправлено в python3 +, но не в python 2.7 и старше. Для этого вам нужно использовать:
code.google.com/p/python-subprocess32/
Ответ 6
Попробуйте следующее:
import sys
class tee-function :
def __init__(self, _var1, _var2) :
self.var1 = _var1
self.var2 = _var2
def __del__(self) :
if self.var1 != sys.stdout and self.var1 != sys.stderr :
self.var1.close()
if self.var2 != sys.stdout and self.var2 != sys.stderr :
self.var2.close()
def write(self, text) :
self.var1.write(text)
self.var2.write(text)
def flush(self) :
self.var1.flush()
self.var2.flush()
stderrsav = sys.stderr
out = open(log, "w")
sys.stderr = tee-function(stderrsav, out)
Ответ 7
Я написал вещь, которая обертывает командами оболочки в Python.
Основные преимущества:
- Эта утилита фиксирует stdout/stderr always
- Эта утилита предоставляет возможность эхо файла stdout/stderr в stdout/stderr для процесса
- При повторном вызове stdout/stderr out/err нет задержки
Основной недостаток:
- Работает только с bash/unix
источник: https://gist.github.com/AndrewHoos/9f03c74988469b517a7a
Ответ 8
Мое решение не элегантно, но оно работает.
Вы можете использовать powershell для получения доступа к "тройнику" под WinOS.
import subprocess
import sys
cmd = ['powershell', 'ping', 'google.com', '|', 'tee', '-a', 'log.txt']
if 'darwin' in sys.platform:
cmd.remove('powershell')
p = subprocess.Popen(cmd)
p.wait()
Ответ 9
Вот как это можно сделать
import sys
from subprocess import Popen, PIPE
with open('log.log', 'w') as log:
proc = Popen(["ping", "google.com"], stdout=PIPE, encoding='utf-8')
while proc.poll() is None:
text = proc.stdout.readline()
log.write(text)
sys.stdout.write(text)