【问题标题】:read subprocess stdout line by line逐行读取子进程标准输出
【发布时间】:2011-02-17 18:32:57
【问题描述】:

我的 python 脚本使用 subprocess 来调用一个非常嘈杂的 linux 实用程序。我想将所有输出存储到一个日志文件中,并将其中的一些显示给用户。我认为以下方法可行,但在实用程序产生大量输出之前,输出不会显示在我的应用程序中。

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

我真正想要的行为是让过滤器脚本打印从子进程接收到的每一行。有点像 tee 所做的,但使用 python 代码。

我错过了什么?这甚至可能吗?


更新:

如果将sys.stdout.flush() 添加到 fake_utility.py,则代码在 python 3.1 中具有所需的行为。我正在使用python 2.6。你会认为使用proc.stdout.xreadlines() 与py3k 的工作方式相同,但事实并非如此。


更新 2:

这是最小的工作代码。

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

【问题讨论】:

标签: python subprocess


【解决方案1】:

自从我上次使用 Python 以来已经有很长时间了,但我认为问题出在语句 for line in proc.stdout 上,它会在迭代之前读取整个输入。解决方案是改用readline()

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

当然你还是要处理子进程的缓冲。

注意:according to the documentation 带有迭代器的解决方案应该等同于使用 readline(),除了预读缓冲区,但是(或正因为如此)提议的更改确实对我产生了不同的结果(Python 2.5在 Windows XP 上)。

【讨论】:

  • 对于 file.readline()for line in file 参见 bugs.python.org/issue3907(简而言之:它适用于 Python3;在 Python 2.6+ 上使用 io.open()
  • 根据 PEP 8 (python.org/dev/peps/pep-0008) 中的“编程建议”,对于 EOF 的更 Pythonic 测试将是“if not line:”。
  • @naxa:对于管道:for line in iter(proc.stdout.readline, ''):.
  • @Jan-PhilipGehrcke:是的。 1. 你可以在 Python 3 上使用for line in proc.stdout(没有预读错误) 2. 在 Python 3 上使用'' != b''——不要盲目地复制粘贴代码——想想它的作用和工作原理.
  • 我建议在破解之前添加sys.stdout.flush(),否则会混淆。
【解决方案2】:

派对迟到了,但很惊讶没有看到我认为最简单的解决方案:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(这需要 Python 3。)

【讨论】:

  • 我想使用这个答案,但我得到:AttributeError: 'file' object has no attribute 'readable'py2.7
  • 适用于 python 3
  • @sorin 这些东西都没有使它“无效”。如果您正在编写仍需要支持 Python 2 的库,请不要使用此代码。但许多人拥有能够使用十年前发布的软件的奢侈。如果您尝试读取已关闭的文件,无论您是否使用TextIOWrapper,都会收到该异常。您可以简单地处理异常。
  • 你可能迟到了,但你的回答是最新版本的 Python,ty
  • @Ammad \n 是换行符。在 Python 中,按行拆分时不会删除换行符是常规的 -​​ 如果您遍历文件的行或使用 readlines() 方法,您将看到相同的行为。你可以用line[:-1] 得到没有它的行(TextIOWrapper 默认在“通用换行符”模式下运行,所以即使你在 Windows 上并且行以\r\n 结尾,你也只会有\n at结束,所以-1 有效)。如果您不介意行尾的任何其他类似空格的字符也被删除,您也可以使用line.rstrip()
【解决方案3】:

确实,如果您整理出迭代器,那么缓冲现在可能是您的问题。你可以告诉子进程中的python不要缓冲它的输出。

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

变成

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

我在从 python 中调用 python 时需要这个。

【讨论】:

    【解决方案4】:

    您想将这些额外参数传递给subprocess.Popen

    bufsize=1, universal_newlines=True
    

    然后你可以像你的例子一样迭代。 (使用 Python 3.5 测试)

    【讨论】:

    • @nicoulaj 如果使用 subprocess32 包,它应该可以工作。
    【解决方案5】:

    允许同时实时逐行迭代stdoutstderr 的函数

    如果您需要同时获取stdoutstderr 的输出流,可以使用以下函数。

    该函数使用队列将两个 Popen 管道合并为一个迭代器。

    这里我们创建函数read_popen_pipes()

    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()
                except Empty:
                    pass
                try:
                    err_line = q_stderr.get_nowait()
                except Empty:
                    pass
    
                yield (out_line, err_line)
    

    read_popen_pipes() 正在使用中:

    import subprocess as sp
    
    
    with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:
    
        for out_line, err_line in read_popen_pipes(p):
    
            # Do stuff with each line, e.g.:
            print(out_line, end='')
            print(err_line, end='')
    
        return p.poll() # return status-code
    

    【讨论】:

      【解决方案6】:

      您还可以阅读不带循环的行。在python3.6中工作。

      import os
      import subprocess
      
      process = subprocess.Popen(command, stdout=subprocess.PIPE)
      list_of_byte_strings = process.stdout.readlines()
      

      【讨论】:

      • 或者转成字符串:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
      • @ndtreviv,您可以将 text=True 传递给 Popen,如果您希望输出为字符串,则可以使用其“编码”kwarg,无需自己转换
      【解决方案7】:

      我用 python3 试过了,它成功了,source

      def output_reader(proc):
          for line in iter(proc.stdout.readline, b''):
              print('got line: {0}'.format(line.decode('utf-8')), end='')
      
      
      def main():
          proc = subprocess.Popen(['python', 'fake_utility.py'],
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.STDOUT)
      
          t = threading.Thread(target=output_reader, args=(proc,))
          t.start()
      
          try:
              time.sleep(0.2)
              import time
              i = 0
      
              while True:
              print (hex(i)*512)
              i += 1
              time.sleep(0.5)
          finally:
              proc.terminate()
              try:
                  proc.wait(timeout=0.2)
                  print('== subprocess exited with rc =', proc.returncode)
              except subprocess.TimeoutExpired:
                  print('subprocess did not terminate in time')
          t.join()
      

      【讨论】:

        【解决方案8】:

        Rômulo 的答案的以下修改适用于 Python 2 和 3(2.7.12 和 3.6.1):

        import os
        import subprocess
        
        process = subprocess.Popen(command, stdout=subprocess.PIPE)
        while True:
          line = process.stdout.readline()
          if line != '':
            os.write(1, line)
          else:
            break
        

        【讨论】:

          【解决方案9】:

          Pythont 3.5 将 run()call() 方法添加到 subprocess 模块,两者都返回 CompletedProcess 对象。有了这个你就可以使用proc.stdout.splitlines()

          proc = subprocess.run( comman, shell=True, capture_output=True, text=True, check=True )
          for line in proc.stdout.splitlines():
             print "stdout:", line
          

          另见How to Execute Shell Commands in Python Using the Subprocess Run Method

          【讨论】:

          • 此解决方案简短有效。与原始问题相比,一个问题是:它不会在“收到时”打印每一行,我认为这意味着实时打印消息,就像直接在命令行中运行进程一样。相反,它只在进程完成运行后打印输出
          猜你喜欢
          • 2020-12-24
          • 2015-01-20
          • 1970-01-01
          • 2011-11-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多