结束目标:等待后台作业完成的BASH脚本不会在第一个Ctrl-c
中止;相反,它需要第二个Ctrl-c
退出。
我很清楚BASH-builtin trap
是如何工作的。你可以:
用它来完全忽略一个信号(例如,trap '' 2
)......或
使用它在允许信号原始函数发生之前执行任意命令(例如,trap cmd 2
,其中cmd
在父脚本因{SIGINT
而被中断之前运行1}})
所以问题归结为:
如何有效地结合 1 & 2 在一起,即阻止信号导致的最终结果( 1 - 例如,由于
SIGINT
而停止脚本取消)同时也使信号导致别的东西( 2 - 例如,增加一个计数器,检查计数器并有条件地打印警告或退出)。
更简单地说:
我怎样才能让信号完全做出其他事情;不只是在它完成任务之前插入一个工作。
这是一些示例代码,用于演示我的目标;但是,它当然不起作用 - 因为trap
只能从上面做 1 或 2 。
#!/bin/bash
declare -i number_of_times_trap_triggered
cleanup_bg_jobs() {
number_of_times_trap_triggered+=1
if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then
echo "There are background jobs still running"
echo "Hit Ctrl-c again to cancel all bg jobs & quit"
else
echo "Aborting background jobs"
for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done
fi
}
f() { sleep 5m; }
trap cleanup_bg_jobs 2
bg_jobs=
for job in 1 2 3; do
f &
bg_jobs+=" $!"
done
wait
因此,当您按Ctrl-c
一次时,这是您最终获得的输出。
[rsaw:~]$ ./zax
^CThere are background jobs still running
Hit Ctrl-c again to cancel all bg jobs & quit
[rsaw:~]$ ps axf|tail -6
24569 pts/3 S 0:00 /bin/bash ./zax
24572 pts/3 S 0:00 \_ sleep 5m
24570 pts/3 S 0:00 /bin/bash ./zax
24573 pts/3 S 0:00 \_ sleep 5m
24571 pts/3 S 0:00 /bin/bash ./zax
24574 pts/3 S 0:00 \_ sleep 5m
当然我可以修改它来清理第一个Ctrl-c
上的作业,但这不是我想要的。我想在触发第一个陷阱后停止BASH退出......直到第二次触发它为止。
PS:目标平台是Linux(我不太关心POSIX合规性)与BASH v4 +
答案 0 :(得分:4)
我做了类似here之类的事情,它主要归结为:
ATTEMPT=0
handle_close() {
if [ $ATTEMPT -eq 0 ]; then
ATTEMPT=1
echo "Shutdown."
else
echo "Already tried to shutdown. Killing."
exit 0
fi
}
trap handle_close SIGINT SIGTERM
您可以在处理程序中设置一个变量,以便下次捕获时再次检查。
答案 1 :(得分:3)
我的用例略有不同,并希望将解决方案保留在此处,因为Google引导我了解此主题。您可以继续运行命令,并允许用户使用一个CTRL+C
重新启动它,并以下列方式使用双CTRL+C
将其终止:
trap_ctrlC() {
echo "Press CTRL-C again to kill. Restarting in 2 second"
sleep 2 || exit 1
}
trap trap_ctrlC SIGINT SIGTERM
while true; do
... your stuff here ...
done
答案 2 :(得分:1)
一位同事(格雷加)刚刚给了我一个解决方案......我无法相信我没有先想到它。
“我的方法是...将它永久地搁置,可能永远,使用一个永远不会返回的函数或另一个东西(另一个等待?),以便第二个处理程序可以正常工作。“
为了记录,wait
在这里不起作用。 (递归。)但是,在原始代码的sleep
函数中添加cleanup_bg_jobs()
命令会处理它...但会导致孤立的进程。因此,我利用进程组来确保脚本的所有子项确实被杀死。后人的简化示例:
#!/bin/bash
declare -i count=
handle_interrupt() {
count+=1
if [[ ${count} -eq 1 ]]; then
echo "Background jobs still running"
echo "Hit Ctrl-c again to cancel all bg jobs & quit"
sleep 1h
else
echo "Aborting background jobs"
pkill --pgroup 0
fi
}
f() { tload &>/dev/null; }
trap handle_interrupt 2
for job in 1 2 3; do
f &
done
wait
答案 3 :(得分:0)
- 使用它在允许信号原始函数发生之前执行任意命令(例如,陷阱cmd 2,其中cmd在父脚本因SIGINT而中断之前运行)
醇>
上述的斜体部分不正确。陷阱处理程序运行而不是让SIGINT(或其他)中断进程。更准确地说:
trap "command" SIGINT
导致command
运行而不是(不是以及)默认操作因此,安装了SIGINT处理程序后,SIGINT不会中断整个脚本。但它确实中断了wait
命令。陷阱处理程序完成后,脚本将在wait
之后恢复,即它从结束时退出并正常退出。您可以通过添加一些调试代码来看到这一点:
echo Waiting
wait
echo Back from wait
exit 55 # Arbitrary value that wouldn't otherwise occur
此版本产生以下内容:
$ foo
Waiting
^CThere are background jobs still running
Hit Ctrl-c again to cancel all bg jobs & quit
back from wait
$ echo $?
55
$
您需要做的是在处理程序返回后重复wait
。这个版本:
#!/bin/bash
declare -i number_of_times_trap_triggered
cleanup_bg_jobs() {
number_of_times_trap_triggered+=1
if [[ ${number_of_times_trap_triggered} -eq 1 ]]; then
echo "There are background jobs still running"
echo "Hit Ctrl-c again to cancel all bg jobs & quit"
else
echo "Aborting background jobs"
for pid in ${bg_jobs}; do echo " Killing ${pid}"; kill -9 ${pid}; done
exit 1
fi
}
f() { sleep 5m; }
trap cleanup_bg_jobs 2
bg_jobs=
for job in 1 2 3; do
f &
bg_jobs+=" $!"
done
while [ 1 ]; do
echo Waiting
wait
echo Back from wait
done
按照您的要求执行:
$ ./foo
Waiting
^CThere are background jobs still running
Hit Ctrl-c again to cancel all bg jobs & quit
Back from wait
Waiting
^CAborting background jobs
Killing 24154
Killing 24155
Killing 24156
$
注意:
exit 1
。这是突破无限主循环的原因