简体   繁体   English

Python子进程readlines()挂起

[英]Python subprocess readlines() hangs

The task I try to accomplish is to stream a ruby file and print out the output. 我尝试完成的任务是流式传输ruby文件并打印出输出。 ( NOTE : I don't want to print out everything at once) 注意 :我不想一次打印出所有内容)

main.py 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_sleep.rb ruby_sleep.rb

puts "hello"

sleep 2

puts "goodbye!"

Problem 问题

Streaming the file works fine. 流文件工作正常。 The hello/goodbye output is printed with the 2 seconds delay. hello / goodbye输出以2秒延迟打印。 Exactly as the script should work. 正如脚本应该工作。 The problem is that readline() hangs in the end and never quits. 问题是readline()最后会挂起而永不退出。 I never reach the last print. 我从未到过最后一个印刷品。

I know there is a lot of questions like this here a stackoverflow but non of them made me solve the problem. 我知道有很多这样的问题,这里有一个stackoverflow但是没有它们让我解决问题。 I'm not that into the whole subprocess thing so please give me a more hands-on/concrete answer. 我不是那个整个子流程的东西,所以请给我一个更实际/具体的答案。

Regards 问候

edit 编辑

Fix unintended code. 修复意外的代码。 (nothing to do with the actual error) (与实际错误无关)

I assume you use pty due to reasons outlined in Q: Why not just use a pipe (popen())? 我假设您使用pty由于Q中概述的原因:为什么不使用管道(popen())? (all other answers so far ignore your "NOTE: I don't want to print out everything at once" ). (到目前为止所有其他答案都忽略了你的“注意:我不想一次打印出所有内容” )。

pty is Linux only as said in the docs : pty是Linux,仅在文档中说

Because pseudo-terminal handling is highly platform dependent, there is code to do it only for Linux. 因为伪终端处理是高度依赖于平台的,所以只有Linux代码才能执行。 (The Linux code is supposed to work on other platforms, but hasn't been tested yet.) (Linux代码应该在其他平台上运行,但尚未经过测试。)

It is unclear how well it works on other OSes. 目前还不清楚它在其他操作系统上的效果如何。

You could try pexpect : 你可以尝试pexpect

import sys
import pexpect

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

Or stdbuf to enable line-buffering in non-interactive mode: 或者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()

Or using pty from stdlib based on @Antti Haapala's answer : 或者根据@Antti Haapala的答案使用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!")

All three code examples print 'hello' immediately (as soon as the first EOL is seen). 所有三个代码示例立即打印'hello'(只要看到第一个EOL)。


leave the old more complicated code example here because it may be referenced and discussed in other posts on SO 在这里留下旧的更复杂的代码示例,因为它可能在SO上的其他帖子中被引用和讨论

Or using pty based on @Antti Haapala's answer : 或者根据@Antti Haapala的回答使用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!")

Not sure what is wrong with your code, but the following seems to work for me: 不确定您的代码有什么问题,但以下似乎对我有用:

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

Note that I don't have Ruby installed and hence cannot check with your actual problem. 请注意,我没有安装Ruby,因此无法检查您的实际问题。 Works fine with ls , though. 但是,与ls正常工作。

Basically what you are looking at here is a race condition between your proc.poll() and your readline() . 基本上你在这里看到的是proc.poll()和你的readline()之间的竞争条件。 Since the input on the master filehandle is never closed, if the process attempts to do a readline() on it after the ruby process has finished outputting, there will never be anything to read, but the pipe will never close. 由于master文件句柄上的输入永远不会关闭,如果进程在ruby进程完成输出后尝试对其执行readline() ,则永远不会有任何内容可读,但管道将永远不会关闭。 The code will only work if the shell process closes before your code tries another readline(). 只有在代码尝试另一个readline()之前shell进程关闭时,代码才有效。

Here is the timeline: 这是时间表:

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).

Easy fix is to just use the subprocess module as it suggests in the docs, not in conjunction with openpty: 简单的解决方法是只使用文档中建议的子进程模块,而不是与openpty结合使用:

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

Here is a very similar problem for further study: 这是一个非常类似的问题需要进一步研究:

Using subprocess with select and pty hangs when capturing output 捕获输出时,使用带有select和pty的子进程挂起

Try this: 尝试这个:

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!")

As others have noted, readline() will block when reading data. 正如其他人所说, readline()将在读取数据时阻止。 It will even do so when your child process has died. 当您的孩子进程死亡时,它甚至会这样做。 I am not sure why this does not happen when executing ls as in the other answer, but maybe the ruby interpreter detects that it is writing to a PIPE and therefore it will not close automatically. 我不确定为什么在执行ls时不会发生这种情况,如同在另一个答案中那样,但是ruby解释器可能检测到它正在写入PIPE,因此它不会自动关闭。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM