Dil*_*rix 165 python error-handling shell logging subprocess
我正在使用python脚本作为流体动力学代码的驱动程序.当运行模拟时,我subprocess.Popen
用来运行代码,从stdout和stderr收集输出到subprocess.PIPE
---然后我可以打印(并保存到日志文件)输出信息,并检查是否有任何错误.问题是,我不知道代码是如何进展的.如果我直接从命令行运行它,它会给我输出关于它在什么时间迭代,什么时间,下一个时间步骤是什么等等的输出.
有没有办法既存储输出(用于记录和错误检查),还产生实时流输出?
我的代码的相关部分:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
Run Code Online (Sandbox Code Playgroud)
最初我正在run_command
通过管道,tee
以便副本直接进入日志文件,并且流仍然直接输出到终端 - 但这样我就不能存储任何错误(对我的知识).
编辑:
临时解决方案:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
Run Code Online (Sandbox Code Playgroud)
然后,在另一个终端,运行tail -f log.txt
(st log_file = 'log.txt'
).
Vik*_*kez 151
有两种方法可以通过从read
或readline
函数创建迭代器来执行此操作:
import subprocess
import sys
with open('test.log', 'w') as f: # replace 'w' with 'wb' for Python 3
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), ''): # replace '' with b'' for Python 3
sys.stdout.write(c)
f.write(c)
Run Code Online (Sandbox Code Playgroud)
要么
import subprocess
import sys
with open('test.log', 'w') as f: # replace 'w' with 'wb' for Python 3
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''): # replace '' with b'' for Python 3
sys.stdout.write(line)
f.write(line)
Run Code Online (Sandbox Code Playgroud)
或者您可以创建一个reader
和一个writer
文件.传递writer
到Popen
并读取reader
import io
import time
import subprocess
import sys
filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
process = subprocess.Popen(command, stdout=writer)
while process.poll() is None:
sys.stdout.write(reader.read())
time.sleep(0.5)
# Read the remaining
sys.stdout.write(reader.read())
Run Code Online (Sandbox Code Playgroud)
这样,您将test.log
在标准输出和标准输出中写入数据.
文件方法的唯一优点是您的代码不会阻塞.因此,您可以在此期间做任何您想做的事情,并reader
以非阻塞的方式随时阅读.当使用PIPE
,read
和readline
功能将阻塞,直到任一个字符被写入到管或线被分别写入到管道.
tor*_*rek 87
subprocess.PIPE
,否则它很难.可能是时候解释一下它是如何subprocess.Popen
做到的.
(警告:这是针对Python 2.x,尽管3.x类似;而且我对Windows变体很模糊.我更了解POSIX的东西.)
该Popen
功能需要同时处理零到三个I/O流.这些表示stdin
,stdout
和stderr
通常一样.
你可以提供:
None
,表示您不想重定向流.它将像往常一样继承这些.请注意,至少在POSIX系统上,这并不意味着它将使用Python sys.stdout
,而只是Python的实际标准输出; 最后看看demo.int
价值.这是一个"原始"文件描述符(至少在POSIX中).(旁注:PIPE
和STDOUT
实际上int
,内部S中,但都是"不可能"的描述,-1,-2)fileno
方法的对象. Popen
将找到该流的描述符,使用stream.fileno()
,然后继续执行int
值.subprocess.PIPE
,表明Python应该创建一个管道.subprocess.STDOUT
(stderr
仅用于):告诉Python使用与之相同的描述符stdout
.这只有在您提供(非None
)值时才有意义stdout
,即使这样,只有在您设置时才需要它stdout=subprocess.PIPE
.(否则你可以提供你提供的相同参数stdout
,例如,Popen(..., stdout=stream, stderr=stream)
.)如果你没有重定向(将所有三个保留为默认None
值或显式提供None
),Pipe
这很容易.它只需要分离子进程并让它运行.或者,如果您重定向到非PIPE
-an int
或流的fileno()
-it仍然很容易,因为操作系统可以完成所有工作.Python只需要分离子进程,将其stdin,stdout和/或stderr连接到提供的文件描述符.
如果您只重定向一个流,Pipe
仍然很容易.我们一次选择一个流并观看.
假设你想提供一些stdin
,但让stdout
和stderr
去未重定向,或去文件描述符.作为父进程,您的Python程序只需write()
用于向管道发送数据.你可以自己做,例如:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
Run Code Online (Sandbox Code Playgroud)
或者您可以将stdin数据传递给proc.communicate()
,然后执行stdin.write
上面显示的操作.没有输出回来,所以communicate()
只有另一个真正的工作:它也为你关闭管道.(如果不调用proc.communicate()
,则必须调用proc.stdin.close()
以关闭管道,以便子进程知道没有更多数据通过.)
假设你想捕捉stdout
,但离开stdin
和stderr
孤独.同样,这很简单:只需调用proc.stdout.read()
(或等效)直到没有更多输出.由于proc.stdout()
是普通的Python I/O流,您可以使用它上面的所有常规构造,例如:
for line in proc.stdout:
Run Code Online (Sandbox Code Playgroud)
或者,再次,你可以使用proc.communicate()
,这只是read()
为你做的.
如果您只想捕获stderr
,它的工作方式与使用相同stdout
.
在事情变得艰难之前还有一个技巧.假设你想捕捉stdout
,并且还捕捉stderr
,但在同一管道的标准输出:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Run Code Online (Sandbox Code Playgroud)
在这种情况下,subprocess
"作弊"!好吧,它必须这样做,所以它并没有真正作弊:它启动子进程,其stdout和stderr都指向(单个)管道描述符,反馈给它的父(Python)进程.在父端,只有一个管道描述符用于读取输出.所有"stderr"输出都显示出来proc.stdout
,如果你调用proc.communicate()
,stderr结果(元组中的第二个值)将是None
,而不是字符串.
当您想要使用至少两个管道时,所有问题都会出现.事实上,subprocess
代码本身有这个位:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Run Code Online (Sandbox Code Playgroud)
但是,唉,这里我们已经制作了至少两个,也许三个不同的管道,所以count(None)
返回1或0.我们必须以艰难的方式做事.
在Windows上,这用于threading.Thread
累积self.stdout
和的结果self.stderr
,并让父线程传递self.stdin
输入数据(然后关闭管道).
在POSIX上,这将使用(poll
如果可用),否则select
,累积输出并传递stdin输入.所有这些都在(单个)父进程/线程中运行.
这里需要线程或轮询/选择以避免死锁.例如,假设我们已将所有三个流重定向到三个单独的管道.进一步假设在写入过程暂停之前,有多少数据可以填充到管道中,等待读取过程从另一端"清理"管道.我们将这个小限制设置为单个字节,仅用于说明.(这实际上是如何工作的,除了限制远大于一个字节.)
如果父(Python)进程尝试写入几个字节 - 比如说,'go\n'
to proc.stdin
,第一个字节进入,然后第二个进程导致Python进程暂停,等待子进程读取第一个字节,清空管道.
同时,假设子进程决定打印一个友好的"你好!不要恐慌!" 问候.在H
进入它的标准输出管道,但e
导致其暂停,等待其家长阅读H
,排空stdout管道.
现在我们陷入困境:Python进程处于睡眠状态,等待完成说"go",子进程也处于睡眠状态,等待完成说"你好!不要恐慌!".
该subprocess.Popen
代码通过线程或选择/轮询避免了这个问题.当字节可以越过管道时,它们就会消失.当它们不能时,只有一个线程(不是整个进程)必须休眠 - 或者在select/poll的情况下,Python进程同时等待"可写"或"数据可用",写入进程的stdin只有当有空间时,才会在数据准备就绪时读取它的标准输出和/或标准输出.一旦发送了所有stdin数据(如果有的话)并且已经累积了所有stdout和/或stderr数据,proc.communicate()
代码(实际上_communicate
处理多毛的情况)将返回.
如果你想同时读取stdout
并stderr
在两个不同的管道(无论任何的stdin
重定向),则需要避免死锁了.这里的死锁场景是不同的 - 当stderr
你从中提取数据时,子进程写了很长的东西stdout
,反之亦然 - 但它仍然在那里.
我答应证明,未重定向,Python subprocess
es写入底层stdout,而不是sys.stdout
.所以,这里有一些代码:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Run Code Online (Sandbox Code Playgroud)
运行时:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Run Code Online (Sandbox Code Playgroud)
请注意,如果添加stdout=sys.stdout
,第一个例程将失败,因为StringIO
对象没有fileno
.第二个将省略hello
如果您添加stdout=sys.stdout
以来sys.stdout
已被重定向到os.devnull
.
(如果重定向Python的文件描述符-1,子进程将遵循该重定向.该open(os.devnull, 'w')
调用产生一个fileno()
大于2 的流.)
小智 20
我们也可以使用默认文件迭代器来读取stdout,而不是使用带有readline()的iter构造.
import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
sys.stdout.write(line)
Run Code Online (Sandbox Code Playgroud)
kab*_*hya 12
除了所有这些答案之外,一种简单的方法也可以如下:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
while process.stdout.readable():
line = process.stdout.readline()
if not line:
break
print(line.strip())
Run Code Online (Sandbox Code Playgroud)
只要可读流,就循环遍历可读流,如果结果为空,则停止。
这里的关键是只要有输出就readline()
返回一行(\n
在末尾),如果它真的在末尾则为空。
希望这可以帮助某人。
pap*_*adp 10
如果您需要的只是输出在控制台上可见,对我来说最简单的解决方案是将以下参数传递给Popen
with Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) as proc:
Run Code Online (Sandbox Code Playgroud)
它将使用你的 python 脚本 stdio 文件句柄
解决方案 1:实时同时记录stdout
ANDstderr
一个简单的解决方案,它同时将 stdout 和 stderr实时逐行记录到日志文件中。
import subprocess as sp
from concurrent.futures import ThreadPoolExecutor
def log_popen_pipe(p, stdfile):
with open("mylog.txt", "w") as f:
while p.poll() is None:
f.write(stdfile.readline())
f.flush()
# Write the rest from the buffer
f.write(stdfile.read())
with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
with ThreadPoolExecutor(2) as pool:
r1 = pool.submit(log_popen_pipe, p, p.stdout)
r2 = pool.submit(log_popen_pipe, p, p.stderr)
r1.result()
r2.result()
Run Code Online (Sandbox Code Playgroud)
解决方案 2:一个read_popen_pipes()
允许您同时实时迭代两个管道 (stdout/stderr)的函数
import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor
def enqueue_output(file, queue):
for line in iter(file.readline, ''):
queue.put(line)
file.close()
def read_popen_pipes(p):
with ThreadPoolExecutor(2) as pool:
q_stdout, q_stderr = Queue(), Queue()
pool.submit(enqueue_output, p.stdout, q_stdout)
pool.submit(enqueue_output, p.stderr, q_stderr)
while True:
if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
break
out_line = err_line = ''
try:
out_line = q_stdout.get_nowait()
err_line = q_stderr.get_nowait()
except Empty:
pass
yield (out_line, err_line)
# The function in use:
with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
for out_line, err_line in read_popen_pipes(p):
print(out_line, end='')
print(err_line, end='')
p.poll()
Run Code Online (Sandbox Code Playgroud)
小智 5
与之前的答案类似,但以下解决方案适用于我在 Windows 上使用 Python3 提供实时打印和登录的通用方法(源代码)
def print_and_log(command, logFile):
with open(logFile, 'wb') as f:
command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
while True:
output = command.stdout.readline()
if not output and command.poll() is not None:
f.close()
break
if output:
f.write(output)
print(str(output.strip(), 'utf-8'), flush=True)
return command.poll()
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
129257 次 |
最近记录: |