子进程命令的实时输出

我正在使用python脚本作为流体动力学代码的驱动程序。当需要运行模拟时,我使用subprocess.Popen来运行代码,将stdoutstderr的输出收集到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)

最初,我是通过tee管道run_command,以便副本直接到日志文件,流仍然直接输出到终端——但这样我就不能存储任何错误(据我所知)。


目前我的临时解决方案是:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()

然后,在另一个终端上运行tail -f log.txt (s.t log_file = 'log.txt')。

226484 次浏览

一个好的但“重量级”的解决方案是使用Twisted -见底部。

如果你愿意只使用stdout,那么下面这些代码应该可以工作:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
stdoutdata = popenobj.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
print "Return code", popenobj.returncode

(如果你使用read(),它会尝试读取整个“文件”,这是没有用的,我们真正可以在这里使用的是读取管道中当前所有数据的东西)

你也可以尝试用线程来解决这个问题,例如:

import subprocess
import sys
import threading


popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)


def stdoutprocess(o):
while True:
stdoutdata = o.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break


t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

现在我们可以通过两个线程来添加stderr。

但是请注意,子进程文档不鼓励直接使用这些文件,并建议使用communicate()(主要涉及死锁,我认为这不是上面的问题),解决方案有点笨拙,所以它看起来真的像子流程模块不能完全胜任这项工作(也请参阅:http://www.python.org/dev/peps/pep-3145/),我们需要看看其他东西。

一个更复杂的解决方案是使用扭曲的,如下所示

使用扭曲的的方法是使用reactor.spawnprocess()创建进程,并提供一个ProcessProtocol,然后异步处理输出。Twisted示例Python代码在这里:https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

看起来行缓冲输出对您来说是可行的,在这种情况下,下面的代码可能适合您。(注意:它未经测试。)这只会实时给出子进程的标准输出。如果你想同时拥有实时的stderr和stdout,你必须用select做一些更复杂的事情。

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
line = proc.stdout.readline()
print line
log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
print line
log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

Python 3的TLDR:

import subprocess
import sys


with open("test.log", "wb") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), b""):
sys.stdout.buffer.write(c)
f.buffer.write(c)

你有两种方法来做到这一点,要么从readreadline函数创建一个迭代器,然后做:

import subprocess
import sys


# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
# replace "" with b'' for Python 3
for c in iter(lambda: process.stdout.read(1), ""):
sys.stdout.write(c)
f.write(c)

import subprocess
import sys


# replace "w" with "wb" for Python 3
with open("test.log", "w") as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
# replace "" with b"" for Python 3
for line in iter(process.stdout.readline, ""):
sys.stdout.write(line)
f.write(line)

或者你可以创建一个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())

这样你就可以把数据写到test.log和标准输出中。

文件方法的唯一优点是代码不会阻塞。因此,你可以在此期间做任何你想做的事情,并以非阻塞的方式随时从reader中读取。当使用PIPE时,readreadline函数将阻塞,直到分别将一个字符写入管道或将一行字符写入管道。

