按下 ctrl+c 只会跳出当前函数,而不会跳出整个 bash 脚本

按下 ctrl+c 只会跳出当前函数,而不会跳出整个 bash 脚本

我正在尝试编写一个脚本,如果有人按下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,并将SIGINTCtrl+时接收c

您可以通过 按需启用作业控制set -m。启用作业控制后,Bash 会在单独的进程组中运行命令,并且Ctrl+c获取当前作为前台进程组的组。

这引出了解决问题的两种基本方法。


解决trap方案

此解决方案无需作业控制即可工作。Bash 解释脚本时默认禁用作业控制,因此无需显式配置 shell。

进程可能会忽略SIGINT。如果忽略,则其子进程也会忽略SIGINT2。即使解释脚本的 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&CtrlcSIGINTSIGINTfgCtrlc

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

相关内容