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

时间:2016-08-06 02:52:31

标签: bash parent-child child-process sigint

我正在编写一个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的脚本,以防您感到好奇)

3 个答案:

答案 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

说明:

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

    < / LI>
  • 在家长中,由于here解释的原因,简单的wait不会这样做。 (尝试一下。)在这种情况下,父级将在执行其SIGINT处理程序之后但在子级完成之前继续。相反,我们使用循环并使用子pid等待。

  • 在孩子合法退出后,将在一个不存在的pid上执行另外一个kill,我们忽略了它的stderr。