我正在尝试编写一个脚本,如果有人按下CTRL+ C(键盘中断),那么它不应该退出整个脚本,而只退出当前执行函数。
有人能建议我在这里使用什么逻辑吗?以下是一些示例代码
#!/bin/bash
func1()
{
wget https://example.com/test1.txt
cat text1.txt |awk '{print $3}'|tr -d '.' > temp1.txt
}
func2()
{
cat temp1.txt|<stdin-to-some-tool> > temp2.txt
}
func3()
{
<some-tool> temp2.txt > temp3.txt
}
同样,如果我们在此之前停止该功能,其他功能仍应执行。
答案1
分析
当您按下Ctrl+时c,终端的线路规程会发送SIGINT
到前台进程组中的进程。
当禁用作业控制时,Bash 会将与进程bash
本身相同的进程组中的所有内容运行起来。当 Bash 解释脚本时,默认情况下会禁用作业控制。这意味着,如果脚本在前台运行,则解释脚本的 shell 以及脚本调用的所有内容将属于前台进程组1,并将SIGINT
在Ctrl+时接收c。
您可以通过 按需启用作业控制set -m
。启用作业控制后,Bash 会在单独的进程组中运行命令,并且Ctrl+c获取当前作为前台进程组的组。
这引出了解决问题的两种基本方法。
解决trap
方案
此解决方案无需作业控制即可工作。Bash 解释脚本时默认禁用作业控制,因此无需显式配置 shell。
进程可能会忽略SIGINT
。如果忽略,则其子进程也会忽略SIGINT
2。即使解释脚本的 shell 不忽略SIGINT
,也可能让其某些子进程忽略它3。
您希望主 shell 忽略SIGINT
。您希望它执行以下操作:
trap '' INT
同时你希望你的函数不要忽略SIGINT
。你希望它们这样做:
trap - INT
foo () { … }
要正确执行此操作,您需要使用子 shell。使用 定义并作为 执行的函数foo
作为主 shell 的一部分运行,因此trap … INT
函数内部将影响主 shell。此类函数可以根据需要在子 shell 中运行(例如作为(foo)
或在管道中),然后其陷阱不会影响主 shell。使用 定义的函数foo () ( … )
将始终在子 shell 中运行。对于您想要执行的操作,最好以这种方式定义函数4。
概念证明:
#!/bin/bash
trap '' INT
foo () (
trap - INT
echo 'foo starts'
sleep 5
echo 'foo returns'
)
foo
echo 'main shell here'
foo
echo 'main shell exits'
Ctrl+c将中断sleep
并执行该功能(您将看不到foo returns
),但不会中断主 shell。
请注意,您直接从脚本主要部分(即不是从函数)运行的任何命令都将继承其忽略设置SIGINT
。要运行不忽略它的命令,您不一定需要一个函数,一个子 shell 就足够了。示例:
#!/bin/bash
trap '' INT
sleep 5 # immune to ctrl+c
echo 'try now'
(trap - INT; exec sleep 5) # susceptible to ctrl+c
类似地,你可以在对+免疫的脚本中,从易受+影响的函数内部运行对Ctrl+免疫的命令。这完全取决于你在哪个(子)shell 中调用了什么。cCtrlcCtrlctrap
作业控制解决方案
(已删除。不再有效。我认为它曾经有效。正在调查中。)
脚注
1除非某些过程故意离开小组。
2除非某些子进程故意选择不忽略。
3例如脚本忽略中启动的异步进程SIGINT
。这样,尽管它们位于前台进程组中,但它们也不会对Ctrl+做出反应。相比之下:在交互式 Bash 中(默认启用作业控制),任何在后台启动的进程(即使用 terminating )都不会对+做出反应,因为它一开始就没有得到响应。它没有得到响应,因为它位于一个单独的进程组中,而该进程组不是前台进程组。但是当你启动它时,它的组就变成了前台进程组。当在前台时,它可以对+做出反应。c&
CtrlcSIGINT
SIGINT
fg
Ctrlc
4但是,如果希望函数在其他方面影响主 shell(例如修改主 shell 的变量),则它不能在子 shell 中运行。这些要求可能会相互矛盾。
答案2
要强制脚本在发送 Ctrl+c 时停止,请在开头添加以下几行:
sigterm_handler() {
echo "Shutdown signal received."
exit 1
}
## Setup signal trap
trap 'trap " " SIGINT SIGTERM SIGHUP; kill 0; wait; sigterm_handler' SIGINT SIGTERM SIGHUP