%20%EC%95%88%EC%A0%95%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EC%A2%85%EB%A3%8C%ED%95%98%EB%8A%94%20%ED%95%A8%EC%88%98%EB%A5%BC%20%EC%9E%91%EC%84%B1%ED%95%98%EB%8A%94%20%EB%B0%A9%EB%B2%95%EC%9D%80%20%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C%3F.png)
아래 스크립트는 문제를 최소한으로(인위적으로) 설명한 것입니다.
## demo.sh
exitfn () {
printf -- 'exitfn PID: %d\n' "$$" >&2
exit 1
}
printf -- 'script PID: %d\n' "$$" >&2
exitfn | :
printf -- 'SHOULD NEVER SEE THIS (0)\n' >&2
exitfn
printf -- 'SHOULD NEVER SEE THIS (1)\n' >&2
이 예제 스크립트에서 는 exitfn
현재 프로세스를 종료해야 하는 작업을 수행하는 함수를 나타냅니다 1 .
불행하게도 이 구현은 exitfn
이 작업을 안정적으로 수행하지 못합니다.
이 스크립트를 실행하면 출력은 다음과 같습니다.
% bash ./demo.sh
script PID: 26731
exitfn PID: 26731
SHOULD NEVER SEE THIS (0)
exitfn PID: 26731
(물론 호출될 때마다 표시되는 PID 값이 달라집니다.)
여기서 중요한 점은 함수가 처음 exitfn
호출될 때 exit 1
해당 명령이 해당 본문에 포함된다는 것입니다.포함된 스크립트의 실행을 종료할 수 없습니다.printf
(즉시 실행된 첫 번째 명령에서 알 수 있듯이). 대신 두 번째 호출에서 exitfn
이 exit 1
명령은 스크립트 실행을 종료합니다( printf
두 번째 명령이 실행되지 않는다는 사실에서 알 수 있듯이).
두 호출 간의 유일한 차이점 exitfn
은 첫 번째 호출은 두 구성 요소 파이프라인의 첫 번째 구성 요소로 나타나고 두 번째 호출은 "독립 실행형" 호출이라는 것입니다.
나는 이것에 대해 혼란스러워합니다. 나는 이로 exit
인해 현재 프로세스(예: PID PID 가 있는 프로세스 $$
)가 종료될 것이라고 생각했습니다. 분명히 이것이 항상 사실은 아닙니다.
exitfn
그래도 파이프라인에서 호출되더라도 주변 스크립트가 종료되도록 작성하는 방법이 있을까요 ?
그런데 위 스크립트도 유효한 zsh 스크립트이며 동일한 결과를 생성합니다.
% zsh ./demo.sh
script PID: 26799
exitfn PID: 26799
SHOULD NEVER SEE THIS (0)
exitfn PID: 26799
나는 또한 zsh에 관한 이 질문에 대한 답변에 관심이 있습니다.
exitfn
마지막으로 이러한 구현은 전혀 작동하지 않는다는 점을 지적하고 싶습니다 .
exitfn () {
printf -- 'exitfn PID: %d\n' "$$" >&2
exit 1
kill -9 "$$"
}
...어쨌든 이 exit 1
명령은 항상 함수를 실행하는 마지막 줄이기 때문입니다. (바꾸다 exit 1
with는 kill -9 $$
허용되지 않습니다. 스크립트의 종료 상태와 stderr에 대한 출력을 제어하고 싶습니다. )
1 실제로 이러한 기능은 현재 프로세스를 종료하기 전에 진단 로깅 또는 정리 작업과 같은 다른 작업을 수행합니다.
답변1
나는 이것이
exit
현재 프로세스(즉, 에서 제공한 PID를 가진 프로세스$$
) 를 죽일 것이라고 생각했습니다.
"현재 프로세스"는 "주어진 PID를 가진 프로세스"와 동일하지 않습니다 $$
. exit
현재 종료서브쉘, 또는 서브쉘에서 호출되지 않은 경우 원래 쉘입니다.
( … )
(서브쉘로 그룹화됨), 명령 대체( $(…)
또는 `…`
) 및 파이프의 각 측면(또는 일부 쉘에서는 바로 왼쪽)의 내용 과 같은 특정 구성은 서브쉘에서 실행됩니다. 서브쉘은 다음을 사용하여 생성된 별도의 프로세스인 것처럼 동작합니다.fork()
, 일반적으로 이런 방식으로 구현됩니다(일부 쉘은 성능 최적화로 하위 프로세스를 사용하지 않는 경우도 있음). 서브셸에는 자체 변수 복사본, 자체 리디렉션 등이 있습니다. 표준 C 라이브러리의 함수가 프로세스를 종료하는 exit
것과 마찬가지로 서브셸을 종료하기 위해 호출됩니다 . exit()
서브셸에 대한 자세한 내용은 다음을 참조하세요.(make 문서의 맥락에서) 서브셸이란 무엇입니까?,"서브쉘"과 "하위 프로세스"의 정확한 차이점은 무엇입니까?그리고$()는 서브쉘인가요?.
$$
항상 원래 셸 프로세스의 프로세스 ID입니다. 서브쉘 내에서는 변경되지 않습니다. 일부 쉘에는 $BASHPID
bash 및 mksh, ${.sh.subshell}
ksh 또는 $ZSH_SUBSHELL
zsh 와 같이 하위 쉘에서 변경되는 변수가 있습니다 $sysparams[pid]
.
exitfn
파이프라인에서 호출되더라도 주변 스크립트가 종료되도록 작성하는 방법이 있나요 ?
완전한 것은 아니고. 스크립트가 하위 셸을 생성하는 위치, 최상위 수준 또는 둘 다에서 더 많은 작업을 수행해야 합니다. 바라보다서브쉘에서 쉘 스크립트 종료근사치에 사용됩니다.
¹ 다른 셸과 달리 파이프의 각 멤버( 하위 프로세스에서 실행되는 경우에도 ) 및 출력 2 zsh
가 아닌 상위 프로세스의 파이프(왼쪽에서 오른쪽으로)에서 확장을 수행합니다 .zsh -c 'echo $ZSH_SUBSHELL | cat'
0
echo
zsh -c 'n=0; echo $((++n)) | echo $((++n))'