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


47

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

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

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

출력을 보겠다고 가정합니다.

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

그러나 실제로 볼 수 있습니다 :

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

않습니다 ()어떻게 든 체인 명령은 범위를 만들? exit스크립트가 아닌 경우 종료되는 것은 무엇입니까 ?


5
이것은 한마디로 답을 요구한다 : subshell
Joshua

답변:


87

()서브 쉘에서 명령을 실행하므로 서브 쉘 exit을 종료하고 상위 쉘로 돌아갑니다. {}현재 쉘에서 명령을 실행 하려면 중괄호를 사용하십시오 .

bash 매뉴얼에서 :

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

{목록; } 리스트는 단순히 현재 쉘 환경에서 실행됩니다. 리스트는 개행 또는 세미콜론으로 끝나야합니다. 이것을 그룹 명령이라고합니다. 반환 상태는 list의 종료 상태입니다. 메타 문자 (및)와 달리 {및}는 예약어이며 예약어를 인식 할 수있는 위치에서 발생해야합니다. 단어 분리를 일으키지 않기 때문에 공백이나 다른 쉘 메타 문자로 목록에서 분리해야합니다.

쉘 구문이 상당히 일관성 있고 서브 쉘이 ()명령 대체 (구식 `..`구문 과 함께 ) 또는 프로세스 대체 와 같은 다른 구성 에도 참여 하므로 다음은 현재 쉘에서 종료되지 않습니다.

echo $(exit)
cat <(exit)

명령이 명시 적으로 내부에 배치 될 때 서브 쉘이 포함되는 것이 명백 할 수 있지만 (), 덜 눈에 띄는 사실은 이들이 다른 구조에서도 생성된다는 것입니다.

  • 백그라운드에서 시작된 명령

    exit &

    (이후 man bash) 때문에 현재 쉘을 종료하지 않습니다.

    제어 연산자 &에 의해 명령이 종료되면, 쉘은 서브 쉘의 백그라운드에서 명령을 실행합니다. 쉘은 명령이 완료되기를 기다리지 않으며 리턴 상태는 0입니다.

  • 파이프 라인

    exit | echo foo

    여전히 서브 쉘에서만 나옵니다.

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

    exit | exit | exit

    기본적으로 bash에서는 아무것도하지 않지만 last 때문에 zsh에서 종료됩니다 exit.

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


5
아 이제 전임자가 잘못된 괄호를 사용한 모든 장소를 찾으려고합니다. 통찰력에 감사드립니다.
Minix

10
man 페이지에서 간격의주의 기록해 둡니다 : {}구문 아니다, 그들은 단어를 예약되어 공간에 둘러싸여해야하며, 목록은 명령 종료 (세미콜론, 줄 바꿈, 앰퍼샌드)로 끝나야합니다
글렌 잭맨

흥미롭게도 이것은 실제로 다른 프로세스이거나 내부 스택의 별도 환경일까요? 나는 chdirs를 분리하기 위해 ()를 많이 사용하며 전자의 경우 $$ 등을 사용하여 운이 좋았을 것입니다.
Dan Sheppard

5
@DanSheppard 또 다른 프로세스이지만 서브 쉘을 작성하기 전에 확장 (echo $$)되었기 때문에 상위 쉘 ID를 인쇄합니다 $$. 실제로 서브 쉘 프로세스 ID를 인쇄하는 것은 까다로울 수 있습니다. stackoverflow.com/questions/9119885/…
jimmij

@jimmij, $$서브 쉘이 생성되기 전에 어떻게 확장 될 수 있고 서브 쉘에 $BASHPID대한 올바른 값을 보여줄 수 있습니까?
와일드 카드

13

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

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

스크립트는 42를 인쇄 하고 리턴 코드 를 사용하여 서브 쉘 을 종료 1한 후 스크립트를 계속합니다. 의 호출 echo $(CALC) || exit 1코드를 바꾸더라도 도움이되지 않습니다.의 리턴 코드는의 리턴 코드에 echo관계없이 0입니다 calc. 그리고 calc이전에 실행됩니다 echo.

더 많은 수수께끼는 다음 스크립트와 같이 내장 exit으로 감싸서 효과를 방해합니다 local. 입력 값을 확인하는 함수를 작성할 때 문제를 우연히 발견했습니다. 예:

오늘의 "year month day.log"라는 파일을 만들고 싶습니다 20141211.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 …트리거되지 않습니다 . 이는 이후 에 실행 되므로 리턴 코드를 겹쳐 쓰기 때문 입니다 . 그리고 그 때문에 스크립트는 계속 빈 매개 변수로 호출 됩니다. 이 예제는 간단하지만 bash 동작은 실제 응용 프로그램에서 상당히 혼란 스러울 수 있습니다. 실제 프로그래머는 현지인을 사용하지 않습니다 .☺local0local $(fname)touch

명확하게하려면 :이 없으면 local유효하지 않은 날짜를 입력하면 스크립트가 예상대로 중단됩니다.

수정은 라인을 다음과 같이 분할하는 것입니다

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

이상한 동작 local은 bash의 맨 페이지에 있는 문서에 따릅니다 . "로컬이 함수 외부에서 사용되거나 유효하지 않은 이름이 제공되거나 name이 읽기 전용 변수가 아니면 리턴 상태는 0입니다."

버그는 아니지만 bash의 동작은 반 직관적이라고 생각합니다. local그럼에도 불구하고 실행 순서를 알고 있지만 깨진 과제를 가리지 않아야합니다.

내 초기 답변에는 약간의 부정확성이 포함되어 있습니다. mikeserv와의 공개적이고 심도있는 토론 후 (고맙습니다) 나는 그들을 고치기 위해 갔다.


@ mikeserv : 관련성을 보여주는 예제를 추가했습니다.
hermannk

@ mikeserv : 네, 그렇습니다. 심지어 터져. 그러나 함정은 여전히 ​​존재합니다.
hermannk

@ mikeserv : 죄송합니다. 예가 깨졌습니다. 에서 테스트를 잊었습니다 doit().
hermannk


1

대괄호는 서브 쉘을 시작하고 종료는 해당 서브 쉘 만 종료합니다.

$?서브 쉘이 종료 된 경우 종료 코드를 읽고 스크립트에 추가하여 스크립트를 종료 할 수 있습니다 .

#!/bin/bash

function bla() {
    return 1
}

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

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

echo '2'

1

실제 솔루션 :

#!/bin/bash

function bla() {
    return 1
}

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

echo '2'

오류 그룹화는 bla오류 상태를 리턴 exit하고 서브 쉘에없는 경우에만 실행 되므로 전체 스크립트가 중지됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.