我正在编写一个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的脚本,以防您感到好奇)
答案 0 :(得分:1)
在这里执行一个简单的测试,它可以提供预期的结果:
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
。
答案 1 :(得分:1)
信号被发送到当前前台进程中的所有作业。因此,防止信号传递给孩子的最简单方法是将其从前台中取出。只需执行以下操作即可查看ffmpeg调用:
...
ffmpeg -vsync passthrough -i ${cachedir}/${filename} -c:v libx265 -c:a copy -f matroska ${cachedir}/out/${filename} &
wait
...
请注意,这也为您提供了更强大的pid,以便您尝试解析ps
的输出,因此您可能希望这样做:
ffmpeg ... &
ffmpegPID=$!
wait
答案 2 :(得分:0)
看看这个:
#!/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
。
在家长中,由于here解释的原因,简单的wait
不会这样做。 (尝试一下。)在这种情况下,父级将在执行其SIGINT处理程序之后但在子级完成之前继续。相反,我们使用循环并使用子pid等待。
在孩子合法退出后,将在一个不存在的pid上执行另外一个kill
,我们忽略了它的stderr。