我问过Bash trap - exit only at the end of loop并且提交的解决方案有效,但在按下CTRL-C时,脚本中的运行命令(mp3convert with lame)将被中断,而完整的for循环将运行到最后。让我告诉你一个简单的脚本:
#!/bin/bash
mp3convert () { lame -V0 file.wav file.mp3 }
PreTrap() { QUIT=1 }
CleanUp() {
if [ ! -z $QUIT ]; then
rm -f $TMPFILE1
rm -f $TMPFILE2
echo "... done!" && exit
fi }
trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT
case $1 in
write)
while [ -n "$line" ]
do
mp3convert
[SOMEMOREMAGIC]
CleanUp
done
;;
QUIT=1
如果我在运行mp3convert函数时按下CTRL-C,则lame命令将被中断,然后[SOMEMOREMAGIC]将在CleanUp运行之前执行。我不明白为什么跛脚命令会被打断以及如何避免它们。
答案 0 :(得分:2)
尝试简化上面的讨论,我在下面结束了一个更容易理解的show-case脚本版本。这个脚本还处理“双控制-C问题”:
(Double control-C问题:如果你按下控制C两次,或三次,取决于你使用了多少wait $PID
,那些清理工作就无法正常完成。)
#!/bin/bash
mp3convert () {
echo "mp3convert..."; sleep 5; echo "mp3convert done..."
}
PreTrap() {
echo "in trap"
QUIT=1
echo "exiting trap..."
}
CleanUp() {
### Since 'wait $PID' can be interrupted by ^C, we need to protected it
### by the 'kill' loop ==> double/triple control-C problem.
while kill -0 $PID >& /dev/null; do wait $PID; echo "check again"; done
### This won't work (A simple wait $PID is vulnerable to double control C)
# wait $PID
if [ ! -z $QUIT ]; then
echo "clean up..."
exit
fi
}
trap PreTrap SIGINT SIGTERM SIGTSTP
#trap CleanUp EXIT
for loop in 1 2 3; do
(
echo "loop #$loop"
mp3convert
echo magic 1
echo magic 2
echo magic 3
) &
PID=$!
CleanUp
echo "done loop #$loop"
done
可以在this link
的评论中找到kill -0
技巧
答案 1 :(得分:1)
这样做的一种方法是在程序完成之前简单地禁用中断。 一些伪代码如下:
#!/bin/bash
# First, store your stty settings and disable the interrupt
STTY=$(stty -g)
stty intr undef
#run your program here
runMp3Convert()
#restore stty settings
stty ${STTY}
# eof
另一个想法是在后台运行你的bash脚本(如果可能的话)。
mp3convert.sh &
甚至,
nohup mp3convert.sh &
答案 2 :(得分:1)
当您在终端中按Ctrl-C时,SIGINT将被发送到该终端的前台进程组中的所有进程,如此Stack Exchange“Unix& Linux”中所述:How Ctrl C works。 (该主题中的其他答案也值得一读)。这就是为什么即使你设置了一个SIGINT陷阱,你的mp3convert函数也会被中断。
但是你可以通过在后台运行mp3convert函数来解决这个问题,就像mattias提到的那样。以下是演示该技术的脚本的变体。
#!/usr/bin/env bash
myfunc()
{
echo -n "Starting $1 :"
for i in {1..7}
do
echo -n " $i"
sleep 1
done
echo ". Finished $1"
}
PreTrap() { QUIT=1; echo -n " in trap "; }
CleanUp() {
#Don't start cleanup until current run of myfunc is completed.
wait $pid
[[ -n $QUIT ]] &&
{
QUIT=''
echo "Cleaning up"
sleep 1
echo "... done!" && exit
}
}
trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT
for i in {a..e}
do
#Run myfunc in background but wait until it completes.
myfunc "$i" &
pid=$!
wait $pid
CleanUp
done
QUIT=1
当myfunc
处于运行过程中时按Ctrl-C,PreTrap
将打印其消息并设置QUIT标志,但myfunc
继续运行CleanUp
在当前的myfunc
运行完成之前不会开始。
请注意,我的CleanUp
版本会重置QUIT标志。这可以防止CleanUp
运行两次。
此版本从主循环中删除CleanUp
调用,并将其置于PreTrap
函数内。它在wait
中使用PreTrap
而没有ID参数,这意味着我们不需要费心保存每个子进程的PID。这应该没问题,因为如果我们在陷阱中,我们做想要在继续之前等待所有子进程完成。
#!/bin/bash
# Yet another Trap demo...
myfunc()
{
echo -n "Starting $1 :"
for i in {1..5}
do
echo -n " $i"
sleep 1
done
echo ". Finished $1"
}
PreTrap() { echo -n " in trap "; wait; CleanUp; }
CleanUp() {
[[ -n $CLEAN ]] && { echo bye; exit; }
echo "Cleaning up"
sleep 1
echo "... done!"
CLEAN=1
exit
}
trap PreTrap SIGINT SIGTERM SIGTSTP
trap "echo exittrap; CleanUp" EXIT
for i in {a..c}
do
#Run myfunc in background but wait until it completes.
myfunc "$i" & wait $!
done
我们不需要在此脚本中执行myfunc "$i" & wait $!
,它可以进一步简化为myfunc "$i" & wait
。但通常最好等待一个特定的PID,以防万一其他进程在后台运行,我们不想要等待。
请注意,在CleanUp
本身运行时按Ctrl-C将中断当前前台进程(在此演示中可能为sleep
)。