현재 프로세스를 (지정된 상태로) 안정적으로 종료하는 함수를 작성하는 방법은 무엇입니까?

현재 프로세스를 (지정된 상태로) 안정적으로 종료하는 함수를 작성하는 방법은 무엇입니까?

아래 스크립트는 문제를 최소한으로(인위적으로) 설명한 것입니다.

## 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(즉시 실행된 첫 번째 명령에서 알 수 있듯이). 대신 두 번째 호출에서 exitfnexit 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 1with는 kill -9 $$허용되지 않습니다. 스크립트의 종료 상태와 stderr에 대한 출력을 제어하고 싶습니다. )


1 실제로 이러한 기능은 현재 프로세스를 종료하기 전에 진단 로깅 또는 정리 작업과 같은 다른 작업을 수행합니다.

답변1

나는 이것이 exit현재 프로세스(즉, 에서 제공한 PID를 가진 프로세스 $$) 를 죽일 것이라고 생각했습니다.

"현재 프로세스"는 "주어진 PID를 가진 프로세스"와 동일하지 않습니다 $$. exit현재 종료서브쉘, 또는 서브쉘에서 호출되지 않은 경우 원래 쉘입니다.

( … )(서브쉘로 그룹화됨), 명령 대체( $(…)또는 `…`) 및 파이프의 각 측면(또는 일부 쉘에서는 바로 왼쪽)의 내용 과 같은 특정 구성은 서브쉘에서 실행됩니다. 서브쉘은 다음을 사용하여 생성된 별도의 프로세스인 것처럼 동작합니다.fork(), 일반적으로 이런 방식으로 구현됩니다(일부 쉘은 성능 최적화로 하위 프로세스를 사용하지 않는 경우도 있음). 서브셸에는 자체 변수 복사본, 자체 리디렉션 등이 있습니다. 표준 C 라이브러리의 함수가 프로세스를 종료하는 exit것과 마찬가지로 서브셸을 종료하기 위해 호출됩니다 . exit()서브셸에 대한 자세한 내용은 다음을 참조하세요.(make 문서의 맥락에서) 서브셸이란 무엇입니까?,"서브쉘"과 "하위 프로세스"의 정확한 차이점은 무엇입니까?그리고$()는 서브쉘인가요?.

$$항상 원래 셸 프로세스의 프로세스 ID입니다. 서브쉘 내에서는 변경되지 않습니다. 일부 쉘에는 $BASHPIDbash 및 mksh, ${.sh.subshell}ksh 또는 $ZSH_SUBSHELLzsh 와 같이 하위 쉘에서 변경되는 변수가 있습니다 $sysparams[pid].

exitfn파이프라인에서 호출되더라도 주변 스크립트가 종료되도록 작성하는 방법이 있나요 ?

완전한 것은 아니고. 스크립트가 하위 셸을 생성하는 위치, 최상위 수준 또는 둘 다에서 더 많은 작업을 수행해야 합니다. 바라보다서브쉘에서 쉘 스크립트 종료근사치에 사용됩니다.


¹ 다른 셸과 달리 파이프의 각 멤버( 하위 프로세스에서 실행되는 경우에도 ) 및 출력 2 zsh가 아닌 상위 프로세스의 파이프(왼쪽에서 오른쪽으로)에서 확장을 수행합니다 .zsh -c 'echo $ZSH_SUBSHELL | cat'0echozsh -c 'n=0; echo $((++n)) | echo $((++n))'

관련 정보