行政摘要(或"tl;dr"当最多有一个subprocess.PIPE时,这很容易,否则很难。

可能是时候解释一下subprocess.Popen是如何做它的事情了。

(注意:这是针对Python 2的。X,尽管3。X是相似的;我不太清楚Windows版本。我更了解POSIX之类的东西。)

Popen函数需要同时处理0到3个I/O流。它们像往常一样被表示为stdinstdoutstderr

你可以提供:

  • None,表示你不想重定向流。它将像往常一样继承这些。请注意,至少在POSIX系统上,这并不意味着它将使用Python的sys.stdout,而只是Python的实际标准输出;见演示结束。
  • int值。这是一个“原始”;文件描述符(至少在POSIX)。(旁注:PIPESTDOUT在内部实际上是__abc0,但“不可能”;描述符,-1和-2。)
  • 流——实际上是任何具有fileno方法的对象。Popen将使用stream.fileno()找到该流的描述符,然后继续寻找int值。
  • subprocess.PIPE,表示Python应该创建一个管道。
  • subprocess.STDOUT(仅适用于stderr):告诉Python使用与stdout相同的描述符。只有当你为stdout提供了一个(非-None)值时,这才有意义,即使这样,如果你设置了stdout=subprocess.PIPE,它也只有需要。(否则,你可以提供与stdout相同的参数,例如,Popen(..., stdout=stream, stderr=stream)。)

最简单的情况(没有管道)

如果你没有重定向任何东西(将这三个都保留为默认的None值或显式提供None),那么Pipe很容易做到。它只需要剥离子进程并让它运行。或者,如果你重定向到一个非- __abc3 -一个int或流的__abc5 -这仍然很容易,因为操作系统会做所有的工作。Python只需要剥离子进程,将其stdin、stdout和/或stderr连接到所提供的文件描述符。

仍然简单的情况是:一根管子

如果你只重定向一个流,Pipe仍然很简单。我们每次选一条小溪看吧。

假设你想要提供一些stdin,但是让stdoutstderr不被重定向,或者转到一个文件描述符。作为父进程,Python程序只需要使用write()将数据发送到管道中。你可以自己做,例如:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

或者你可以将stdin数据传递给proc.communicate(),然后执行上面所示的stdin.write。没有输出返回,所以communicate()只有另一个真正的工作:它也为你关闭管道。(如果你不调用proc.communicate(),你必须调用proc.stdin.close()来关闭管道,这样子进程就知道没有更多的数据通过了。)

假设你想捕获stdout,但保留stdinstderr。同样,这很简单:只需调用proc.stdout.read()(或等效),直到没有更多输出。由于proc.stdout()是一个正常的Python I/O流,你可以在它上面使用所有正常的结构,比如:

for line in proc.stdout:

或者,同样,你可以使用proc.communicate(),它只是为你做read()

如果你只想捕获stderr,它的工作原理与stdout相同。

在事情变得复杂之前,还有一个技巧。假设你想要捕获stdout,并且还捕获stderr在与stdout相同的管道上:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

在这种情况下,subprocess "cheats"!好吧,它必须这样做,所以它不是真正的欺骗:它启动子进程时,将它的标准输出和标准derr指向(单个)管道描述符,然后反馈给它的父进程(Python)。在父端,同样只有一个用于读取输出的管道描述符。所有的“stderr"output显示在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:

但是,哎呀,这里我们已经创建了至少两个,也许三个不同的管道,所以count(None)返回1或0。我们必须用艰难的方式做事。

在Windows上,它使用threading.Thread来累积self.stdoutself.stderr的结果,并让父线程传递self.stdin输入数据(然后关闭管道)。

在POSIX上,如果可用,则使用poll,否则使用select来累积输出并传递stdin输入。所有这些都运行在(单个)父进程/线程中。

这里需要线程或轮询/选择来避免死锁。例如,假设我们将三个流重定向到三个独立的管道。进一步假设,在写入过程暂停之前,可以向管道中填充多少数据有一个小限制,等待读取过程“清理”。管子的另一端。让我们将这个小限制设置为单个字节,只是为了说明。(这实际上是事情的工作原理,除了限制比一个字节大得多。)

如果父进程(Python)试图写入几个字节——比如__abc0到proc.stdin,第一个字节进入,然后第二个字节导致Python进程挂起,等待子进程读取第一个字节,清空管道。

同时,假设子进程决定打印友好的"Hello!别慌!“的问候。H进入它的标准输出管道,但是e使它挂起,等待它的父对象读取H,清空标准输出管道。

现在我们被困住了:Python进程睡着了,等着说完“go”,子进程也睡着了,等着说完“Hello!”别慌!“。

subprocess.Popen代码使用线程或选择/轮询避免了这个问题。当字节可以通过管道时,它们就会通过。当它们不能时,只有一个线程(而不是整个进程)必须休眠——或者,在select/poll的情况下,Python进程同时等待“can write”。或者“data available”,只有在有空间时才写入进程的stdin,只有在数据准备好时才读取它的stdout和/或stderr。proc.communicate()代码(实际上是处理复杂情况的_communicate)在发送所有stdin数据(如果有)并且所有stdout和/或stderr数据已经累积之后返回。

如果你想在两个不同的管道上同时读取stdoutstderr(不管是否有任何stdin重定向),你也需要避免死锁。这里的死锁场景是不同的——当你从stdout提取数据时,子进程将很长的内容写入stderr时,就会发生死锁,反之亦然——但它仍然存在。


演示

我承诺演示,在未重定向的情况下,Python subprocesses写入底层标准输出,而不是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()

运行时:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered


start show2
hello

注意,如果添加stdout=sys.stdout,第一个例程将失败,因为StringIO对象没有fileno。如果添加stdout=sys.stdout,第二个函数将省略hello,因为sys.stdout已被重定向到os.devnull

(如果重定向Python的file-descriptor-1,子进程将遵循该重定向。open(os.devnull, 'w')调用产生一个fileno()大于2的流。)

如果你能够使用第三方库,你可以使用类似sarge的东西(披露:我是它的维护者)。这个库允许对子进程的输出流进行非阻塞访问——它是分层在subprocess模块之上的。

我们也可以使用默认的文件迭代器来读取stdout,而不是使用带有readline()的iter构造。

import subprocess
import sys


process = subprocess.Popen(
your_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
for line in process.stdout:
sys.stdout.write(line)

为什么不直接将stdout设置为sys.stdout?如果你也需要输出到日志,那么你可以简单地重写f的write方法。

import sys
import subprocess


class SuperFile(open.__class__):


def write(self, data):
sys.stdout.write(data)
super(SuperFile, self).write(data)


f = SuperFile("log.txt","w+")
process = subprocess.Popen(command, stdout=f, stderr=f)

这是我在我的一个项目中使用的一个类。它将子流程的输出重定向到日志。起初,我尝试简单地重写write方法,但这并不管用,因为子进程永远不会调用它(重定向发生在文件描述符级别)。我使用了我自己的管道,类似于subprocess模块。这样做的好处是将所有日志记录/打印逻辑封装在适配器中,你可以简单地将记录器的实例传递给Popen: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):


def __init__(self, logname, level = logging.INFO):
super().__init__()
self.log = logging.getLogger(logname)
self.readpipe, self.writepipe = os.pipe()


logFunctions = {
logging.DEBUG: self.log.debug,
logging.INFO: self.log.info,
logging.WARN: self.log.warn,
logging.ERROR: self.log.warn,
}


try:
self.logFunction = logFunctions[level]
except KeyError:
self.logFunction = self.log.info


def fileno(self):
#when fileno is called this indicates the subprocess is about to fork => start thread
self.start()
return self.writepipe


def finished(self):
"""If the write-filedescriptor is not closed this thread will
prevent the whole program from exiting. You can use this method
to clean up after the subprocess has terminated."""
os.close(self.writepipe)


def run(self):
inputFile = os.fdopen(self.readpipe)


while True:
line = inputFile.readline()


if len(line) == 0:
#no new data was added
break


self.logFunction(line.strip())

如果你不需要日志记录,而只是想使用print(),你显然可以删除大部分代码,使类更短。你也可以通过__enter____exit__方法来扩展它,并在__exit__中调用finished,这样你就可以很容易地将它用作上下文。

我尝试过的所有上述解决方案都无法分离stderr和stdout输出(多个管道),或者当操作系统管道缓冲区已满时永远阻塞,这发生在你运行输出太快的命令时(在python poll() subprocess手册上对此有警告)。我发现唯一可靠的方法是通过选择,但这是一个posix-only解决方案:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poller = select.poll()
poller.register(p.stdout, select.POLLIN)
poller.register(p.stderr, select.POLLIN)


coutput=''
cerror=''
fdhup={}
fdhup[p.stdout.fileno()]=0
fdhup[p.stderr.fileno()]=0
while sum(fdhup.values()) < len(fdhup):
try:
r = poller.poll(1)
except select.error, err:
if err.args[0] != EINTR:
raise
r=[]
for fd, flags in r:
if flags & (select.POLLIN | select.POLLPRI):
c = os.read(fd, 1024)
if rtoutput:
sys.stdout.write(c)
sys.stdout.flush()
if fd == p.stderr.fileno():
cerror+=c
else:
coutput+=c
else:
fdhup[fd]=1
return p.poll(), coutput.strip(), cerror.strip()

除了这些答案之外,一个简单的方法也可以如下:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)


