当我的shell脚本退出时,如何终止后台进程/作业?
要清理一些烂摊子,可以使用trap
。它可以提供的执行功能的列表,当一个特定的信号到达:
trap "echo hello" SIGINT
,但也可以使用,如果在shell退出执行的东西:
trap "killall background" EXIT
这是一个内置的,所以help trap
会给你信息(与bash一起工作)。如果你只是想杀死后台作业,你可以做
trap 'kill $(jobs -p)' EXIT
当心使用单一'
,以防止shell立即替换$()
。
因此脚本加载脚本。运行一个killall
(或任何在您的操作系统上可用的)命令,脚本完成后立即执行。
另一个选择是让脚本将自己设置为进程组组长,并在退出时在进程组中捕获killpg。
陷阱“杀死$(工种-p)” EXIT
我会做给约翰的回答和使用工作只有轻微的变化-PR给杀限制正在运行的进程,并添加几个信号到列表:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
工作-p并不适用于所有壳工作,如果调用一个子shell,可能除非它的输出重定向到一个文件,但不是一个烟斗。 (我假设它最初仅用于交互使用。)
什么如下:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
调用到“工作”需要符合Debian的仪表板外壳,从而未能更新当前的工作( “%%”)如果它丢失。
为了安全起见,我觉得它更好地定义清理功能和陷阱调用它:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
或完全避免功能:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
为什么?因为通过简单地使用trap 'kill $(jobs -pr)' [...]
,假设有将作为发送陷阱条件时运行的后台作业。当没有工作一个将看到以下(或类似)消息:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
因为jobs -pr
是空的 - 我结束了在“陷阱”(双关语意)。
trap "exit" INT TERM
trap "kill 0" EXIT
为什么要转变INT
和TERM
退出?因为两者都应触发kill 0
而不进入无限循环。
为什么在EXIT
上触发kill 0
?因为正常的脚本退出也应该触发kill 0
。
为什么kill 0
?因为嵌套的子壳也需要被杀死。这将取下the whole process tree。
@tokland's answer中描述的trap 'kill 0' SIGINT SIGTERM EXIT
解决方案非常好,但使用它时最新的Bash crashes with a segmantation fault。这是因为击,从V 4.3开始,允许陷阱递归,成为在此情况下无限:
- 壳过程临危
SIGINT
或SIGTERM
或EXIT
; - 信号被捕获,执行
kill 0
,发送SIGTERM
到组中的所有进程,包括shell本身; - 去1 :)
这可以通过周围进行加工手动取消注册陷阱:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
更花哨的方式,允许打印收到的信号和避免“终止” 消息:
#!/usr/bin/env bash
trap_with_arg() { # from https://*.com/a/2183063/804678
local func="$1"; shift
for sig in "[email protected]"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf '\n%s\n' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while ((++i)); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while ((++i)); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
UPD:加入迷你一个例子;改进了stop
函数以避免不必要的信号,并隐藏输出中的“Terminated:”消息。谢谢Trevor Boyd Smith的建议!
我做了@ tokland的答案与知识相结合的适应从http://veithen.github.io/2014/11/16/sigterm-propagation.html时,我注意到,如果我运行一个前台进程(不与&
转到后台)trap
不会触发:
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://*.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT
echo [email protected]
"[email protected]" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
例它的工作:
$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1] + 31568 suspended bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100
niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100
niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep
$ bg
[1] + 31568 continued bash killable-shell.sh sleep 100
$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1] + 31568 terminated bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
一个很好的版本,在Linux,BSD和MacOS X的第一部作品试图发送SIGTERM,如果没有成功,10秒后终止进程。
KillJobs() {
for job in $(jobs -p); do
kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
done
}
TrapQuit() {
# Whatever you need to clean here
KillJobs
}
trap TrapQuit EXIT
请注意,作业不包括大孩子的过程。
那么你怎么killall *孩子*只? (或者我错过了一些明显的东西) – elmarco 2008-12-11 20:51:01
killall杀死你的孩子,但不是你 – orip 2008-12-11 22:10:56
elmarco,更新回答 – 2008-12-12 16:33:21