errexit(`set -e`)를 수정하는 쉘을 작성해 본 사람이 있습니까?

errexit(`set -e`)를 수정하는 쉘을 작성해 본 사람이 있습니까?

errexit( set -e)은 간단한 스크립트를 더욱 강력하게 만드는 방법으로 종종 제안됩니다. 그러나 더 복잡한 스크립트(특히 함수)에서 그 동작을 본 사람이라면 일반적으로 이것이 끔찍한 함정이라는 데 동의합니다. 서브쉘에도 매우 유사한 문제가 존재합니다. 예를 들어,https://stackoverflow.com/questions/29926013/exit-subshell-on-error

(
set -o errexit
false
true
) && echo "OK" || echo "FAILED";

여기서 함정은 쉘이 "FAILED" 대신 "OK"를 표시한다는 것입니다. [*]

특정 상황에서의 동작은 set -e역사적으로 불행한 방식으로 다양했지만 최신 배포판에서 사용 가능한 셸은 POSIX 셸 표준을 따른다고 주장하며 테스트에 따르면 다음 모두 동일한 동작("OK")이 있는 것으로 나타났습니다.

  • bash-4.4.19-2.fc28.x86_64(페도라 모자)
  • busybox-1.26.2-3.fc27.x86_64(페도라 모자)
  • dash 0.5.8-2.4(데비안 9)
  • zsh 5.3.1-4+b2(데비안 9)
  • posh 0.12.6+b1(데비안 9)
  • ksh 93u+20120801-3.1(데비안 9).

질문

  1. 위 코드를 sh인쇄하는 것 외에 POSIX와 유사한 기능을 가진 쉘을 작성할 수 없는 기술적 이유가 있습니까 ?FAILED

    false를 반환하는 최상위 표현식이 set -e치명적인 것으로 간주되도록 변경될 수도 있었으면 좋겠습니다.&&https://serverfault.com/a/847016/133475 grep과 함께 사용할 수 있는 더 나은 관용구가 있기를 바랍니다.

    set -e
    
    # print matching lines, but it's not an error if there are no matches in the file
    (
    set +e
    grep pattern file.txt
    RET=$?
    [[ $RET == 0 || $RET == 1 ]] || exit $RET
    )
    

    let나는 또한 숫자 값을 암묵적으로 true/false 종료 상태로 강제하는 것을 피하기 위해 산술 명령문(내장)이 어떤 방식으로든 재정의될 것이라고 가정하고 있습니다 .

  2. 이를 수행할 수 있는 쉘 예제가 있습니까?

    Bourne의 구문과 다른 것을 보는 것은 괜찮습니다. 짧은 "접속" 스크립트를 작성할 때 저는 간결한 것에 관심이 있지만 여기에는 오류 감지를 위한 간결한 전략도 있습니다. &&실수로 문 구분 기호를 생략하면 오류가 자동으로 무시된다는 뜻이라면 이를 문 구분 기호 로 사용하는 것을 좋아하지 않습니다 .


[*] 편집하다. 이는 완벽한 예가 아닐 수도 있습니다. 토론에 따르면 더 나은 예는 set -o errexit위의 하위 쉘을 이동하는 것입니다.

(false; echo foo) || echo bar; echo \ $?AFAICT 이는 표의 테스트 사례와 매우 유사합니다.여기, 이는 원래 Bourne 쉘과 그 자손 대부분에 "foo"(그리고 "0")가 표시됨을 의미합니다. "hist.ash"와 bash 1.14.7은 예외입니다.

set -e하위 쉘에 - - 를 추가 하면 (set -e; false; echo foo) || echo bar; echo \ $?"SVR4 sh sun5.10" 및 dash-0.3.4라는 두 가지 추가 예외가 있습니다.

내 질문의 정신에 따르면, set -e서브쉘로 들어가는 것은 주의를 산만하게 할 것이라고 생각합니다. 나는 주로 set -e스크립트 상단에 사용되는 관용구에 관심이 있으며 이는 서브쉘에도 적용됩니다(이미 POSIX 동작입니다).

Jörg Schilling은 원래 Unix의 Bourne 쉘이 내가 이 질문에 사용한 예에 대해 "FAILED"를 인쇄할 것이라고 말했으며 그는 이 쉘을 다음과 같이 POSIX로 포팅했습니다.Healy 도구의 "osh", 그 결과는 2018년 6월 11일 발표 당시 검증되었습니다. 아마도 이는 1) set -e서브쉘 내부에 있는 것과 2) "SVR4 sh sun5.10"을 기반으로 한 것일 수 있습니다 . "osh"는 "OpenSolaris 소스를 기반으로 하므로 SVR4 및 SVID3"입니다. 아니면 && echo "OK"서브쉘 사이에 추가하면 끔찍한 추가 차이가 발생할 수도 있습니다 || echo "FAILED".

