31

我尝试完成的任务是流式传输 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 但没有一个让我解决了问题。我不是整个子流程的事情,所以请给我一个更实际/具体的答案。

问候

编辑

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

4

4 回答 4

34

我假设您使用Qpty中概述的原因:为什么不只使用管道 (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 的回答从标准库中使用:

#!/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!")
于 2012-09-18T06:58:53.747 回答
5

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

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

于 2012-09-17T14:23:30.250 回答
2

基本上你在这里看到的是你proc.poll()和你的readline(). 由于文件句柄上的输入永远不会关闭,如果进程在 ruby​​ 进程完成输出后master尝试对其执行操作,则将永远不会读取任何内容,但管道将永远不会关闭。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

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

捕获输出时使用带有 select 和 pty 的子进程挂起

于 2012-09-17T17:34:47.440 回答
1

尝试这个:

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,因此它不会自动关闭。

于 2012-09-17T17:52:39.497 回答