"set -eu" 사용 시 EXIT 및 ERR 트랩의 올바른 동작

"set -eu" 사용 시 EXIT 및 ERR 트랩의 올바른 동작

set -e( errexit), set -u( ), ERR 및 EXIT 트랩을 사용할 nounset때 몇 가지 이상한 동작을 관찰했습니다 . 서로 관련이 있는 것 같으니 하나의 질문으로 묶는 것이 타당해 보입니다.

1) set -uERR 트랩을 트리거하지 않습니다.

  • 암호:

    #!/bin/bash
    trap 'echo "ERR (rc: $?)"' ERR
    set -u
    echo ${UNSET_VAR}
    
  • 예상: ERR 트랩 호출, RC != 0
  • 실제: ERR 트랩은 다음과 같습니다.아니요호출됨, RC == 1
  • 참고: set -e결과는 변경되지 않습니다.

2) set -euEXIT 트랩에서 1 대신 종료 코드 0을 사용하십시오.

  • 암호:

    #!/bin/bash
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
    
  • 예상: EXIT 트랩이 호출됩니다.RC==1
  • 실제: EXIT 트랩이 호출됩니다.RC==0
  • 참고: 사용 시 set +eRC == 1입니다. 다른 명령에서 오류가 발생하면 EXIT 트랩이 올바른 RC를 반환합니다.
  • 편집하다:이 주제에 대한 게시물이 있습니다흥미로운 댓글이는 사용 중인 Bash 버전과 관련이 있을 수 있음을 나타냅니다. Bash 4.3.11로 이 코드 조각을 테스트하면 RC=1이 제공되므로 더 잘 작동합니다. 불행하게도 현재 모든 호스트(3.2.51 기준)에서 Bash를 업그레이드하는 것은 불가능하므로 다른 솔루션을 찾아야 합니다.

누구든지 이러한 행동을 설명할 수 있습니까?

이러한 주제를 검색하는 것은 그다지 성공적이지 않았으며 Bash 설정 및 함정에 대한 게시물 수를 고려하면 상당히 놀랍습니다. 가지다포럼 주제그럼에도 결론은 만족스럽지 않다.

답변1

에서 man bash:

  • set -u
    • 매개변수 확장 시 설정되지 않은 변수와 특수 매개변수 이외의 매개변수는 오류 "@"로 처리됩니다 "*". 설정되지 않은 변수나 매개변수에 대해 확장을 시도하면 쉘은 오류 메시지를 인쇄하고 -i대화형이 아닌 경우 0이 아닌 상태로 종료됩니다.

POSIX에서는 다음과 같이 말합니다.인플레이션 오류, 비대화형 쉘종료해야 함확장이 쉘 특수 내장과 연관되는 경우(이것은 어쨌든 종종 간과되는 구별 bash이므로 관련이 없을 수도 있습니다.)아니면 그 외에 다른 유틸리티도 있습니다.

  • 쉘 오류의 결과:
    • 하나인플레이션 오류셸 확장이 정의된 경우입니다.단어 확장지휘하다(예: 은 유효한 연산자가 아니기 "${x!y}"때문에 )!;구현가능한확장 중이 아닌 토큰화 중에 감지될 수 있는 경우 구문 오류로 간주됩니다.
    • [A]n 대화형 쉘은 종료하지 않고 표준 오류에 진단 메시지를 써야 합니다.

또한 다음에서 man bash:

  • trap ... ERR
    • 시그스펙이 다음과 같은 경우, 주문하다아르기닌파이프라인이 있을 때마다 실행(간단한 명령으로 구성될 수 있음), 목록 또는 복합 명령은 다음 조건이 충족되는 경우 0이 아닌 종료 상태를 반환합니다.
      • 이것실패한 명령이 while또는 until키워드 바로 다음에 나오는 명령 목록의 일부인 경우 트랩은 실행되지 않습니다.
      • ...문에 있는 테스트의 일부입니다 if...
      • &&||...마지막 또는 다음 명령을 제외하고 목록에서 실행되는 명령의 일부 ...&&||
      • ...마지막 명령을 제외한 파이프라인의 모든 명령...
      • ...또는 명령의 반환 값을 반전하는 데 사용되는 경우 !.
    • 이러한 조건은 다음과 관련이 있습니다.오류가 발생하여 종료 -e옵션.

