我有一个bash脚本进程,它在某个时刻同步执行一个长时间运行的子进程。在该子进程的运行过程中,会直接向bash脚本进程发送一个信号,请求终止脚本。是否有任何方法可以拦截该信号,终止子流程,然后退出bash流程?
显然,bash的信号处理从不中断同步调用?
我无法控制终止信号被发送到bash进程的事实。尽管如果信号可以传播到子进程,这也将解决我的问题。
提前感谢,Broes
请参阅bash的手册页,章节信号:
如果bash正在等待一个命令完成并接收到一个已设置陷阱的信号,则在命令完成之前,陷阱将不会执行。当bash通过wait内置等待异步命令时,接收到已设置陷阱的信号将导致wait内置立即返回,退出状态大于128,之后立即执行陷阱。
因此,异步运行外部程序并使用wait。用$杀死它!。
下面是我为处理此问题而编写的bash实用程序函数。事实证明,它既有用又稳健。我希望你觉得它有用。
# Run a command in a way that can be interrupted by a signal (eg SIGTERM)
#
# When bash receives a SIGTERM it normally simply exits. If it's executing a subprocess
# that subprocess isn't signaled. (Typically that's not a problem for interactive shells
# because the entire Process Group gets sent the signal.)
#
# When running a script it's sometimes useful for the script to propagate a SIGTERM
# to the command that was running. We can do that by using the trap builtin to catch
# the signal. But it's a little tricky, per the bash manual:
#
# If bash is waiting for a command to complete and receives a signal for
# which a trap has been set, the trap will not be executed until the
# command completes.
#
# so a script executing a long-running command with a signal trap set won't
# notice the signal until later. There's a way around that though...
#
# When bash is waiting for an asynchronous command via the wait builtin, the
# reception of a signal for which a trap has been set will cause the wait
# builtin to return immediately with an exit status greater than 128,
# immediately after which the trap is executed.
#
# Usage:
#
# interruptable [options] command [args]
#
# Options:
# --killall - put the child into a process group (via setsid)
# and send the SIGTERM to the process group
# --debug - print a message including pid of the child
#
# Usage examples:
#
# interruptable sleep 3600
#
# If not interrupted, the exit status of the specified command is returned.
# If interrupted, the specified command is sent a SIGTERM and the current
# shell exits with a status of 143.
interruptable() {
# handle options
local setsid=""
local debug=false
while true; do
case "${1:-}" in
--killall) setsid=setsid; shift ;;
--debug) debug=true; shift ;;
--*) echo "Invalid option: $1" 1>&2; exit 1;;
*) break;; # no more options
esac
done
# start the specified command
$setsid "$@" &
local child_pid=$!
# arrange to propagate a signal to the child process
trap '
exec 1>&2
set +e
trap "" SIGPIPE # ensure a possible sigpipe from the echo does not prevent the kill
echo "${BASH_SOURCE[0]} caught SIGTERM while executing $* (pid $child_pid), sending SIGTERM to it"
# (race) child may have exited in which case kill will report an error
# if setsid is used then prefix the pid with a "-" to indicate that the signal
# should be sent to the entire process group
kill ${setsid:+-}$child_pid
exit 143
' SIGTERM
# ensure that the trap doesn't persist after we return
trap 'trap - SIGTERM' RETURN
$debug && echo "interruptable wait (child $child_pid, self $$) for: $*"
# An error status from the child process will trigger an exception (via set -e)
# here unless the caller is checking the return status
wait $child_pid # last command, so status of waited for command is returned
}
是的,可以使用trap
命令截获信号。参见以下示例:
#!/bin/bash
function wrap {
local flag=0
trap "flag=1" SIGINT SIGTERM
xeyes &
subppid=$!
while :
do
if [ $flag -ne 0 ] ; then
kill $subppid
break
fi
sleep 1
done
}
flag=0
trap "flag=1" SIGINT SIGTERM
wrap &
wrappid=$!
while : # This is the same as "while true".
do
if [ $flag -ne 0 ] ; then
kill $wrappid
break
fi
sleep 1 # This script is not really doing anything.
done
echo 'end'
trap
的基本功能是执行"之间的命令。这里的主要函数在下面的while循环中。在每次迭代中,脚本都会检查是否设置了标志,如果没有,则会休眠一秒钟。在此之前,我们通过$!
记住了子进程的pid。捕获SIGINT
或SIGTERM
时,陷阱会发出命令(有关其他信号,请参阅kill
手册)。
包装器函数的作用与主函数相同。此外,它调用实际的subprocess
函数(在本例中,子流程为xeyes
)。当包装器函数接收到来自主函数的SIGTERM
信号时(主函数也捕捉到了其中一个信号),包装器函数可以在实际杀死子进程之前进行清理。之后,它从while循环中断并退出包装器函数。然后主功能也会中断并打印'end'
。
编辑:希望我能正确理解,你被迫执行xeyes &
。然后步骤如下(在终端中):
xeyes &
subpid=$!
trap "kill $subpid && exit " SIGINT SIGTERM
.... other stuff
.... more stuff
^C #TERMINATE - this firstly kills xeyes and then exits the terminal