Python subprocess readlines()挂起

2024-04-18 17:04:38 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图完成的任务是流化一个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!")

红宝石睡眠.rb

puts "hello"

sleep 2

puts "goodbye!"

问题

流式传输文件工作正常。hello/goodbye输出以2秒延迟打印。完全符合剧本的要求。问题是readline()挂在最后,永远不会退出。我从来没找到最后的指纹。

我知道这里有很多像这样的问题,但是没有这些问题让我解决了这个问题。我对整个子流程不感兴趣,所以请给我一个更实际的回答。

问候

编辑

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


Tags: 文件pathimportmasterdataosstdoutsleep
3条回答

不知道你的代码出了什么问题,但下面的代码似乎对我有用:

#!/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文件句柄上的输入从未关闭,因此,如果在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结合使用:

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

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

Using subprocess with select and pty hangs when capturing output

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

pty仅限Linux as said in the docs

Because pseudo-terminal handling is highly platform dependent, there is code to do it only for Linux. (The Linux code is supposed to work on other platforms, but hasn’t been tested yet.)

目前尚不清楚它在其他操作系统上的工作情况。

您可以尝试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()

或者基于@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)。


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

或者基于@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!")

相关问题 更多 >