Python 子进程 readlines() 挂起

Posted

技术标签:

【中文标题】Python 子进程 readlines() 挂起【英文标题】:Python subprocess readlines() hangs 【发布时间】:2012-09-07 07:18:38 【问题描述】:

我尝试完成的任务是流式传输 ruby​​ 文件并打印输出。 (注意:我不想一次打印所有内容)

ma​​in.py

from subprocess import Popen, PIPE, STDOUT

import pty
import os

file_path = '/Users/luciano/Desktop/ruby_sleep.rb'

command = ' '.join(["ruby", file_path])

master, slave = pty.openpty()
proc = Popen(command, bufsize=0, shell=True, stdout=slave, stderr=slave, close_fds=True)     
stdout = os.fdopen(master, 'r', 0)

while proc.poll() is None:
    data = stdout.readline()
    if data != "":
        print(data)
    else:
        break

print("This is never reached!")

ruby_sleep.rb

puts "hello"

sleep 2

puts "goodbye!"

问题

流式传输文件工作正常。 hello/goodbye 输出以 2 秒的延迟打印。就像脚本应该工作一样。问题是 readline() 最终挂起并且永远不会退出。我从来没有到达最后一张。

我知道这里有很多这样的问题 *** 但没有一个让我解决了问题。我对整个子流程的事情不太感兴趣,所以请给我一个更实际/具体的答案。

问候

编辑

修复意外代码。 (与实际错误无关)

【问题讨论】:

不确定在将代码粘贴到问题中时是否有错字,或者问题是否真实存在。在我看来,if 应该缩进,以便它位于循环内。 感谢您的关注。这是我粘贴代码时的错字。现在已经修好了。对此感到抱歉。 我不确定,但您的问题是不是和***.com/questions/8495794/… 中的问题非常相似? 【参考方案1】:

由于Q: Why not just use a pipe (popen())? 中列出的原因,我假设您使用pty(到目前为止,所有其他答案都忽略了您的“注意:我不想一次打印所有内容”)。

pty 仅适用于 Linux as said in the docs:

由于伪终端处理高度依赖于平台,因此 是仅适用于 Linux 的代码。 (Linux代码应该可以工作 在其他平台上,但尚未经过测试。)

尚不清楚它在其他操作系统上的效果如何。

你可以试试pexpect:

import sys
import pexpect

pexpect.run("ruby ruby_sleep.rb", logfile=sys.stdout)

stdbuf 在非交互模式下启用行缓冲:

from subprocess import Popen, PIPE, STDOUT

proc = Popen(['stdbuf', '-oL', 'ruby', 'ruby_sleep.rb'],
             bufsize=1, stdout=PIPE, stderr=STDOUT, close_fds=True)
for line in iter(proc.stdout.readline, b''):
    print line,
proc.stdout.close()
proc.wait()

或者使用基于@Antti Haapala's answer的stdlib中的pty

#!/usr/bin/env python
import errno
import os
import pty
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable
                                     # line-buffering on ruby's side
proc = Popen(['ruby', 'ruby_sleep.rb'],
             stdin=slave_fd, stdout=slave_fd, stderr=STDOUT, close_fds=True)
os.close(slave_fd)
try:
    while 1:
        try:
            data = os.read(master_fd, 512)
        except OSError as e:
            if e.errno != errno.EIO:
                raise
            break # EIO means EOF on some systems
        else:
            if not data: # EOF
                break
            print('got ' + repr(data))
finally:
    os.close(master_fd)
    if proc.poll() is None:
        proc.kill()
    proc.wait()
print("This is reached!")

所有三个代码示例都会立即打印“hello”(只要看到第一个 EOL)。


在此处保留旧的更复杂的代码示例,因为它可能会在 SO 的其他帖子中被引用和讨论

或者基于@Antti Haapala's answer使用pty

import os
import pty
import select
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable
                                     # line-buffering on ruby's side
proc = Popen(['ruby', 'ruby_sleep.rb'],
             stdout=slave_fd, stderr=STDOUT, close_fds=True)
timeout = .04 # seconds
while 1:
    ready, _, _ = select.select([master_fd], [], [], timeout)
    if ready:
        data = os.read(master_fd, 512)
        if not data:
            break
        print("got " + repr(data))
    elif proc.poll() is not None: # select timeout
        assert not select.select([master_fd], [], [], 0)[0] # detect race condition
        break # proc exited
os.close(slave_fd) # can't do it sooner: it leads to errno.EIO error
os.close(master_fd)
proc.wait()

