v3n*_*3nM 7 linux shell signals trap interrupt
我试图以这样一种方式处理 SIGINT/CTRL+C 中断,如果用户不小心按下了 ctrl-c,他会收到一条消息提示,“你想退出吗?(y/n)”。如果他输入 yes,则退出脚本。如果否,则从中断发生的地方继续。基本上,我需要 Ctrl-C 与 Ctrl-Z/SINTSTP 类似,但方式略有不同。我尝试了各种方法来实现这一点,但没有得到预期的结果。以下是我尝试过的几个场景。
情况1
脚本:play.sh
#!/bin/sh
function stop()
{
while true; do
read -rep $'\nDo you wish to stop playing?(y/n)' yn
case $yn in
[Yy]* ) echo "Thanks for playing !!!"; exit 1;;
[Nn]* ) break;;
* ) echo "Please answer (y/n)";;
esac
done
}
trap 'stop' SIGINT
echo "going to sleep"
for i in {1..100}
do
echo "$i"
sleep 3
done
echo "end of sleep"
Run Code Online (Sandbox Code Playgroud)
当我运行上面的脚本时,我得到了预期的结果。
输出:
$ play.sh
going to sleep
1
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$ play.sh
going to sleep
1
2
^C
Do you wish to stop playing?(y/n)n
3
4
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$
Run Code Online (Sandbox Code Playgroud)
案例:2 我将 for 循环移动到一个新的脚本 loop.sh,因此 play.sh 成为父进程,而 loop.sh 成为子进程。
脚本:play.sh
#!/bin/sh
function stop()
{
while true; do
read -rep $'\nDo you wish to stop playing?(y/n)' yn
case $yn in
[Yy]* ) echo "Thanks for playing !!!"; exit 1;;
[Nn]* ) break;;
* ) echo "Please answer (y/n)";;
esac
done
}
trap 'stop' SIGINT
loop.sh
Run Code Online (Sandbox Code Playgroud)
脚本:loop.sh
#!/bin/sh
echo "going to sleep"
for i in {1..100}
do
echo "$i"
sleep 3
done
echo "end of sleep"
Run Code Online (Sandbox Code Playgroud)
这种情况下的输出与预期不符。
输出:
$ play.sh
going to sleep
1
2
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$ play.sh
going to sleep
1
2
3
4
^C
Do you wish to stop playing?(y/n)n
$
Run Code Online (Sandbox Code Playgroud)
我知道当一个进程收到一个 SIGINT 信号时,它会将信号传播到所有子进程,因此我的第二种情况失败了。有什么方法可以避免将 SIGINT 传播到子进程,从而使 loop.sh 完全按照第一种情况的方式工作?
注意: 这只是我实际应用的一个例子。我正在开发的应用程序在 play.sh 和 loop.sh 中有几个子脚本。我应该确保接收 SIGINT 的应用程序不应终止,但应向用户提示一条消息。
用很好的例子来管理工作和信号的经典问题!我开发了一个精简的测试脚本,专注于信号处理的机制。
为此,在后台启动子进程 (loop.sh) 后,调用wait,并在收到 INT 信号后,调用killPGID 等于您的 PID 的进程组。
对于有问题的脚本play.sh,可以通过以下方式完成:
在stop()函数中替换exit 1为
kill -TERM -$$ # note the dash, negative PID, kills the process group
Run Code Online (Sandbox Code Playgroud)loop.sh作为后台进程启动(可以在此处启动多个后台进程并由 管理play.sh)
loop.sh &
Run Code Online (Sandbox Code Playgroud)wait在脚本末尾添加以等待所有子项。
wait
Run Code Online (Sandbox Code Playgroud)当您的脚本启动一个进程时,该子进程将成为进程组的成员,其 PGID 等于$$父 shell 中父进程的 PID 。
例如,脚本在后台trap.sh启动了三个sleep进程,现在正在wait运行它们,注意进程组 ID 列 (PGID) 与父进程的 PID 相同:
PID PGID STAT COMMAND
17121 17121 T sh trap.sh
17122 17121 T sleep 600
17123 17121 T sleep 600
17124 17121 T sleep 600
Run Code Online (Sandbox Code Playgroud)
在Unix和Linux,你可以通过调用发送信号到进程组的每个进程kill具有的负值PGID。如果你给出kill一个负数,它将被用作 -PGID。由于脚本的 PID ( $$) 与它的 PGID 相同,因此您可以kill在 shell 中使用
kill -TERM -$$ # note the dash before $$
Run Code Online (Sandbox Code Playgroud)
您必须提供信号编号或名称,否则某些 kill 实现会告诉您“非法选项”或“无效信号规范”。
下面的简单代码说明了所有这些。它设置一个trap信号处理程序,产生 3 个子kill进程,然后进入一个无限的等待循环,等待信号处理程序中的进程组命令杀死自己。
$ cat trap.sh
#!/bin/sh
signal_handler() {
echo
read -p 'Interrupt: ignore? (y/n) [Y] >' answer
case $answer in
[nN])
kill -TERM -$$ # negative PID, kill process group
;;
esac
}
trap signal_handler INT
for i in 1 2 3
do
sleep 600 &
done
wait # don't exit until process group is killed or all children die
Run Code Online (Sandbox Code Playgroud)
这是一个示例运行:
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17111 17111 R+ ps -o pid,pgid,stat,args
$
Run Code Online (Sandbox Code Playgroud)
OK 没有额外的进程在运行。启动测试脚本,中断它(^C),选择忽略中断,然后挂起它(^Z):
$ sh trap.sh
^C
Interrupt: ignore? (y/n) [Y] >y
^Z
[1]+ Stopped sh trap.sh
$
Run Code Online (Sandbox Code Playgroud)
检查正在运行的进程,注意进程组号 ( PGID):
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17121 17121 T sh trap.sh
17122 17121 T sleep 600
17123 17121 T sleep 600
17124 17121 T sleep 600
17143 17143 R+ ps -o pid,pgid,stat,args
$
Run Code Online (Sandbox Code Playgroud)
将我们的测试脚本带到前台(fg)并^C再次中断(),这次选择不忽略:
$ fg
sh trap.sh
^C
Interrupt: ignore? (y/n) [Y] >n
Terminated
$
Run Code Online (Sandbox Code Playgroud)
检查正在运行的进程,不再休眠:
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17159 17159 R+ ps -o pid,pgid,stat,args
$
Run Code Online (Sandbox Code Playgroud)
请注意您的外壳:
我不得不修改你的代码才能让它在我的系统上运行。您#!/bin/sh在脚本中使用了第一行,但脚本使用了 /bin/sh 中不可用的扩展名(来自 bash 或 zsh)。