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/Deskto...

问题描述:

我尝试完成的任务是流式传输 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,因此它不会自动关闭。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用