print("This is reached!")

【讨论】:

这里出现if not data: break 的原因是什么?这种情况不会被proc.poll() is not None 在下一次迭代中捕获吗? 之所以问这个(相关)答案:***.com/a/52954716/1240268 @AndyHayden 忽略最后一个代码示例。出于历史原因(请阅读代码前的注释)。 p.poll() 不用于打破新代码中的循环(使用while 1 循环)。相关Python subprocess .check_call vs .check_output @AndyHayden 关于您链接的答案:避免使用多个 pty,请参阅 caveats and the link at the end of the answer which uses 2 ptys @jfs: 除了更复杂之外,最后一个代码示例有什么问题吗?这似乎是解决Andy Hayden's problem 的有用选项,因为我们需要像select.select 这样的东西来区分stdoutstderr。上面的其他代码示例没有提供这种能力(我们需要这种能力,因为通常我们不知道从哪一个开始读取)。【参考方案2】:

不确定您的代码有什么问题,但以下似乎对我有用:

#!/usr/bin/python

from subprocess import Popen, PIPE
import threading

p = Popen('ls', stdout=PIPE)

class ReaderThread(threading.Thread):

    def __init__(self, stream):
        threading.Thread.__init__(self)
        self.stream = stream

    def run(self):
        while True:
            line = self.stream.readline()
            if len(line) == 0:
                break
            print line,


reader = ReaderThread(p.stdout)
reader.start()

# Wait until subprocess is done
p.wait()

# Wait until we've processed all output
reader.join()

print "Done!"

请注意,我没有安装 Ruby,因此无法检查您的实际问题。不过,ls 可以正常工作。

【讨论】:

使用if len(line): 是一种对我有帮助的平静。仅使用 if line: 在 python3 上不起作用。【参考方案3】:

基本上你在这里看到的是你的proc.poll() 和你的readline() 之间的竞争条件。由于master 文件句柄上的输入永远不会关闭,如果进程在 ruby​​ 进程完成输出后尝试对其执行readline(),则将永远不会读取任何内容,但管道永远不会关闭。只有在您的代码尝试另一个 readline() 之前关闭 shell 进程,该代码才会起作用。

这是时间线:

readline()
print-output
poll()
readline()
print-output (last line of real output)
poll() (returns false since process is not done)
readline() (waits for more output)
(process is done, but output pipe still open and no poll ever happens for it).

简单的解决方法是只使用文档中建议的子进程模块,而不是与 openpty 结合使用:

http://docs.python.org/library/subprocess.html

这是一个非常相似的问题,需要进一步研究:

Using subprocess with select and pty hangs when capturing output

【讨论】:

但是,当不使用pty,而是使用readline时,代码也会挂起。 但它在作为 pty 一部分的 fd 上使用 readline。这就是 readline 挂起的原因。如果输出管道关闭,它将返回 EOF,而 readline 将返回“”。因为它的输出管道仍然打开,但是没有进程提供任何输入,所以它没有提供任何输出。 不,我重写了代码,以免使用pty。它仍然挂在readline中。只有当我删除 readline 时,代码才起作用。 +1 表示最后一个链接。 “只使用文档中建议的子进程模块,而不是与 openpty 一起使用” 不清楚您的意思,但由于 ruby​​ 方面的块缓冲,它可能无法工作。见my answer【参考方案4】:

试试这个:

proc = Popen(command, bufsize=0, shell=True, stdout=PIPE, close_fds=True)
for line in proc.stdout:
    print line

print("This is most certainly reached!")

正如其他人所指出的,readline() 在读取数据时会阻塞。当您的子进程死亡时,它甚至会这样做。我不确定为什么在执行 ls 时不会发生这种情况,就像在另一个答案中一样,但也许 ruby​​ 解释器检测到它正在写入 PIPE,因此它不会自动关闭。

【讨论】:

这不会在孩子因块缓冲而死亡之前显示hello。在您的情况下,.readline() 在孩子死后返回''(尝试使用iter(proc.stdout.readline, b''))。

以上是关于Python 子进程 readlines() 挂起的主要内容,如果未能解决你的问题,请参考以下文章

Python 子进程挂起命名管道

通过 python 子进程 SSH 后终端挂起

Python C 程序子进程在“for line in iter”处挂起

Python C 程序子进程在“for line in iter”处挂起

子进程 readline 挂起等待 EOF

子进程失效,`communicate()` 挂起