接收SIGUSR2 / SIGINT时如何在python中获取子进程的stdout

时间:2016-02-17 14:52:04

标签: python python-3.x subprocess signals

我有以下简单的python脚本:

import os, subprocess,signal,sys
import time

out = None
sub = None

def handler(signum,frame):
    print("script.py: cached sig: %i " % signum)
    sys.stdout.flush()

    if sub is not None and not sub.poll():
        print("render.py: sent signal to prman pid: ", sub.pid)
        sys.stdout.flush()
        sub.send_signal(signal.SIGTERM)
        sub.wait() # deadlocks....????
        #os.kill(sub.pid, signal.SIGTERM)  # this works
        #os.waitpid(sub.pid,0)             # this works

    for i in range(0,5):
        time.sleep(0.1)
        print("script.py: cleanup %i" % i)
        sys.stdout.flush()

    sys.exit(128+signum)

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGUSR2, handler)
signal.signal(signal.SIGTERM, handler)

sub = subprocess.Popen(["./doStuff.sh"], stderr = subprocess.STDOUT)
sub.wait()


print("finished script.py")

doStuff.sh

#!/bin/bash

function trap_with_arg() {
    func="$1" ; shift
    for sig ; do
        trap "$func $sig" "$sig"
    done
}

pid=False

function signalHandler() {

    trap - SIGINT SIGTERM

    echo "doStuff.sh chached sig: $1"
    echo "doStuff.sh cleanup: wait 10s"
    sleep 10s

    # kill ourself to signal calling process we exited on SIGINT
    kill -s SIGINT $$

}

trap_with_arg signalHandler SIGINT SIGTERM
trap "echo 'doStuff.sh ignore SIGUSR2'" SIGUSR2 
# ignore SIGUSR2

echo "doStuff.sh : pid:  $$"
echo "doStuff.sh: some stub error" 1>&2
for i in {1..100}; do
    sleep 1s
    echo "doStuff.sh, rendering $i"
done

当我发送在终端上发起的进程时 python3 scripts.py & kill -USR2 -$!的信号 该脚本捕获SIGINT,并在sub.wait()中永远等待,ps -uf显示以下内容:。

user   27515  0.0  0.0  29892  8952 pts/22   S    21:56   0:00  \_ python script.py
user   27520  0.0  0.0      0     0 pts/22   Z    21:56   0:00      \_ [doStuff.sh] <defunct>

请注意doStuff.sh正确处理SIGINT并退出。

我还希望在调用handler时得到stdout的输出?如何正确地做到这一点?

非常感谢!

3 个答案:

答案 0 :(得分:1)

您的代码无法获得子流程&#39; stdout因为它在调用subprocess.Popen()时没有重定向其标准流。在信号处理程序中做任何事都为时已晚。

如果您想捕获标准输出,请传递stdout=subprocess.PIPE并致电.communicate()而不是.wait()

child = subprocess.Popen(command, stdout=subprocess.PIPE)
output = child.communicate()[0]

有一个完全独立的问题,信号处理程序在Python 3上的.wait()调用中挂起(Python 2或os.waitpid()不会挂起,但会收到错误的子项退出状态代替)。这是a minimal code example to reproduce the issue

#!/usr/bin/env python
import signal
import subprocess
import sys


def sighandler(*args):
    child.send_signal(signal.SIGINT)
    child.wait()  # It hangs on Python 3 due to child._waitpid_lock

signal.signal(signal.SIGUSR1, sighandler)
child = subprocess.Popen([sys.executable, 'child.py'])
sys.exit("From parent %d" % child.wait())  # return child's exit status

其中child.py

#!/usr/bin/env python
"""Called from parent.py"""
import sys
import time

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:  # handle SIGINT
    sys.exit('child exits on KeyboardInterrupt')

示例:

$ python3 parent.py &
$ kill -USR1 $!
child exits on KeyboardInterrupt
$ fg
... running    python3 parent.py

该示例显示孩子已退出但父母仍在运行。如果按 Ctrl + C 中断它;回溯显示它挂在with _self._waitpid_lock:调用内的.wait()语句中。如果self._waitpid_lock = threading.Lock()替换为self._waitpid_lock = threading.RLock()中的subprocess.py,则效果与使用os.waitpid()相同 - 它不会挂起,但退出状态不正确。< / p>

为了避免这个问题,不要在信号处理程序中等待子状态:调用send_signal(),设置一个简单的布尔标志,然后从hanlder返回。在主代码中,检查child.wait()之后的标志(问题代码中的print("finished script.py")之前),看看是否收到了信号(如果child.returncode不清楚) 。如果设置了标志;调用适当的清理代码并退出。

答案 1 :(得分:0)

您应该查看 subprocess.check_output

proc_output = subprocess.check_output(commands_list, stderr=subprocess.STDOUT)

你可以在尝试除了然后再包围它:

except subprocess.CalledProcessError, error:
    create_log = u"Creation Failed with return code {return_code}\n{proc_output}".format(
        return_code=error.returncode, proc_output=error.output
    )

答案 2 :(得分:0)

我只能使用

等待这个过程
  os.kill(sub.pid, signal.SIGINT)
  os.waitpid(sub.pid,0)

而不是

  sub.send_signal(signal.SIGINT)
  sub.wait() # blocks forever

这与UNIX上的进程组有关,我真的不明白:我认为进程./doStuff.sh没有收到信号,因为同一进程组中的子进程没有收到信号。 (我不确定这是否正确)。希望有人可能会再详细说明这个问题。

调用处理程序之前的输出被推送到调用bash(控制台)的stdout。