(exit 1)이 스크립트를 종료하지 않는 이유는 무엇입니까?

(exit 1)이 스크립트를 종료하지 않는 이유는 무엇입니까?

내가 원할 때 종료되지 않는 스크립트가 있습니다.

동일한 오류가 있는 예제 스크립트는 다음과 같습니다.

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

echo '2'

나는 출력을 본다고 가정합니다 :

:~$ ./test.sh
1
:~$

하지만 실제로는 다음과 같습니다.

:~$ ./test.sh
1
2
:~$

명령 연결이 ()어떤 방식으로 범위를 생성합니까? exit스크립트가 아닌 경우 무엇을 종료합니까?

답변1

()하위 쉘에서 명령을 실행하므로 exit하위 쉘을 종료하고 상위 쉘로 돌아갈 수 있습니다. {}현재 셸에서 명령을 실행하려면 중괄호를 사용하세요.

Bash 매뉴얼에서:

(목록)목록은 서브쉘 환경에서 실행됩니다. 쉘 환경에 영향을 미치는 변수 할당 및 내장 명령은 명령이 완료된 후에 더 이상 유효하지 않습니다. 반환 상태는 목록의 종료 상태입니다.

{ 목록; }list는 현재 쉘 환경에서만 실행됩니다. 목록은 줄 바꿈 또는 세미콜론으로 끝나야 합니다. 이를 그룹 명령이라고 합니다. 반환 상태는 목록의 종료 상태입니다. 메타 문자( 및 )와 달리 { 및 }는 예약어이므로 예약어를 인식할 수 있는 위치에 나타나야 합니다. 단어 분리를 유발하지 않으므로 공백이나 기타 쉘 메타 문자로 목록과 구분해야 합니다.

쉘 구문은 매우 일관적이며 하위 쉘은 ()명령 대체(이전 스타일 `..`구문 사용) 또는 프로세스 대체와 같은 다른 구성에도 참여하므로 다음은 현재 쉘에서 종료되지 않습니다.

echo $(exit)
cat <(exit)

명령이 명시적으로 내부에 배치될 때 하위 쉘이 관련된다는 것은 분명하지만 (), 하위 쉘이 이러한 다른 구조 내에서도 생성된다는 사실은 덜 분명합니다.

  • 명령이 백그라운드에서 시작됩니다.

    exit &
    

    man bash(이후 ) 때문에 현재 쉘을 종료하지 마십시오.

    명령이 제어 연산자 &에 의해 종료되면 쉘은 서브쉘의 백그라운드에서 명령을 실행합니다. 쉘은 명령이 완료될 때까지 기다리지 않고 상태 0을 반환합니다.

  • 관로

    exit | echo foo
    

    여전히 서브쉘에서만 종료됩니다.

    그러나 이와 관련하여 다른 껍질은 다르게 작동합니다. 예를 들어 AT&T는 bash파이프라인의 모든 구성 요소를 별도의 하위 셸에 넣지만( lastpipe작업 제어가 활성화되지 않은 호출에서 이 옵션을 사용하지 않는 한) AT&T는 현재 셸에서 마지막 부분을 ksh실행합니다 zsh(POSIX에서는 두 동작을 모두 허용합니다). 그러므로

    exit | exit | exit
    

    기본적으로 bash에서는 아무것도 수행하지 않지만 다음으로 인해 zsh를 종료합니다.마침내 exit.

  • coproc exitexit서브쉘에서도 실행됩니다 .

답변2

서브쉘에서 실행하는 것은 exit함정입니다:

#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)

스크립트는 42를 인쇄하고 종료됩니다.서브쉘반환 코드를 사용하면 1스크립트 실행이 계속됩니다. 의 반환 코드에 관계없이 의 반환 코드는 항상 0이므로 호출을 로 바꾸는 것도 echo $(CALC) || exit 1도움이 되지 않습니다 . 그리고 전에 실행합니다 .echocalccalcecho

exitlocal더 혼란스러운 것은 다음 스크립트에 표시된 것처럼 내장 스크립트로 래핑하여 효과를 차단하는 것입니다 . 입력 값의 유효성을 검사하는 함수를 작성할 때 이 문제를 우연히 발견했습니다. 예:

20141211.log오늘의 파일 인 "년월일.log"라는 파일을 생성하고 싶습니다 . 합리적인 가치를 제공하지 못하는 사용자가 날짜를 입력한 것입니다. 따라서 내 함수에서는 fname반환 값을 확인 date하여 사용자 입력의 유효성을 확인합니다.

#!/bin/bash

doit ()
    {
    local FNAME=$(fname "$1") || exit 1
    touch "${FNAME}"
    }

fname ()
    {
    date +"%Y%m%d.log" -d"$1" 2>/dev/null
    if [ "$?" != 0 ] ; then
        echo "fname reports \"Illegal Date\"" >&2
        exit 1
    fi
    }

doit "$1"

좋아 보인다. 스크립트 이름을 으로 지정하십시오 s.sh. ./s.sh "Thu Dec 11 20:45:49 CET 2014"이 파일은 사용자가 호출 스크립트를 사용하는 경우 생성됩니다 . 20141211.log그러나 사용자가 를 입력하면 ./s.sh "Thu hec 11 20:45:49 CET 2014"스크립트는 다음을 출력합니다.

fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory

이 행은 fname…서브셸에서 잘못된 입력 데이터가 감지되었음을 나타냅니다. 그러나 명령이 항상 반환되기 때문에 exit 1줄 끝 부분은 local …트리거되지 않습니다 . 처형당하기 때문이죠local0local뒤쪽에 $(fname)따라서 반환 코드를 덮어씁니다. 따라서 스크립트는 계속 진행되며 touch빈 매개변수를 사용하여 호출됩니다. 이 예는 간단하지만 실제 애플리케이션에서는 bash의 동작이 매우 혼란스러울 수 있습니다. 실제 프로그래머는 로컬을 사용하지 않는다는 것을 알고 있습니다. ☺

명확하게 말하면, 그렇지 않은 경우 local잘못된 날짜를 입력하면 스크립트가 예상대로 중단됩니다.

해결책은 선을 분리하는 것입니다

local FNAME
FNAME=$(fname "$1") || exit 1

이 이상한 동작은 bash 매뉴얼 페이지의 문서와 일치합니다 local. "local이 함수 외부에서 사용되거나 잘못된 이름이 제공되거나 name이 읽기 전용 변수가 아닌 한 반환 상태는 0입니다."

버그는 아니지만 bash의 동작은 직관적이지 않습니다. 그러나 나는 실행 순서를 알고 있으므로 local손상된 할당을 가려서는 안 됩니다.

내 원래 답변에는 일부 부정확한 내용이 포함되어 있었습니다. mikeserv와 길고 철저한 논의 끝에 (감사합니다) 문제를 해결하기 시작했습니다.

답변3

실제 솔루션:

#!/bin/bash

function bla() {
    return 1
}

bla || { echo '1'; exit 1; }

echo '2'

오류 그룹화는 bla오류 상태가 반환될 때만 실행되며 exit하위 셸 내에서는 실행되지 않으므로 전체 스크립트가 중지됩니다.

답변4

괄호는 a로 시작합니다.서브쉘종료는 해당 하위 쉘만 종료합니다.

다음 명령을 사용하여 종료 코드를 읽고 $?이를 스크립트에 추가하여 서브셸이 종료될 때 종료되도록 할 수 있습니다.

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi

echo '2'

관련 정보