timeoutコマンドを持つパイプの終了状態は、対話型シェルとシェルスクリプトで異なる動作をします。

timeoutコマンドを持つパイプの終了状態は、対話型シェルとシェルスクリプトで異なる動作をします。

インタラクティブshシェルセッション:

$ 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
$ 

この2つの例で異なる終了コードが生成されるのはなぜですか?

答え1

timeouttimeout少なくとも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 +++

新しいプロセスグループを作成するためにtimeouta setpgid(0, 0)(と同じ)を実行するを参照してください。 Killを実行すると同時に、独自のプロセスグループ(以前に作成した)も終了し、processによって生成されたグループも終了します(存在する場合)。この場合は何もありません)。setpgrp()kill(316060, SIGTERM)sleep 2kill(0, SIGTERM)sleep 2

これで、シェルでジョブ制御が有効になると(たとえば、対話的にまたは-m/-o monitorオプションを使用して呼び出される場合)、次のようになります。

timeout 1 sleep 2 | sleep 3

シェルが開始され、timeout新しいsleep 3プロセスグループに属し、ほとんどのシェルでは、プロセスグループリーダーは実行中のプロセスグループになりますtimeout

したがってtimeout、実行すると、setpgrp()同じプロセスと同様に、setpgid(0, 0)新しいプロセスグループを作成せず、何もしません。したがって、プロセスグループには実行用に作成され、シェルによって事前に配置されたプロセスが含まれますtimeoutsleep 2sleep 3

~$ 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 3318594プロセスグループ(シェルを使用して配置されている)にあるため終了しますsetpgid(318595, 318594)

存在する:

sleep 2 | timeout 1 sleep 3 | sleep 4

パイプは4秒間持続し、sleep 31秒後にのみ終了します。なぜなら、この時点でシェルによって生成されたプロセスグループは実行中のプロセスによってブートストラップされ、それ自体sleep 2timeout新しいプロセスを作成できるからです。 children)group)これは、パイプラインの3つのコマンドに対してシェルが生成するプロセスグループとは異なります(したがって、+または+がフォアグラウンドプロセスグループで実行されないため、実行時に正しく機能しないことがsleep 3わかります)。CtrlCCtrlZtimeouttimeout

ランタイムを使用すると、追加の--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

これは、端末の対話型シェルの理由も説明します。

sh -c 'timeout 10 cat; exit'

または:

sleep 10 | timeout 10 cat /dev/tty

cat端末から読み取れません。新しいプロセスグループにあるため、端末のフォアグラウンドプロセスグループに属さなくなったため、試みると停止します。

繰り返しますが、この--foregroundオプションを追加すると問題が回避されます。

関連情報