`set -eu`를 사용할 때 EXIT 및 ERR 트랩의 올바른 동작


27

ERR 및 EXIT 트랩과 함께 set -e( errexit), set -u( 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 버전과 관련이 있다는 흥미로운 의견 이있는 SO 게시물 이 있습니다. Bash 4.3.11로이 스 니펫을 테스트하면 RC = 1이되므로 더 좋습니다. 불행히도 현재 모든 호스트에서 Bash (3.2.51에서)를 업그레이드하는 것은 불가능하므로 다른 해결책을 찾아야합니다.

누구든지 이러한 행동 중 하나를 설명 할 수 있습니까?

이러한 주제를 검색하는 데 성공하지 못했습니다. Bash 설정 및 트랩에 대한 게시물 수가 많으면 놀랍습니다. 그러나 포럼 스레드하나 있지만 결론은 다소 불만족입니다.


3
4에서 나는 bash표준을 깨고 트랩을 서브 쉘에 넣기 시작 했다고 생각 합니다. 함정이 돌아 왔을 때와 같은 환경에서 함정을 실행해야하지만 bash꽤 오랫동안 그렇게하지는 않았습니다.
mikeserv 2016 년

1
잠깐만-해결책이나 설명을 원하십니까? 그리고 솔루션을 원한다면 정확히 무엇에 대한 솔루션입니까? 무엇을 원하십니까? set -e그리고 set -u둘 다하기 위해 특별히 설계된 죽일 스크립팅 쉘을. 것이다 애플리케이션 실행할 수있는 조건을 사용하여 죽일 스크립팅 쉘을. 그것들을 사용 하지 않고 코드 시퀀스에 적용 할 때 그러한 조건 을 테스트 하는 것을 제외하고는 그 문제를 해결할 수 없습니다 . 따라서 기본적으로 좋은 쉘 코드를 작성하거나을 사용할 수 있습니다 set -eu.
mikeserv 2016 년

2
실제로, 나는 -uERR 트랩을 트리거하지 않는 이유 (오류이므로 트랩을 트리거 해서는 안 됨)에 대한 충분한 정보를 찾을 수 없거나 오류 코드가 1 대신 0이므로 둘 다 찾고 있습니다. 후자는 이미 이후 버전에서 수정 된 버그 인 것 같습니다. 그러나 쉘 평가 (매개 변수 확장)의 오류와 명령의 실제 오류가 두 가지 다른 것 같다는 것을 깨닫지 못하면 첫 번째 부분을 이해하기가 매우 어렵습니다. 솔루션의 경우, 제안한대로 -eu필요할 때 수동으로 피하고 확인 하려고 합니다.
dvdgsng

1
@dvdsng-좋아. 그것은 갈 길입니다-당신은 답변으로 할 때 스크립트를 게시하고 현상금을 스스로에게 수여해야합니다. 나는 그 옵션을 정말로 좋아하지 않습니다. 안전한 방식으로 예외 처리를 허용하지 않습니다.
mikeserv 2016 년

1
@dvdsng-이러한 옵션 중 하나가 유용 할 수있는 곳은 하위 셸 컨텍스트입니다. 따라서 이전에 사용했던 모든 것을 다음 (set -u; : $UNSET_VAR)과 같은 하위 셸 컨텍스트로 현지화 할 수 있습니다 . 이런 종류의 물건도 좋을 수 있습니다- &&때때로 (set -e; mkdir dir; cd dir; touch dirfile)드리프트를 얻는다면 많은 것을 떨어 뜨릴 수 있습니다 . 그것들은 단지 통제 된 맥락 일뿐입니다-그것들을 전역 옵션으로 설정하면 통제를 잃고 통제됩니다. 그러나 일반적으로 더 효율적인 솔루션이 있습니다.
mikeserv 2016 년

답변:


15

보낸 사람 man bash:

  • set -u
    • 취급 해제 변수 특별한 변수 이외의 변수 "@""*"파라미터 확장을 수행 할 때 에러 등. 설정되지 않은 변수 또는 매개 변수에서 확장을 시도하면, 쉘은 오류 메시지를 인쇄하고, -i활성화 되지 않은 경우 0이 아닌 상태로 종료됩니다.

POSIX에 따르면 확장 오류가 발생 하면 확장이 쉘 특수 내장 ( 어쨌든 정기적으로 무시되므로 관계가 없음) 또는 기타 유틸리티 와 연관 될 때 비 대화식 쉘 이 종료 됩니다 .bash

  • 쉘 오류의 결과 :
    • 확장 오류Word Expansions에 정의 된 셸 확장 이 수행 될 때 발생 하는 오류 입니다 (예 : 유효한 연산자가 아니기 "${x!y}"때문에 !) . 구현은 수도 가 아닌 확장하는 동안, 토큰 화하는 동안이를 감지 할 수있는 경우 구문 오류 이러한 치료.
    • [A] n 대화식 쉘은 종료하지 않고 표준 오류에 진단 메시지를 작성해야합니다.

또한 man bash:

  • trap ... ERR
    • sigspec이 ERR 인 경우 다음 조건에 따라 파이프 라인 (단일 단순 명령으로 구성 될 수 있음) , 목록 또는 복합 명령이 0이 아닌 종료 상태를 리턴 할 때마다 명령 arg 가 실행 됩니다 .
      • ERR의 실패한 명령은 즉시 다음 명령 목록의 일부인 경우 트랩은 실행되지 않습니다 while또는 until키워드가 ...
      • ... if성명서 에서 테스트의 일부 ...
      • ... 최종 또는 다음에 오는 명령을 제외하고 &&또는 ||목록 에서 실행 된 명령의 일부 ...&&||
      • ... 파이프 라인의 명령이지만 마지막 명령은 ...
      • ... 또는를 사용하여 명령의 반환 값이 반전되는 경우 !.
    • 이 옵션 은 errexit -e 옵션 과 동일한 조건 입니다.

것을 위의 참고 ERR의 트랩이 일부의 평가에 대한 모든 것입니다 다른 명령의 반환. 그러나 확장 오류 가 발생하면 아무 것도 반환하기위한 명령이 실행되지 않습니다. 당신의 예에서, echo 일이 결코 - 쉘 평가하고 그 인수를 확대하면서이 발생하기 때문에 -u현재, 스크립트 쉘에서 즉시 종료의 원인이 명시 적 쉘 옵션으로 지정되어 NSET 변수를.

따라서 EXIT 트랩 (있는 경우)이 실행되고 셸은 진단 메시지와 함께 종료되고 0이 아닌 종료 상태가 그대로 있어야합니다.

에 관해서는 RC : 0 건, 그 어떤 종류의 버전 특정 버그입니다 기대 - 아마도에 대한 두 개의 트리거와 함께 할 EXIT 동시에 발생하고 상대방의 종료 코드를 얻는 일 (발생하지 않습니다) . 어쨌든, 최신 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가 잘못된 행 번호를 갖도록 하는 버그를 수정했습니다 .
  • 비동기 서브 쉘 명령에서 SIGINTSIGQUIT 를 사용할 수없는 버그가 수정 trap되었습니다.
  • 대화식 쉘 에서 두 번째 이후의 SIGINT 가 무시되는 인터럽트 처리 문제를 수정했습니다 .
  • 쉘은 더 이상 trap해당 신호에 대한 핸들러를 실행하는 동안 신호 수신을 차단하지 않으며 대부분의 trap 핸들러가 재귀 적으로 실행되도록합니다 ( 핸들러가 실행 trap되는 동안 trap핸들러 실행) .

가장 관련성이 높은 마지막 또는 첫 번째 또는 두 가지의 조합이라고 생각합니다. trap핸들러는 매우 자연입니다 비동기 의 전체 작업이 대기하고 처리 할 수 있기 때문에 비동기 신호를 . 그리고 당신은 동시에 두 개의 트리거 -eu하고 $UNSET_VAR.

따라서 업데이트해야 할 수도 있지만 원하는 경우 다른 셸을 사용하여 업데이트해야합니다.


파라미터 확장이 다르게 처리되는 방법에 대한 설명에 감사드립니다. 그것은 나에게 많은 것들을 정리했다.
dvdgsng

귀하의 설명이 가장 도움이 되었기 때문에 현상금을드립니다.
dvdgsng

@dvdgsng-그라시아 스. 호기심에서 당신은 당신의 솔루션과 함께 나타 났습니까?
mikeserv

9

(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

내 bash에서 나는 얻는다.

./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

실제로 가치를 더하는 실용적인 솔루션입니다!
Florian Heigl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.