微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

subprocess.Popen with shell=True 挂在 'time <...>'

如何解决subprocess.Popen with shell=True 挂在 'time <...>'

我正在使用这段代码,它应该是python2到python3移植阶段的过渡(我知道run有第三方库,我想实现自己的来收集经验)

>
def run(args,stdin=None,input=None,stdout=None,stderr=None,capture_output=False,timeout=None,encoding=None,**popen_kwargs):        
    # create stderr and stdout pipes,if capture_output is true
    if capture_output:
        _stderr = subprocess.PIPE
        _stdout = subprocess.PIPE
    else:
        _stdout,_stderr = stdout,stderr

    # if input is given,create stdin as pipe,where input will be passed into
    if input is not None:
        _stdin = subprocess.PIPE
    else:
        _stdin = stdin

    # this starts the process. python2 did not have 'encoding'
    if sys.version_info.major >= 3:
        proc = subprocess.Popen(args,stdin=_stdin,stdout=_stdout,stderr=_stderr,encoding=encoding,**popen_kwargs)
    else:
        proc = subprocess.Popen(
            args,**popen_kwargs)

    # run a background timer to interrupt 'communicate',if necessary
    if timeout is not None:
        def cancel():
            try:
                proc.terminate()
            except OSError:
                # an exception here means that the process is gone already
                pass
        cancel_timer = Timer(timeout,cancel)
        cancel_timer.start()

    # special case for python2 for which we allow passing 'unicode' if an encoding is used
    if input is not None and sys.version_info.major < 3:
        if type(input) == unicode:
            import codecs
            input = codecs.encode(input,encoding)
    (stdoutoutput,stderroutput) = proc.communicate(input)

    # check timeout scenario
    if timeout is not None:
        if not cancel_timer.is_alive():
            raise TimeoutExpired(args,timeout,stdoutoutput,stderroutput)
        else:
            cancel_timer.cancel()
            cancel_timer.join()

    # on python2,outputs will always be 'str',which is fine with us,as it's the union of
    # str and bytes
    return CompletedProcess(args,proc.poll(),stderroutput)

但是,每当我尝试在 python2 和 python3 上使用 shell 内置 communicate 前缀和 time 执行任何操作时,代码都会在 capture_output 内无限期地阻塞。这一定是非常愚蠢的事情。

>>> run("time sleep 5m",shell=True,capture_output=True,timeout=1)
... (^ C to stop it)
>>> run("sleep 5m",timeout=1)
zsubprocess.TimeoutExpired: sleep 5m: Timeout after 1 seconds

我不明白为什么会这样。

解决方法

事实证明,当命令仅为 subprocess 时,sleep 5m 不会执行 shell,但仅当命令为 time sleep 5m 时(更准确地说: sh 优化了它的 -c 并且显然 exec 它而不是分叉)。

当我执行 proc.terminate() 时,它只终止了包装壳,而不是 sleep,它成为子子收割者(此处为 init)的子级。并且可能(我猜)communicate 等待管道产生 EOF 以收集未完成的数据,除非睡眠终止,否则这种情况不会发生。以下解决方案取自 https://stackoverflow.com/a/25134985/34509 。而不是proc.kill,做

process = psutil.Process(proc.pid)
if proc.poll() is None:
    for child in process.children(recursive=True):
        child.kill()
    process.kill()

我们可能会与进程竞争,它可以在我们杀死它们之前创建新进程,但至少现在简单的情况可以工作!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。