while process.stdout.readable():
line = process.stdout.readline()


if not line:
break


print(line.strip())

循环可读流,只要它是可读的,如果它得到一个空的结果,停止。

这里的关键是readline()返回一行(结尾是\n),只要有输出,如果它真的在结尾,则返回空行。

希望这能帮助到一些人。

类似于以前的答案,但以下解决方案适用于我在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()

基于以上所有内容,我建议使用稍微修改过的版本(python3):

  • while循环调用readline (iter建议的解决方案似乎永远阻塞我- Python 3, Windows 7)
  • 结构化的,因此在轮询返回非-None后,读取数据的处理不需要重复
  • Stderr管道到stdout,因此两个输出输出都被读取
  • 增加了获取cmd退出值的代码。

代码:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, universal_newlines=True)
while True:
rd = proc.stdout.readline()
print(rd, end='')  # and whatever you want to do...
if not rd:  # EOF
returncode = proc.poll()
if returncode is not None:
break
time.sleep(0.1)  # cmd closed stdout, but not exited yet


# You may want to check on ReturnCode here

我认为subprocess.communicate方法有点误导:它实际上填充了你在subprocess.Popen中指定的stdoutstderr

然而,从你可以提供给subprocess.Popenstdoutstderr参数的subprocess.PIPE中读取最终会填满操作系统管道缓冲区并导致应用程序死锁(特别是当你有多个必须使用subprocess的进程/线程时)。