나는 교활한 껍질이 내 질문에 대답하지 않는다고 생각합니다. 함수에 서브셸을 사용할 수 있으며( 서브셸에서 함수를 실행하는 (대신 사용) . 그러나 서브쉘을 사용하면 전역 변수를 수정할 수 없다는 제한이 따릅니다. 적어도 보편적인 코딩 스타일이라고 옹호하는 사람은 본 적이 없습니다.{set -e

답변1

이것POSIX 표준(보다-이자형) errexit쉘 옵션에 대해서는 Bash 매뉴얼보다 더 명확합니다.

이 옵션이 활성화되면 명령이 실패할 때(셸 오류의 결과에 나열된 이유 또는 0보다 큰 종료 상태를 반환하는 경우) 마치 종료 특수 내장 유틸리티가 없이 실행된 것처럼 셸이 즉시 종료되어야 합니다. 다음 상황을 제외하고 모든 인수:

  1. 다중 명령 파이프라인에서 단일 명령이 실패하더라도 셸이 종료되어서는 안 됩니다. 파이프라인 자체의 오류만 고려됩니다.

  2. while, Until, if 또는 elif 예약어(!로 시작하는 파이프) 뒤에 오는 복합 목록을 실행할 때는 -e 설정을 무시해야 합니다. 예약어 또는 AND-OR 목록의 마지막 항목을 제외한 모든 명령.

  3. -e가 생략되었을 때 종료 상태가 실패의 결과인 경우 -e는 하위 쉘 명령이 아닌 복합 명령에 적용되지 않습니다.

이 요구 사항은 셸 환경과 각 하위 셸 환경에 별도로 적용됩니다. 예를 들어:

set -e; (false; echo one) | cat; echo two

false 명령을 사용하면 echo 1을 실행하지 않고 하위 쉘이 종료됩니다. 그러나 파이프(false; echo one) |cat의 종료 상태가 0이므로 echo 2가 실행됩니다.

분명히 예제 코드는 다음 의사 코드와 동일합니다.

( LIST; ) && COMMAND || COMMAND

종료 상태목록 다음과 같은 이유로 0입니다.

  • errexit옵션은 무시됩니다.

  • 반환 상태는 지정된 마지막 명령의 종료 상태입니다.목록, 여기 true.

따라서 AND 목록의 두 번째 부분인 를 수행하십시오 echo "OK".

답변2

"작문을 시도한 사람이 있습니까?"라는 질문에 대한 답변이 아니라 이를 수행하는 방법에 대한 몇 가지 아이디어입니다. 누구든지 피드백을 제공할 수 있는지 궁금합니다.

현재 스크립팅 솔루션(Python, JavaScript, 이전 Perl 등)의 일반적인 접근 방식은 try ... catch 블록을 사용하는 것입니다. 예상되는 동작은 오류가 발생하면 예외가 발생한다는 것입니다. 이 동작은 $?에서 오류를 포착하는 *sh 동작과 충돌합니다. 분명히 "-e"는 대부분의 개발자가 기대하는 방식으로 올바르게 작동하지 않습니다. 이 내용은 널리 문서화되어 있으므로 여기서 자세히 설명할 필요가 없습니다.

두 가지 가능한 접근 방식:

  • 더 나은 것을 만듭니다 set -e. 예를 들어 set -ocatcherr더 나은 오류 처리 기능을 사용합니다.
  • try { list ; }최신 스크립팅 솔루션을 따르기 위한 새로운 명령 추가

'-ofailerr`은 다음과 같이 정의할 수 있습니다:

  • 제어 흐름(if/while/until) 외부에서 명령이 실패하면 현재 실행 컨텍스트는 1을 반환해야 합니다(또는 최상위 블록인 경우 종료해야 합니다). 이 변경으로 인해 개발자는 실패한 명령에 대한 오류 처리를 제공하거나 명령을 무시할 수 있는 오류로 명시적으로 표시해야 합니다.
  • 간단한 스크립트에 적합하며 처리되지 않은 오류가 발생하면 효과적으로 중단됩니다.

방법은 try { list ; }비슷하지만 구문이 다릅니다.

예:

function foo {
    cat $* > a.txt
    wc -l a.txt
}

set -oerrfail
foo /non/existing/file    # Will  fail, without executing the "wc -l"

if foo /non/existing/file ; then
   ...
else
   # This is the "catch" block
fi

try 블록을 사용하세요:

try {
    foo /non/existing/file
    ...
} || { catch-block }

실제로 이 시도는 "if !" 아래의 명령 목록을 실행하는 "set -oerofail"과 동일합니다. { ... } 다음 { 블록 잡기 }

관련 정보