script1.sh:
#!/bin/bash
./script2.sh
echo after-script
script2.sh:
#!/bin/bash
function handler {
exit 130
}
trap handler SIGINT
while true; do true; done
当我从终端启动script1.sh,然后使用Ctrl + C将SIGINT发送到其进程组时,该信号被script2.sh捕获,当script2.sh终止时,script1.sh打印“after-script” ”。但是,我原本希望script1.sh在调用script2.sh的行之后立即终止。为什么在这个例子中不是这种情况?
补充说明(编辑):
由于script1.sh和script2.sh位于同一进程组中,因此在命令行上按Ctrl + C时,SIGINT将被发送到两个脚本。这就是为什么我不希望script2.sh在script2.sh退出时继续。
当script2.sh中的“陷阱处理程序SIGINT”行被注释掉时,script1.sh会在script2.sh存在后立即退出。我想知道为什么它的行为不同,因为script2.sh只产生相同的退出代码(130)。
答案 0 :(得分:11)
新答案:
这个问题比我原先怀疑的要有趣得多。答案基本上在这里给出:
What happens to a SIGINT (^C) when sent to a perl script containing children?
这是相关的花絮。我意识到你没有使用Perl,但我认为Bash正在使用C的约定。
Perl的内置系统功能就像C系统一样(3) 就信号而言,从标准C库中起作用。 如果您使用的是Perl版本的system()或管道打开或反引号, 那么父母 - 一个呼叫系统而不是一个呼叫系统 它会在孩子们的时候忽略任何SIGINT和SIGQUIT 运行
This explanation是我见过的关于可以做出的各种选择的最好的。它还说Bash采用WCE方法。也就是说,当父进程收到SIGINT时,它会等待其子进程返回。如果处理的进程退出SIGINT,它也会退出SIGINT。如果孩子退出任何其他方式,它将忽略SIGINT。
还有一种方法是调用shell可以判断是否调用了 程序退出SIGINT并忽略SIGINT(或用于 其他目的)。就像在WUE方式中一样,shell会等待孩子 完成。它确定程序是否在SIGINT上结束,如果 所以,它停止了脚本。如果程序做了任何其他退出,那么 脚本将继续。我会称之为做事的方式 本文其余部分的“WCE”(用于“等待和合作退出”)。
我在Bash手册页中找不到对此的引用,但我会继续查看info文档。但我有99%的信心这是正确的答案。
旧回答:
Bash脚本中命令的非零退出状态不会终止程序。如果您在echo $?
后执行./script2.sh
,则会显示130.您可以使用set -e
作为phs建议终止脚本。
$ help set
...
-e Exit immediately if a command exits with a non-zero status.
答案 1 :(得分:2)
@ seanmcl更新后的答案的第二部分是正确的,http://www.cons.org/cracauer/sigint.html的链接非常适合仔细阅读。
从该链接“即使您查找系统的数值,也不能通过带有特殊数值的出口(3)'伪造'正确的退出状态”。事实上,这就是在@Hermann Speiche的script2.sh中尝试的内容。
一个答案是修改script2.sh中的函数处理程序,如下所示:
function handler {
# ... do stuff ...
trap INT
kill -2 $$
}
这有效地删除了信号处理程序并“重新”重新生成了SIGINT,导致bash进程退出并使用适当的标志,以便其父bash进程正确处理最初发送给它的SIGINT。这样,实际上不需要使用set -e
或任何其他黑客。
还值得注意的是,如果你有一个可执行文件在发送一个SIGINT时行为不正确(它不符合上面链接中的“如何成为一个正确的程序”,例如它以正常的返回码退出)解决这个问题的一种方法是使用如下脚本将调用包装到该进程:
#!/bin/bash
function handler {
trap INT
kill -2 $$
}
trap handler INT
badprocess "$@"
答案 2 :(得分:0)
原因是script1.sh
未终止,script2.sh
正在子shell中运行。要使前一个脚本退出,您可以按照phs和seanmcl的建议设置-e
,或强制script2.sh
在同一个shell中运行,然后说:
. ./script2.sh
在你的第一个脚本中。如果您在执行脚本之前执行set -x
,那么您所观察到的内容将会很明显。 help set
告诉:
-x Print commands and their arguments as they are executed.
答案 3 :(得分:0)
您还可以让第二个脚本通过SIGHUP在其父脚本上发送终止信号,或其他安全可用的信号,如SIGQUIT,其中父脚本也可以考虑或陷阱(发送SIGINT不起作用)。 / p>
script1.sh:
#!/bin/bash
trap 'exit 0' SIQUIT ## We could also just accept SIGHUP if we like without traps but that sends a message to the screen.
./script2.sh ## or "bash script.sh" or "( . ./script.sh; ) which would run it on another process
echo after-script
script2.sh:
#!/bin/bash
SLEEPPID=''
PID=$BASHPID
read PPID_ < <(exec ps -p "$PID" -o "$ppid=")
function handler {
[[ -n $SLEEPPID ]] && kill -s SIGTERM "$SLEEPPID" &>/dev/null
kill -s SIGQUIT "$PPID_"
exit 130
}
trap handler SIGINT
# better do some sleeping:
for (( ;; )); do
[[ -n $SLEEPPID ]] && kill -s 0 "$SLEEPPID" &>/dev/null || {
sleep 20 &
SLEEPPID=$!
}
wait
done
您脚本中的原始最后一行也可能就像这样,具体取决于您的脚本预期实现。
./script2.sh || exit
...
或者
./script2.sh
[[ $? -eq 130 ]] && exit
...
答案 4 :(得分:0)
正确的方法是通过setpgrp()。 shell的所有子代都应放在同一pgrp中。当tty驱动程序发出SIGINT信号时,它将被汇总传递给所有进程。在任何级别的外壳程序都应注意接收到的信号,等待孩子退出然后再次使用sigint进行信号处理,以杀死他们自己,以便退出代码正确。
此外,当SIGINT被其父进程设置为在启动时忽略时,他们应该忽略SIGINT。
作为逻辑的任何部分,外壳程序都不应该“检查子代是否退出并带有sigint”。外壳程序应该始终尊重直接接收到的信号,作为采取行动然后退出的理由。
在真正的UNIX时代,SIGINT只需一次击键就停止了Shell和所有子进程。退出外壳程序和继续运行子进程从来没有任何问题,除非它们本身已将SIGINT设置为忽略。
对于任何shell管道,它们应该是从右到左的管道创建的子进程关系。最正确的命令是外壳程序的直接子级,因为多数民众赞成在最后一个退出正常进程。在此之前的每个命令行是紧接在下一个管道符号或&&或||右侧的过程的子级。符号。 &&和||周围有明显的孩子群体。会自然脱落。
最后,进程组保持工作整洁,以便nohup正常工作,并且所有接收SIGINT或SIGQUIT或SIGHUP或其他tty驱动程序信号的子代也一样。