Python子进程readlines()挂起

Python子进程readlines()挂起,python,subprocess,Python,Subprocess,我试图完成的任务是流式处理一个ruby文件并打印输出。(注意:我不想一次打印出所有内容) main.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 = P

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

main.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\u sleep.rb

puts "hello"

sleep 2

puts "goodbye!"
问题

流式传输文件效果良好。问候/再见输出以2秒延迟打印。正如脚本应该工作的那样。问题是readline()最终挂起,并且永不退出。我从来没有读过最后一页

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

问候

编辑


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

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

#!/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

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

以下是时间表:

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结合使用:

下面是一个非常类似的问题,需要进一步研究:

试试这个:

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解释器检测到它正在写入管道,因此它不会自动关闭。

我假设您使用
pty
,原因如下(到目前为止,所有其他答案都忽略您的答案)“注意:我不想一次打印出所有内容”)

pty
仅适用于Linux:

由于伪终端处理高度依赖于平台,因此 是只针对Linux的代码 在其他平台上,但尚未进行测试。)

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

您可以尝试
pexpect

import sys
import pexpect

pexpect.run("ruby ruby_sleep.rb", logfile=sys.stdout)
或在非交互模式下启用行缓冲:

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()
或者使用stdlib提供的
pty
,基于:

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


将旧的更复杂的代码示例留在这里,因为它可能会在其他帖子中引用和讨论

或基于以下情况使用
pty


不确定在将代码粘贴到问题中时是否有输入错误,或者问题是否是真的。在我看来,
if
应该缩进以使其位于循环中。感谢您的注意。这是我粘贴代码时的输入错误。现在已修复。抱歉。我不确定,但您的问题是否与中的问题非常相似?Howe无论如何,当不使用
pty
而使用
readline
时,代码也会挂起。但它在作为pty一部分的fd上使用readline。这就是readline挂起的原因。如果输出管道关闭,它将返回EOF,readline将返回““。事实上,输出管道仍然打开,但没有进程提供任何输入,因此它不提供任何输出。不,我重写了代码,以避免使用
pty
。它仍然挂在readline中。只有当我删除readline时,代码才起作用。最后一个链接为+1。不清楚您所说的“只使用文档中建议的子流程模块,而不是与openpty结合使用”是什么意思,但由于ruby端的块缓冲,它可能无法工作。请参阅,在子项由于块缓冲而死亡之前,此项不会显示
hello
.readline()
在您的案例中,当孩子死亡时,返回
'
(使用
iter(proc.stdout.readline,b'')
。使用
if len(line):
是对我有帮助的和平。如果行:在python3上不起作用,那么只使用
if-line:
是什么原因导致
if-not-data:break
?在下一次while迭代中,
proc.poll()不是None
会不会捕捉到这种情况?询问的原因是获得以下(相关)答案:@AndyHayden忽略最后一个代码示例。出于历史原因(请阅读代码前的注释)
p.poll()
不用于在新代码中中断循环(使用
while 1
循环)。相关@AndyHayden关于您链接的答案:避免多个PTY,请参阅@jfs:上一个代码示例除了更加复杂之外,还有什么问题吗?这似乎是一个很有用的寻址选项,因为我们需要类似于
select.select
的东西来区分
stdout
stderr
。上面的其他代码示例没有提供这种能力(我们需要这种能力,因为通常我们不知道从这两个代码中的哪一个开始读取)。
#!/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!")
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!")