当我按 Ctrl+C 时,如何在 shell 脚本中终止并等待后台进程完成?

当我按 Ctrl+C 时,如何在 shell 脚本中终止并等待后台进程完成?

我正在尝试设置一个 shell 脚本,以便它运行后台进程,当我使用Ctrlcshell 脚本时,它会杀死子进程,然后退出。

我能想到的最好的就是这个。看起来,它kill 0 -INT也在等待发生之前终止了脚本,因此 shell 脚本在子进程完成之前就终止了。

关于如何让这个 shell 脚本在发送后等待孩子们死亡,有什么想法吗INT

#!/bin/bash
trap 'killall' INT

killall() {
    echo "**** Shutting down... ****"
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever

答案1

你的kill命令是倒退的。

与许多 UNIX 命令一样,以减号开头的选项必须先出现在其他参数之前。

如果你写

kill -INT 0

它将 视为-INT一个选项,并发送到SIGINT00一个特殊数字,表示当前进程组中的所有进程)。

但如果你写

kill 0 -INT

它看到0, 决定没有更多选项,因此SIGTERM默认使用。并发送到当前进程组,就像您所做的一样

kill -TERM 0 -INT    

(它也会尝试发送SIGTERM-INT,这会导致语法错误,但它首先发送SIGTERM0,并且永远不会到达那么远。)

所以你的主脚本SIGTERM在运行waitand之前会得到 a echo DONE

添加

trap 'echo got SIGTERM' TERM

在顶部,就在之后

trap 'killall' INT

并再次运行来证明这一点。

正如 Stephane Chazelas 指出的那样,你有背景的孩子(process1等)将SIGINT默认忽略。

无论如何,我认为发送SIGTERM更有意义。

最后,我不确定是否kill -process group能保证先给孩子们。关闭时忽略信号可能是个好主意。

所以试试这个:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever

答案2

不幸的是,在后台启动的命令被 shell 设置为忽略 SIGINT,更糟糕的是,它们无法使用trap.否则,你所要做的就是

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

因为当您按 Ctrl-C 时,process1 和 process2 会收到 SIGINT,因为它们属于同一进程组(即终端的前台进程组)。

上面的代码将与 pdksh 和 zsh 一起使用,在这方面它们不符合 POSIX 标准。

对于其他 shell,您必须使用其他方法来恢复 SIGINT 的默认处理程序,例如:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

或者使用不同的信号,例如 SIGTERM。

答案3

如果您想要管理一些后台进程,为什么不使用 bash 作业控制功能呢?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs

答案4

所以我也对此进行了修改。在 Bash 中,您可以定义函数并用它们做一些有趣的事情。我和我的同事使用terminator,因此对于批处理执行,如果我们想查看输出,我们通常会生成一大堆终结器窗口(不如tmux选项卡优雅,但您可以使用更像 GUI 的界面哈哈)。

这是我提出的设置,以及您可以运行的示例:

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

运行它

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

编辑 @Maxim,刚刚看到你的建议,这使它变得简单多了!谢谢!

相关内容