위 사항을 참고해주세요함정은 특정인에 대한 평가다.다른명령의 반환. 하지만 언제인플레이션 오류이런 일이 발생하면 아무 것도 반환하지 않는 명령이 실행되지 않습니다. 귀하의 예에서는echo 결코 일어나지 않을 것이다- 쉘이 인수를 평가하고 확장할 때 -u명시적 쉘 옵션으로 지정된 nset 변수를 발견하여 현재 스크립트된 쉘이 즉시 종료되기 때문입니다.

그래서출구트랩(있는 경우)이 실행되고 쉘은 진단 메시지와 0이 아닌 종료 상태로 종료됩니다.

에 관해서는제어 센터: 0문제는 이것이 일종의 버전별 버그이길 바랍니다. 아마도 두 가지 트리거와 관련이 있을 것입니다.출구동시에 발생하고 한 당사자가 상대방의 종료 코드를 얻습니다.(이런 일이 일어나서는 안 됩니다). 어쨌든 bash설치된 최신 바이너리를 사용하십시오 pacman.

bash <<\IN
    printf "shell options:\t$-\n"
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
IN

쉘의 조건이 스크립트된 쉘의 조건임을 알 수 있도록 첫 번째 줄을 추가했습니다.아니요인터렉티브. 출력은 다음과 같습니다

shell options:  hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)

다음은 관련 참고사항입니다.최근 변경 내역:

  • 비동기 명령이 잘못 설정되는 버그를 수정했습니다 $?.
  • 오류 메시지가 생성되는 버그를 수정했습니다.확장 오류명령의 for줄 번호가 잘못되었습니다 .
  • 발생한 문제를 해결했습니다.지능을 신호하다그리고신호 출구trap비동기 서브셸 명령과 함께 사용할 수 없습니다 .
  • 두 번째 및 후속 이벤트를 일으키는 인터럽트 처리 문제를 수정했습니다.지능을 신호하다대화형 쉘에서는 무시됩니다.
  • 쉘은 더 이상 이러한 신호에 대한 핸들러를 실행할 때 신호 수신을 차단하지 않으며 trap다음을 허용합니다.최대 trap핸들러를 재귀적으로 실행 ( 핸들러가 실행되는 trap동안 핸들러를 실행합니다 )trap.

내 생각에 가장 관련성이 높은 것은 마지막 항목이나 첫 번째 항목, 아니면 둘의 조합일 수도 있습니다. ㅏtrap매니저그 성격상비동기식왜냐하면 그것이 하는 일은 기다리고 처리하는 것뿐이기 때문입니다.비동기 신호. -eu두 가지를 동시에 사용하고 트리거할 수 있습니다 $UNSET_VAR.

따라서 업데이트해야 할 수도 있지만 자신이 마음에 들면 완전히 다른 쉘을 사용하여 업데이트할 수도 있습니다.

답변2

(저는 bash 4.2.53을 사용하고 있습니다). 파트 1의 경우 bash 매뉴얼 페이지에는 "오류 메시지가 표준 오류에 기록되고 비대화형 쉘이 종료됩니다"라고만 나와 있습니다. ERR 트랩이 호출될 것이라고 말하지는 않지만 그렇게 하면 유용할 것이라는 데 동의합니다.

실용적으로 말하자면, 정말로 원하는 것이 정의되지 않은 변수를 보다 깔끔하게 처리하는 것이라면 가능한 해결책 중 하나는 대부분의 코드를 함수에 넣은 다음 서브셸에서 함수를 실행하고 반환 코드와 stderr 출력을 복원하는 것입니다. 다음은 "cmd()"가 함수인 예입니다.

#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions

cmd(){
 echo "args=$*"
 echo ${UNSET_VAR}
 echo hello
}
oops(){
 rc=$?
 echo "$@"
 return $rc # provoke ERR trap
}

exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout 
then    echo ok
else    oops "fail: $output"
fi

내 난교에서 나는 얻을

./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1

답변3

nounset와 함께 사용하는 방법을 찾다가 이것을 발견한 사람들을 위해 trap ERR.

다음은 오류 트랩을 트리거하지 않습니다.

set -o nounset

trap 'trapped error on line $LINENO' ERR

echo $foo

예상 출력

trapped error on line 5

실제 출력

./script: line 5: foo: unbound variable

이 플래그를 활성화된 상태로 유지하려는 경우 nounset한 가지 해결 방법은 종료 트랩에서 반환 코드를 확인하는 것입니다.

set -o nounset

trap '[[ $? != 0 ]] && echo caught error' EXIT

echo $foo

산출

./script: line 5: foo: unbound variable
caught error

관련 정보