Python 子进程 readlines() 挂起
- 2024-12-11 08:47:00
- admin 原创
- 123
问题描述:
我尝试完成的任务是流式传输 ruby 文件并打印出输出。(注意:我不想一次打印出所有内容)
主程序
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() 最终挂起并且永不退出。我从未到达最后的打印。
我知道 stackoverflow 上有很多这样的问题,但没有一个能让我解决问题。我对整个子进程的事情不太感兴趣,所以请给我一个更实际/具体的答案。
问候
编辑
修复非预期代码。(与实际错误无关)
解决方案 1:
我假设您使用的pty
原因在Q 中概述:为什么不直接使用管道(popen())?(到目前为止,所有其他答案都忽略了您的“注意:我不想一次打印出所有内容”)。
pty
是否仅适用于 Linux,如文档中所述:
由于伪终端处理高度依赖于平台,因此只有 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()
pty
或者根据@Antti Haapala 的回答从 stdlib使用:
#!/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 上的其他帖子中被引用和讨论
pty
或者根据@Antti Haapala 的回答使用:
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!")
解决方案 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
但是,使用 可以正常工作。
解决方案 3:
proc.poll()
基本上,您在这里看到的是您和您的之间的竞争条件readline()
。由于文件句柄上的输入master
从未关闭,因此如果该进程readline()
在 ruby 进程完成输出后尝试对其进行操作,则永远不会有任何内容可读取,但管道永远不会关闭。仅当 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 结合使用:
http://docs.python.org/library/subprocess.html
这是一个非常相似的问题,可供进一步研究:
使用带有 select 和 pty 的子进程捕获输出时会挂起
解决方案 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,因此它不会自动关闭。