考虑以下两个C程序:
#include <signal.h>
int main(void) {
raise(SIGTERM);
}
int main(void) {
return 143;
}
如果我运行任何一个,则bash中$?
的值将为143。The wait
syscall可让您区分它们,
wait4(-1, [{WIFSIGNALED(s) && WTERMSIG(s) == SIGTERM}], 0, NULL) = 11148
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 143}], 0, NULL) = 11214
bash显然使用了这一知识,因为第一个导致Terminated
被打印到终端上(奇怪的是,即使我将stdout和stderr都重定向到其他地方,也会发生这种情况),而第二个则没有。如何区分这两种情况与bash脚本?
答案 0 :(得分:5)
我相信无法从纯bash / shell获取完整的退出代码。 Unix' StackExchange的答案非常全面。
所有shell之间的共同点是,如果进程正常终止,
$?
包含退出代码的最低8位(传递给exit()
的数字)。不同之处在于该过程由信号终止。在所有情况下,POSIX都需要该数字,该数字将大于128。POSIX没有指定该值是多少。但是实际上,在我所知道的所有类似Bourne的shell中,
$?
的最低7位将包含信号号。但是,n
是信号编号,
在ash,zsh,pdksh,bash,Bourne shell中,
$?
是128 + n
。这意味着在这些shell中,如果您获得$?
中的129
,则不知道是因为该进程以exit(129)
退出还是被信号杀死了1
(在大多数系统上为HUP
)。但是基本原理是,当shell退出自身时,它们默认情况下会返回最后退出的命令的退出状态。通过确保$?
不大于255,可以使出口状态保持一致:$ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"' bash: line 1: 16720 Terminated sh -c "kill \$\$" 8f # 128 + 15 $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?" bash: line 1: 16726 Terminated sh -c "kill \$\$" 8f # here that 0x8f is from a exit(143) done by bash. Though it's # not from a killed process, that does tell us that probably # something was killed by a SIGTERM
出于这个原因,我认为,您需要在bash外部运行命令以捕获退出代码。
通过某种抽象,有人问similar question是关于unbuffer的问题,这是一个用tcl编写的小脚本。更准确地说, unbuffer 将库libexpect与tcl / tk包装器一起使用。 从unbuffer的源中,我提取了相关代码以得出解决方法:
#!/bin/bash
expectStat() {
expect <(cat << EOT
set stty_init "-opost"
set timeout -1
eval [list spawn -noecho ] $@
expect
send_user "[wait]\n"
EOT
)
}
expectStat sleep 5 &
wait
如果sleep
正常退出,则返回大约以下行:
18383 exp4 0 0
如果睡眠在退出自身之前被杀死,则上述脚本将大致返回:
18383 exp4 0 0 CHILDKILLED SIGTERM {software termination signal}
如果脚本以exit 143
结尾,则该脚本将大致返回:
18383 exp4 0 143
这些字符串的含义可以从expect
的手册中提取。集成函数wait
返回上面的返回行。
前两个值是进程的pid和expect
的名称。
第四个是退出状态。如果出现单个信息,则会打印更多信息。第六个值是终止时发送到进程的信号。
等待
通常返回四个整数的列表。第一个整数是等待的进程的pid。第二个整数是相应的生成ID。如果发生操作系统错误,则第三个整数为-1,否则为0。如果第三个整数为0,则第四个整数是生成的进程返回的状态。如果第三个整数为-1,则第四个整数是操作系统设置的errno值。全局变量errorCode也已设置。
其他元素可能会出现,位于等待返回值的末尾。可选的第五个元素标识一类信息。当前,此元素唯一可能的值是CHILDKILLED,在这种情况下,接下来的两个值是C样式信号名称和简短的文本描述。
这意味着第四个值,如果存在,则第六个值是您要查找的值。存储整行并提取信号和退出代码,例如,使用以下代码:
RET=$(expectStat script.sh 1>&1)
# Filter status
EXITVALUE="$(echo "$RET" | cut -d' ' -f4)"
SIGNAL=$(echo "$RET" | cut -d' ' -f6)
#echo "Exit value: $EXITVALUE, Signal: $SIGNAL"
if [ -n "$SIGNAL" ]; then
echo "Likely killed by signal"
else
echo "$EXITVALUE"
fi
最终,此解决方法非常小巧。也许,还有另一个工具会带来自己的基于c的工具来获取信号的出现。
答案 1 :(得分:2)
wait
是一个系统调用,也是一个 bash builtin。
为了与 bash 区分这两种情况,在后台运行程序并使用内置的 wait
报告结果。
以下是非零退出代码和未捕获信号的示例。这些示例在子 bash shell 中使用 exit
和 kill
bash 内置函数,而不是您将运行程序的子 bash shell。
$ bash -c 'kill -s SIGTERM $$' & wait
[1] 36068
[1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
$ bash -c 'exit 143' & wait
[1] 36079
[1]+ Exit 143 bash -c 'exit 143'
$
至于为什么即使您重定向 stdout 和 stderr 也会看到 Terminated
打印到终端,原因是它是由 bash 打印的,而不是由程序打印的。
更新:
通过显式使用 wait
内置函数,您现在可以将其 stderr(带有程序的退出状态)重定向到一个单独的文件。
以下示例显示了三种类型的终止:正常退出 0、非零退出和未捕获信号。 wait
报告的结果存储在标有相应程序 PID 的文件中。
$ bash -c 'exit 0' & wait 2> exit_status_pid_$!
[1] 40279
$ bash -c 'exit 143' & wait 2> exit_status_pid_$!
[1] 40291
$ bash -c 'kill -s SIGTERM $$' & wait 2> exit_status_pid_$!
[1] 40303
$ for f in exit_status_pid*; do echo $f: $(cat $f); done
exit_status_pid_40279: [1]+ Done bash -c 'exit 0'
exit_status_pid_40291: [1]+ Exit 143 bash -c 'exit 143'
exit_status_pid_40303: [1]+ Terminated: 15 bash -c 'kill -s SIGTERM $$'
$
答案 2 :(得分:1)
这与 bash 相去甚远,但 bcc 提供了 exitsnoop
。使用
Debian Sid 上的描述示例:
root@vsid:~# apt install bpfcc-tools linux-headers-amd64
root@vsid:~# exitsnoop-bpfcc
PCOMM PID PPID TID AGE(s) EXIT_CODE
example1 1041 948 1041 0.00 signal 15 (TERM)
example2 1042 948 1042 0.00 code 143
^C
有关其他发行版,请参阅 install guide。
答案 3 :(得分:0)
Strace可以捕获大多数信号,但可能不适用于系统调用(例如kill -9),因此,如this article中所述:
Auditd是一个守护进程或服务,顾名思义,它会执行系统级活动的审核日志。它是从通常的存储库中作为审核程序包安装的,然后在/etc/audit/auditd.conf中进行配置,规则在/etc/audit/audit.rules中。
本文提供了“审计”输出的示例,可以帮助确定它是否对您有帮助:
通常的输出如下:
时间-> 2015年6月3日星期三16:34:08 类型= SYSCALL msg =审核(1433363648.091:6342):arch = c000003e syscall = 62成功=否退出= -3 a0 = 1e06 a1 = 0 a2 = 1e06 a3 = fffffffffffffff0项目= 0 ppid = 10044 pid = 10140 auid = 500 uid = 0 gid = 0 euid = 0 suid = 0 fsuid = 0 egid = 0 sgid = 0 fsgid = 0 tty = pts0 ses = 2 comm = 4174746163682041504920696E6974 exe =“ / opt / ibm / WebSphere / AppServer / java / jre / bin / java“ subj = unconfined_u:unconfined_r:unconfined_java_t:s0-s0:c0.c1023 key =” kill_signals“
这里也提到了系统点击,以及指南的redirection。