接收 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
中不清楚) .如果设置了标志;调用适当的清理代码并退出。
我有以下简单的 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
中不清楚) .如果设置了标志;调用适当的清理代码并退出。