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

防止SIGINT关闭bash脚本中的子进程,bash,parent-child,child-process,sigint,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} ];

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

在此执行了一个简单的测试,它提供了预期的结果:

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
$
编辑(回答问题编辑):

您可能希望对脚本中的每一个其他命令(例如
(trap''SIGINT&&command)
)捕获并忽略
SIGINT
,以便在调用
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秒

但当您忽略睡眠的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

,因此该信号被发送到当前前台进程中的所有作业。所以防止信号传到孩子身上最简单的方法就是把它从前景中拿出来。只需通过执行以下操作来设置ffmpeg调用的背景:

...
ffmpeg -vsync passthrough -i ${cachedir}/${filename} -c:v libx265 -c:a copy -f matroska ${cachedir}/out/${filename} &
wait
...
请注意,这也为您提供了比尝试解析
ps
的输出更可靠的子级pid,因此您可能需要执行以下操作:

ffmpeg ... &
ffmpegPID=$!
wait
看看这个:

#!/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

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

  • 合法退出子级后,将在不存在的pid上执行另一个
    kill
    ,我们将忽略其stderr


请查看脚本。@JohnLeuenhagen如果您没有忽略SIGINT,而是使用自定义处理程序捕获它,请注意,当前命令将捕获信号,bash将进入处理程序,并且仅在当前命令退出时返回。如果您想处理SIGINT并同时允许在所有子进程上忽略它,则需要显式地为每个命令执行它,例如
(trap''SIGINT&&rm${cachedir}/*.mkv)
。另外,繁琐的
If[-e${cachedir}
序列可以简单地用
mkdir-p“$cachedir/out”替换。如果所有目录都存在,它什么也不做;如果缺少一个或多个路径组件,则会创建所有路径组件。还要注意大括号是如何完全多余的,但是双引号对于正确处理包含空格或许多其他有问题的字符的目录名是必要的。下面是另一个类似的问题:这允许运行处理程序并冻结进程,但似乎在处理程序完成后,SIGINT仍然可以传递到ffmpeg。