我正在编写一个bash脚本,在其中我编写了一个处理程序,以在用户按下Control+C时(通过使用trap interruptHandler SIGINT
(进行处理,但SIGINT被发送到bash脚本和当前正在运行的子进程,从而关闭子进程。我该如何防止这种情况发生?
编辑:这是剧本,不要过多批评我的技术。。
#!/bin/bash
trap "interruptHandler" SIGINT
inInterrupt=false;
quit=false;
if [ -z ${cachedir+x} ]; then cachedir=~/.cache/zlima12.encoding; fi
cachedir=$(realpath ${cachedir});
if [ ! -e ${cachedir} ]; then mkdir ${cachedir}; fi
if [ ! -e ${cachedir}/out ]; then mkdir ${cachedir}/out; fi
cleanCache ()
{
rm ${cachedir}/*.mkv;
rm ${cachedir}/out/*.mkv;
}
interruptHandler ()
{
if [ ${inInterrupt} != true ]; then
printf "BASHPID: ${BASHPID}";
inInterrupt=true;
ffmpegPID=$(pgrep -P ${BASHPID});
kill -s SIGTSTP ${ffmpegPID};
printf "nWould you like to quit now(1) or allow the current file to be encoded(2)? ";
read response;
if [ ${response} = "1" ]; then kill ${ffmpegPID}; cleanCache;
elif [ ${response} = "2" ]; then quit=true; kill -s SIGCONT ${ffmpegPID};
else printf "I'm not sure what you said... continuing execution.n"; kill -s SIGCONT ${ffmpegPID};
fi
inInterrupt=false;
fi
}
for param in "$@"; do
dir=$(realpath ${param});
if [ ! -e ${dir} ]; then
printf "Directory ${dir} doesn't seem to exist... Exiting...n"
exit 1;
elif [ -e ${dir}/new ]; then
printf "${dir}/new already exists! Proceed? (y/n) ";
read response;
if [ ${response} != y ]; then exit 1; fi
else
mkdir ${dir}/new;
fi
for file in ${dir}/*.mkv; do
filename="$(basename ${file})";
cp $file ${cachedir}/${filename};
ffmpeg -vsync passthrough -i ${cachedir}/${filename} -c:v libx265 -c:a copy -f matroska ${cachedir}/out/${filename};
rm ${cachedir}/${filename};
mv ${cachedir}/out/${filename} ${dir}/new/${filename};
if [ ${quit} = true ]; then exit 0; fi
done
done
(这是一个将matroska(mkv(文件编码为H.265的脚本,以防您好奇(
信号被发送到当前前台进程中的所有作业。因此,防止信号传到孩子身上的最简单方法是将其从前景中移除。只需在ffmpeg调用的后台执行以下操作:
...
ffmpeg -vsync passthrough -i ${cachedir}/${filename} -c:v libx265 -c:a copy -f matroska ${cachedir}/out/${filename} &
wait
...
请注意,这也为您提供了比尝试解析ps
的输出更稳健的子级pid,因此您可能需要执行以下操作:
ffmpeg ... &
ffmpegPID=$!
wait
在这里执行了一个简单的测试,它提供了预期的结果:
int.sh
内容:
#!/bin/bash
trap '' SIGINT
tail -f /var/log/syslog >& /dev/null
测试:
$ ./int.sh
^C^C
# ... SIGINT ignored (CTRL+C) ...
# ... Will send SIGTSTP with CTRL+Z ...
^Z
[1]+ Stopped ./int.sh
$ kill %1
$
[1]+ Terminated ./int.sh
$
编辑(回答问题编辑(:
您可能希望捕获并忽略每一个其他命令的SIGINT
,例如脚本中的(trap '' SIGINT && command)
,这样就可以在调用interruptHandler
之前防止从当前命令捕获信号。
正在发生的事情的一个简单例子:
#!/bin/bash
function intHandler() {
echo "If SIGINT was caught, this will be printed AFTER sleep exits."
}
trap intHandler SIGINT
sleep 5 # Sleep will exit as soon as SIGINT is caught
输出:
$ time ./int.sh
^C
# ... Right here, only 0.6 seconds have elapsed before the below message being printed ...
If SIGINT was caught, this will be printed AFTER sleep exits.
real 0m0.634s
user 0m0.004s
sys 0m0.000s
注意,由于SIGINT
被捕获,它只持续了0.6秒。
但当您忽略sleep
:的SIGINT时
function intHandler() {
echo "If SIGINT was caught, this will be printed AFTER sleep exits."
}
trap intHandler SIGINT
(trap '' SIGINT && sleep 5)
输出为:
$ time ./int.sh
^C
# ... Right here, 5 seconds have elapsed without any message ...
If SIGINT was caught, this will be printed AFTER sleep exits.
real 0m5.007s
user 0m0.000s
sys 0m0.000s
请注意,尽管SIGINT
已被脚本传递并捕获,但intHandler
仅会在当前sleep
退出时返回,还请注意,当前sleep
没有从父级捕获SIGINT
(它持续了整整5秒(,因为它运行的子shell(( ... )
(忽略了SIGINT
。
看看这个:
#!/bin/bash
echo $$
trap 'echo "got C-c"' SIGINT
#bash -c 'trap - SIGINT; echo $$; exec sleep 60' &
sleep 60 &
pid=$!
echo "$$: waiting on $pid"
while kill -0 $pid 2>/dev/null; do
wait $pid
done
echo done
解释:
子这足以让孩子离开前台,以防止它接收SIGINT。ffmpeg
(此处为sleep
(必须忽略SIGINT本身。要做到这一点,请使用bash -c
启动它,重置处理程序,然后重置exec
在父级中,由于此处解释的原因,简单的
wait
将不起作用。(试试看。(在这种情况下,父级将在执行其SIGINT处理程序之后,但在子级完成之前继续。相反,我们使用循环并使用子pid进行等待。在子进程合法退出后,将在不存在的pid上再执行一个
kill
,我们将忽略该pid的stderr。