简体   繁体   English

当 stderr 被重定向到 subprocess.STDOUT 时 subprocess.stdout.readline() 挂起

[英]subprocess.stdout.readline() hangs when stderr is redirected to subprocess.STDOUT

I've been looking at other similar questions, but I don't think anyone has covered this particular case.我一直在研究其他类似的问题,但我认为没有人讨论过这个特殊情况。

I've found that using stderr=subprocess.STDOUT in my Popen call causes stdout.readline() to hang forever in rare cases;我发现在我的 Popen 调用中使用stderr= subprocess.STDOUT 会导致 stdout.readline() 在极少数情况下永远挂起; the best example I have is an attempt at an internal SSH command that sets up a tunnel:我最好的例子是尝试使用内部 SSH 命令设置隧道:

ssh -N -f -L 81:<SOME URL>:80 <SOME OTHER URL>

When I try to run this command in Popen with stderr redirected to stdout, it prints the 2 outputs and then hangs on stdout.readline() forever:当我尝试在 Popen 中运行此命令并将 stderr 重定向到 stdout 时,它会打印 2 个输出,然后永远挂在 stdout.readline() 上:

import subprocess as sp
cmd = "ssh -N -f -L 81:<SOME URL>:80 <SOME OTHER URL>"

p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT, shell=True, env=os.environ, encoding="utf-8")
while True:
    out = p.stdout.readline()
    if out:
        print(out)
    if p.poll() is not None:
        break

I'm redirecting stderr to stdout so that I can have both streams in one place.我正在将 stderr 重定向到 stdout,以便我可以将两个流放在一个地方。

Is there a way I can check if there's a line to read BEFORE trying to read it with readline()?有没有办法在尝试使用 readline() 读取之前检查是否有要读取的行?

Also, why doesn't readline() simply return "" if the process is finished?另外,如果进程完成,为什么 readline() 不简单地返回"" This seems like a huge miss.这似乎是一个巨大的失误。

To anyone finding this, I worked around this with a (not my own) clever use of signal .对于任何发现这一点的人,我通过(不是我自己的)巧妙地使用signal来解决这个问题。

All this does is set up a straight timeout so that when your code runs, you're only giving it N seconds before it has to complete, or you're killing it.所有这一切都是设置一个直接超时,这样当您的代码运行时,您只需在它必须完成之前给它 N 秒,或者您正在杀死它。

This Timeout code has been immensely useful in many places for me;这个超时代码对我来说在很多地方都非常有用; you can import signal and then straight copy/paste this in to your work:您可以import signal ,然后直接将其复制/粘贴到您的工作中:

# This stuff is so when we get SIGALRM from the timeout functionality we can handle it instead of
# crashing to the ground
class TimeOutError(Exception):
    pass
def raise_timeout(var1, var2):
    raise TimeOutError
signal.signal(signal.SIGALRM, raise_timeout)

Once you have this set, you're now free to set an alarm with signal.alarm(<SOME SECONDS>) and do except TimeOutError in your code;一旦你有了这个设置,你现在可以自由地用signal.alarm(<SOME SECONDS>)设置一个警报,并在你的代码中做except TimeOutError which is what I did for this problem:这就是我为这个问题所做的:

import subprocess as sp
import signal

class TimeOutError(Exception):
    pass
def raise_timeout(var1, var2):
    raise TimeOutError
signal.signal(signal.SIGALRM, raise_timeout)

cmd = "ssh -N -f -L 81:<SOME URL>:80 <SOME OTHER URL>"

p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT, shell=True, env=os.environ, encoding="utf-8")

while True:
    out = ""
    signal.alarm(1)
    try:
        out = p.stdout.readline()
    except TimeOutError as e:
        # Is the process complete?
        if p.poll() is not None:
            break
        else:
            pass

    # Turn the alarm back off
    signal.alarm(0)

    if out:
        print(out)
    if p.poll() is not None:
        break

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

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