接收 SIGUSR2 /SIGINT 时如何在 python 中获取子进程的标准输出

how to get stdout of subprocess in python when receving SIGUSR2 /SIGINT

我有以下简单的 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="" ; shift
    for sig ; do
        trap "$func $sig" "$sig"
    done
}

pid=False

function signalHandler() {

    trap - SIGINT SIGTERM

    echo "doStuff.sh chached sig: "
    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 的输出吗?如何正确执行此操作?

非常感谢!

你应该看看 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
    )

我只能用

等待进程
  os.kill(sub.pid, signal.SIGINT)
  os.waitpid(sub.pid,0)

而不是

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

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

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

您的代码无法获取 child 进程的标准输出,因为它在调用 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() 不挂在此处而是挂起而是接收到错误的 child 的退出状态)。这里是 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

示例显示 child 已退出,但 parent 仍为 运行。如果按Ctrl+C打断它;回溯显示它挂在 .wait() 调用中的 with _self._waitpid_lock: 语句上。如果在 subprocess.py 中将 self._waitpid_lock = threading.Lock() 替换为 self._waitpid_lock = threading.RLock() 那么效果与使用 os.waitpid() 相同——它不会挂起,但退出状态不正确。

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