我提出的解决方案是为stdoutstderr提供文件-并读取文件的内容,而不是从死锁PIPE中读取。这些文件可以是tempfile.NamedTemporaryFile()——当它们被subprocess.communicate写入时,也可以访问它进行读取。

下面是一个示例用法:

try:
with ProcessRunner(
("python", "task.py"), env=os.environ.copy(), seconds_to_wait=0.01
) as process_runner:
for out in process_runner:
print(out)
except ProcessError as e:
print(e.error_message)
raise

这是源代码,它是准备使用,我可以提供尽可能多的注释来解释它的功能:

如果你正在使用python2,请确保首先从pypi安装最新版本的subprocess32包。

import os
import sys
import threading
import time
import tempfile
import logging


if os.name == 'posix' and sys.version_info[0] < 3:
# Support python 2
import subprocess32 as subprocess
else:
# Get latest and greatest from python 3
import subprocess


logger = logging.getLogger(__name__)




class ProcessError(Exception):
"""Base exception for errors related to running the process"""




class ProcessTimeout(ProcessError):
"""Error that will be raised when the process execution will exceed a timeout"""




class ProcessRunner(object):
def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
"""
Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
Process Runner. This is a class that should be used as a context manager - and that provides an iterator
for reading captured output from subprocess.communicate in near realtime.


Example usage:




try:
with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
for out in process_runner:
print(out)
except ProcessError as e:
print(e.error_message)
raise


:param args: same as subprocess.Popen
:param env: same as subprocess.Popen
:param timeout: same as subprocess.communicate
:param bufsize: same as subprocess.Popen
:param seconds_to_wait: time to wait between each readline from the temporary file
:param kwargs: same as subprocess.Popen
"""
self._seconds_to_wait = seconds_to_wait
self._process_has_timed_out = False
self._timeout = timeout
self._process_done = False
self._std_file_handle = tempfile.NamedTemporaryFile()
self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
self._thread = threading.Thread(target=self._run_process)
self._thread.daemon = True


