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
$ 

이 두 예제에서 서로 다른 종료 코드가 생성되는 이유는 무엇입니까?

답변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 +++

새로운 프로세스 그룹을 생성하기 위해 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)새 프로세스 그룹을 생성하지 않고 아무 작업도 수행하지 않습니다. 따라서 프로세스 그룹에는 실행하기 위해 생성되고 셸에 의해 미리 배치된 프로세스가 계속 포함 됩니다 timeout.sleep 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 프로세스 그룹(shell을 사용하여 배치됨)에 있기 때문에 종료됩니다 setpgid(318595, 318594).

존재하다:

sleep 2 | timeout 1 sleep 3 | sleep 4

파이프는 4초 동안 지속되고 sleep 31초 후에만 종료됩니다. 왜냐하면 이 시점에서 쉘에 의해 생성된 프로세스 그룹은 실행 중인 프로세스에 의해 부트스트랩되고 sleep 2따라서 timeout자체적으로 새 프로세스를 생성할 수 있기 때문입니다. 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옵션을 추가하면 문제가 방지됩니다.

관련 정보