交互式sh
shell 会话:
$ sh
$ timeout 1 yes | sed -n s/a/b/p ; echo $?
Terminated
143
$
非交互式脚本通过sh -c
:
$ sh -c 'timeout 1 yes | sed -n s/a/b/p ; echo $?'
0
$
为什么这两个示例会产生不同的退出代码?
答案1
timeout
(timeout
至少 GNU)默认情况下尝试在新进程组中运行该命令,并在超时时使用 SIGTERM 终止该进程组。
这样,如果该命令生成更多进程,它们也会在超时时被终止。
~$ strace -fze '/[sg]etpg|exec|kill|exit' sh -c 'timeout 1 sleep 2 | sleep 3'
execve("/usr/bin/sh", ["sh", "-c", "timeout 1 sleep 2 | sleep 3"], 0x7fffe2349230 /* 69 vars */) = 0
strace: Process 316058 attached
strace: Process 316059 attached
[pid 316058] execve("/usr/bin/timeout", ["timeout", "1", "sleep", "2"], 0x561e384e43a8 /* 69 vars */) = 0
[pid 316059] execve("/usr/bin/sleep", ["sleep", "3"], 0x561e384e41c8 /* 69 vars */) = 0
[pid 316058] setpgid(0, 0) = 0
strace: Process 316060 attached
[pid 316060] execve("/usr/bin/sleep", ["sleep", "2"], 0x7ffc3ef29910 /* 69 vars */) = 0
[pid 316058] --- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_int=0, si_ptr=NULL} ---
[pid 316058] kill(316060, SIGTERM) = 0
[pid 316060] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=316058, si_uid=1000} ---
[pid 316058] kill(0, SIGTERM) = 0
[pid 316058] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=316058, si_uid=1000} ---
[pid 316060] +++ killed by SIGTERM +++
[pid 316058] kill(316060, SIGCONT <unfinished ...>
) = 0
[pid 316058] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=316060, si_uid=1000, si_status=SIGTERM, si_utime=0, si_stime=0} ---
[pid 316058] kill(0, SIGCONT) = 0
[pid 316058] --- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=316058, si_uid=1000} ---
[pid 316058] +++ exited with 124 +++
[pid 316057] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=316058, si_uid=1000, si_status=124, si_utime=0, si_stime=0} ---
[pid 316059] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=316059, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
请参阅timeout
执行 a setpgid(0, 0)
(与 相同setpgrp()
)来创建一个新的进程组,您会看到它正在执行kill(316060, SIGTERM)
Kill sleep 2
,而且还kill(0, SIGTERM)
杀死了它自己的进程组(它之前创建的),这也会杀死由它生成的进程sleep 2
(如果有)(在本例中没有) )。
现在,当在 shell 中启用作业控制时(例如交互或使用-m
/-o monitor
选项调用时),在:
timeout 1 sleep 2 | sleep 3
shell 已经启动timeout
并sleep 3
在一个新的进程组中,并且在大多数 shell 中,进程组领导者将是运行的进程组timeout
。
因此,当timeout
执行时setpgrp()
,与 相同setpgid(0, 0)
,它不会创建新的进程组,而只是成为空操作。因此,进程组仍将包含timeout
它生成的要运行的进程sleep 2
,并且sleep 3
该进程是由 shell 预先放置在那里的。
~$ strace -fze '/[sg]etpg|exec|kill|exit' sh -o monitor -c 'timeout 1 sleep 2 | sleep 3'
execve("/usr/bin/sh", ["sh", "-o", "monitor", "-c", "timeout 1 sleep 2 | sleep 3"], 0x7ffeb8e13640 /* 69 vars */) = 0
getpgrp() = 318590
setpgid(0, 318593) = 0
strace: Process 318594 attached
[pid 318593] setpgid(318594, 318594) = 0
[pid 318594] setpgid(0, 318594) = 0
strace: Process 318595 attached
[pid 318595] setpgid(0, 318594) = 0
[pid 318593] setpgid(318595, 318594) = 0
[pid 318595] execve("/usr/bin/sleep", ["sleep", "3"], 0x557fde7871c8 /* 69 vars */) = 0
[pid 318594] execve("/usr/bin/timeout", ["timeout", "1", "sleep", "2"], 0x557fde7873a8 /* 69 vars */) = 0
[pid 318594] setpgid(0, 0) = 0
strace: Process 318596 attached
[pid 318596] execve("/usr/bin/sleep", ["sleep", "2"], 0x7ffc60f34c60 /* 69 vars */) = 0
[pid 318594] --- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_int=0, si_ptr=NULL} ---
[pid 318594] kill(318596, SIGTERM) = 0
[pid 318596] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=318594, si_uid=1000} ---
[pid 318596] +++ killed by SIGTERM +++
[pid 318594] kill(0, SIGTERM <unfinished ...>
) = 0
[pid 318595] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=318594, si_uid=1000} ---
[pid 318594] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=318594, si_uid=1000} ---
[pid 318595] +++ killed by SIGTERM +++
[pid 318594] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=318596, si_uid=1000, si_status=SIGTERM, si_utime=0, si_stime=0} ---
[pid 318593] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=318595, si_uid=1000, si_status=SIGTERM, si_utime=0, si_stime=0} ---
Terminated
[pid 318594] kill(318596, SIGCONT) = 0
[pid 318594] kill(0, SIGCONT) = 0
[pid 318594] --- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=318594, si_uid=1000} ---
[pid 318594] +++ exited with 124 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=318594, si_uid=1000, si_status=124, si_utime=0, si_stime=0} ---
setpgid(0, 318590) = 0
+++ exited with 143 +++
这次,它kill(0, SIGTERM)
会终止正在运行的进程sleep 3
,因为它也在 318594 进程组中(由 shell 使用 放置在那里setpgid(318595, 318594)
)。
在:
sleep 2 | timeout 1 sleep 3 | sleep 4
你会发现管道持续了 4 秒,仅sleep 3
在 1 秒后被杀死,因为此时 shell 创建的进程组是由正在运行的进程引导的sleep 2
,因此timeout
能够为自己(及其子进程)创建一个新的进程组sleep 3
)这与 shell 为管道中的 3 个命令创建的进程组不同(因此,您会发现Ctrl+C或Ctrl+在运行Z时无法正常工作,因为未在前台进程组中运行)。timeout
timeout
当使用 运行时--foreground
,timeout
会跳过创建额外的进程组,并且不会执行kill(0, SIGTERM)
杀死自己的进程组的操作,因此行为更加一致,但意味着孙进程不会被杀死。
$ strace -fze '/[sg]etpg|ioctl|exec|kill|exit' sh -o monitor -c 'timeout --foreground 1 sh -c "sleep 2; exit" | sleep 3'
execve("/usr/bin/sh", ["sh", "-o", "monitor", "-c", "timeout --foreground 1 sh -c \"sl"...], 0x7ffe289c2910 /* 69 vars */) = 0
ioctl(10, TIOCGPGRP, [331754]) = 0
getpgrp() = 331754
setpgid(0, 331757) = 0
ioctl(10, TIOCSPGRP, [331757]) = 0
strace: Process 331758 attached
[pid 331757] setpgid(331758, 331758) = 0
[pid 331758] setpgid(0, 331758) = 0
[pid 331758] ioctl(10, TIOCSPGRP, [331758]) = 0
strace: Process 331759 attached
[pid 331757] setpgid(331759, 331758) = 0
[pid 331759] setpgid(0, 331758) = 0
[pid 331759] ioctl(10, TIOCSPGRP, [331758]) = 0
[pid 331759] execve("/usr/bin/sleep", ["sleep", "3"], 0x55dc52f76418 /* 69 vars */) = 0
[pid 331758] execve("/usr/bin/timeout", ["timeout", "--foreground", "1", "sh", "-c", "sleep 2; exit"], 0x55dc52f763a8 /* 69 vars */) = 0
strace: Process 331760 attached
[pid 331760] execve("/usr/bin/sh", ["sh", "-c", "sleep 2; exit"], 0x7fff756826c0 /* 69 vars */) = 0
strace: Process 331761 attached
[pid 331761] execve("/usr/bin/sleep", ["sleep", "2"], 0x557447790168 /* 69 vars */) = 0
[pid 331758] --- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_int=0, si_ptr=NULL} ---
[pid 331758] kill(331760, SIGTERM) = 0
[pid 331760] --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=331758, si_uid=1000} ---
[pid 331760] +++ killed by SIGTERM +++
[pid 331758] +++ exited with 124 +++
[pid 331757] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=331758, si_uid=1000, si_status=124, si_utime=0, si_stime=0} ---
[pid 331761] +++ exited with 0 +++
[pid 331759] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=331759, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
ioctl(10, TIOCSPGRP, [331757]) = 0
ioctl(10, TIOCSPGRP, [331754]) = 0
setpgid(0, 331754) = 0
+++ exited with 0 +++
sh
被杀了,但没有sleep 2
。
这也解释了为什么在终端的交互式 shell 中:
sh -c 'timeout 10 cat; exit'
或者:
sleep 10 | timeout 10 cat /dev/tty
cat
无法从终端读取。当它尝试时最终会被挂起,因为它位于新的进程组中,因此不再位于终端的前台进程组中。
同样,添加该--foreground
选项可以避免该问题。