阻止SIGINT关闭bash脚本中的子进程



我正在编写一个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

解释:

  • ffmpeg(此处为sleep(必须忽略SIGINT本身。要做到这一点,请使用bash -c启动它,重置处理程序,然后重置exec 这足以让孩子离开前台,以防止它接收SIGINT。

  • 在父级中,由于此处解释的原因,简单的wait将不起作用。(试试看。(在这种情况下,父级将在执行其SIGINT处理程序之后,但在子级完成之前继续。相反,我们使用循环并使用子pid进行等待。

  • 在子进程合法退出后,将在不存在的pid上再执行一个kill,我们将忽略该pid的stderr。

相关内容

  • 没有找到相关文章

最新更新