def __enter__(self):
self._thread.start()
return self


def __exit__(self, exc_type, exc_val, exc_tb):
self._thread.join()
self._std_file_handle.close()


def __iter__(self):
# read all output from stdout file that subprocess.communicate fills
with open(self._std_file_handle.name, 'r') as stdout:
# while process is alive, keep reading data
while not self._process_done:
out = stdout.readline()
out_without_trailing_whitespaces = out.rstrip()
if out_without_trailing_whitespaces:
# yield stdout data without trailing \n
yield out_without_trailing_whitespaces
else:
# if there is nothing to read, then please wait a tiny little bit
time.sleep(self._seconds_to_wait)


# this is a hack: terraform seems to write to buffer after process has finished
out = stdout.read()
if out:
yield out


if self._process_has_timed_out:
raise ProcessTimeout('Process has timed out')


if self._process.returncode != 0:
raise ProcessError('Process has failed')


def _run_process(self):
try:
# Start gathering information (stdout and stderr) from the opened process
self._process.communicate(timeout=self._timeout)
# Graceful termination of the opened process
self._process.terminate()
except subprocess.TimeoutExpired:
self._process_has_timed_out = True
# Force termination of the opened process
self._process.kill()


self._process_done = True


@property
def return_code(self):
return self._process.returncode






没有一个python的解决方案对我有效。 结果是proc.stdout.read()或类似的可能会永远阻塞

因此,我像这样使用tee:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

如果你已经在使用shell=True,这个解决方案很方便。

${PIPESTATUS}捕获整个命令链的成功状态(仅在Bash中可用)。 如果我省略了&& exit ${PIPESTATUS},那么这将总是返回0,因为tee永远不会失败

unbuffer可能需要立即将每行打印到终端,而不是等待太长时间,直到“管道缓冲区”被填满。 但是,unbuffer将吞下assert (SIG Abort)退出状态…

2>&1也记录文件的标准错误。

解决方法1:实时并发记录stdoutstderr

一个简单的解决方案,同时记录标准输出和标准错误,在实时中逐行记录到日志文件中。

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()

解决方案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()


import os


def execute(cmd, callback):
for line in iter(os.popen(cmd).readline, ''):
callback(line[:-1])


execute('ls -a', print)

如果你所需要的只是输出将在控制台上可见,对我来说最简单的解决方案是将以下参数传递给Popen

with Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) as proc:

哪个将使用您的python脚本stdio文件句柄

我找到了一个解决复杂问题的简单方法。

  1. stdout和stderr都需要流化。
  2. 它们都需要是非阻塞的:当没有输出时,当有太多输出时。
  3. 不想使用Threading或multiprocessing,也不愿意使用pexpect。

这个解决方案使用了一个要点,我发现在这里

import subprocess as sbp
import fcntl
import os


def non_block_read(output):
fd = output.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
try:
return output.readline()
except:
return ""


with sbp.Popen('find / -name fdsfjdlsjf',
shell=True,
universal_newlines=True,
encoding='utf-8',
bufsize=1,
stdout=sbp.PIPE,
stderr=sbp.PIPE) as p:
while True:
out = non_block_read(p.stdout)
err = non_block_read(p.stderr)
if out:
print(out, end='')
if err:
print('E: ' + err, end='')
if p.poll() is not None:
break

有同样的问题,并使用process.sdtout.read1()得出了一个简单而干净的解决方案,它完全适合我在python3中的需求。

下面是一个使用ping命令的演示(需要互联网连接):

from subprocess import Popen, PIPE


cmd = "ping 8.8.8.8"
proc = Popen([cmd], shell=True, stdout=PIPE)
while True:
print(proc.stdout.read1())

当ping命令实时报告其数据时,大约每秒钟就会在python控制台中打印一行新行。