pipefail과 비슷한 방식으로 bash가 backtick 실패에서 종료되도록하려면 어떻게해야합니까?


55

따라서 오류가 포착되지 않도록 가능한 한 bash 스크립트를 강화하고 싶습니다 (Python / Ruby와 같은 언어로 위임 할 수없는 경우).

그 정맥에는 strict.sh가 있으며 다음과 같은 것을 포함합니다.

set -e
set -u
set -o pipefail

그리고 다른 스크립트에서 소스하십시오. 그러나 pipefail은 다음과 같이 나타납니다.

false | echo it kept going | true

픽업하지 않습니다 :

echo The output is '`false; echo something else`' 

출력은

출력은 ''

False는 0이 아닌 상태 및 비표준 출력을 반환합니다. 파이프에서는 실패했지만 여기서 오류가 발생하지 않습니다. 실제로 이것은 나중에 변수에 저장된 계산이고 값이 공백으로 설정된 경우 나중에 문제가 발생할 수 있습니다.

그래서 배쉬가 백틱 내부의 0이 아닌 리턴 코드를 종료하기에 충분한 이유로 취급하도록하는 방법이 있습니까?

답변:


51

단일 UNIX 사양 에서 의미set -e 를 설명 하는 데 사용되는 정확한 언어 는 다음과 같습니다.

이 옵션이 켜져 있으면 쉘 오류 결과에 나열된 이유로 단순 명령 이 실패 하거나 종료 상태 값> 0을 리턴하고 [조건부 또는 부정 명령]이 아닌 경우 쉘은 즉시 종료됩니다.

이러한 명령이 서브 쉘 에서 발생할 때 어떤 일이 발생하는지 모호합니다 . 실용적인 관점에서, 서브 쉘이 수행 할 수있는 모든 것은 종료하고 0이 아닌 상태를 상위 쉘로 리턴하는 것입니다. 부모 쉘이 차례로 종료되는지 여부는이 0이 아닌 상태가 부모 쉘에서 실패한 간단한 명령으로 변환되는지 여부에 따라 다릅니다.

이러한 문제가 발생한 사례 중 하나는 명령 대체 에서 0이 아닌 리턴 상태입니다 . 이 상태는 무시되므로 상위 쉘이 종료되지 않습니다. 이미 발견 했듯이 종료 상태를 고려하는 방법은 간단한 할당 에서 명령 대체를 사용하는 것입니다 . 그런 다음 할당 의 종료 상태는 할당에서 마지막 명령 대체의 종료 상태입니다 .

마지막 대체 상태 만 고려되므로 단일 명령 대체가있는 경우에만 의도 한대로 수행됩니다. 예를 들어, 다음 명령은 성공적입니다 (표준 및 내가 본 모든 구현에 따라).

a=$(false)$(echo foo)

를 감시하는 또 다른 경우는 명시 적으로 서브 쉘 : (somecommand). 위의 해석에 따르면, 서브 쉘은 0이 아닌 상태를 리턴 할 수 있지만 이것은 상위 쉘에서 간단한 명령이 아니므로 상위 쉘이 계속되어야합니다. 사실, 내가 아는 모든 껍질은이 시점에서 부모가 돌아 오게합니다. 이것은 (cd /some/dir && somecommand)현재 디렉토리 변경과 같은 작업을 로컬로 유지하기 위해 괄호를 사용하는 경우와 같이 많은 경우에 유용하지만 set -e, 서브 쉘에서 꺼져 있거나 서브 쉘이 0이 아닌 상태를 리턴하는 방식으로 스펙을 위반합니다. !실제 명령에서 사용 하는 것과 같이 종료하지 않습니다 . 예를 들어, ash, bash, pdksh, ksh93 및 zsh foo는 모두 다음 예제에 표시되지 않고 종료됩니다 .

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

그럼에도 불구하고 간단한 명령은 실패하지 않았습니다 set -e!

세 번째 문제는 사소하지 않은 파이프 라인의 요소입니다 . 실제로 모든 쉘은 마지막 파이프 라인 이외의 파이프 라인 요소의 실패를 무시하고 마지막 파이프 라인 요소와 관련하여 다음 두 가지 동작 중 하나를 나타냅니다.

  • 상위 쉘에서 파이프 라인의 마지막 요소를 실행하는 ATT ksh 및 zsh는 평상시처럼 비즈니스를 수행합니다. 파이프 라인의 마지막 요소에서 간단한 명령이 실패하면 해당 명령을 실행하는 쉘이 상위 쉘이됩니다. 종료합니다.
  • 다른 셸은 파이프 라인의 마지막 요소가 0이 아닌 상태를 반환하면 종료하여 동작에 근사합니다.

이전과 마찬가지로 set -e파이프 라인의 마지막 요소에서 부정을 끄 거나 사용하면 쉘을 종료하지 않아야하는 방식으로 0이 아닌 상태를 반환합니다. ATT ksh 및 zsh 이외의 쉘은 종료됩니다.

Bash의 pipefail옵션을 사용하면 파이프 라인 set -e의 요소 중 하나가 0이 아닌 상태를 반환하면 파이프 라인이 즉시 종료 됩니다.

추가 합병증으로 bash는 set -ePOSIX 모드가 set -o posix아니거나 POSIXLY_CORRECTbash가 시작될 때 환경 에 있지 않는 한 서브 쉘에서 꺼집니다 .

이 모든 것은 POSIX 사양이 불행히도 -e옵션 을 지정할 때 좋지 않은 작업을 수행한다는 것을 보여줍니다 . 다행히도 기존 쉘은 대부분 동작이 일관됩니다.


고마워 내가 겪은 경험은 bash의 이후 버전에서 발생하는 일부 오류가 set -e를 사용하는 이전 버전에서 무시되었다는 것입니다. 여기서의 의도는 처리되지 않은 오류 반환 / 실패 조건으로 인해 스크립트가 종료 될 정도로 스크립트를 강화하는 것입니다. 이것은 잘못된 출력으로 30 분 동안 혼돈 된 후 가비지 출력 파일과 "0"해피 종료 코드를 생성하는 것으로 알려진 레거시 시스템에 있습니다. stderr에 있고, 일부는 stdout에 있으며, 일부는 / dev / null'd에 있습니다.)
Danny Staple

"예를 들어, 다음 예제에서는 foo를 표시하지 않고 모든 ash, bash, pdksh, ksh93 및 zsh가 종료됩니다."BusyBox ash는 이러한 방식으로 작동하지 않으며 스펙에 따라 출력을 표시합니다. (BusyBox 1.19.4로 테스트되었습니다.) 또한로 종료되지 않습니다 set -e; (cd /nonexisting).
dubiousjim

27

(솔루션을 찾았 기 때문에 내 대답) 한 가지 해결책은 항상 중간 변수에 할당하는 것입니다. 이렇게하면 반환 코드 ( $?)가 설정됩니다.

그래서

ABC=`exit 1`
echo $?

그러나 출력합니다 1(또는 set -e존재하는 경우 종료 ).

echo `exit 1`
echo $?

0빈 줄 후에 출력 됩니다. 에코의 리턴 코드 (또는 백틱 출력으로 실행 된 다른 명령)는 0 리턴 코드를 대체합니다.

나는 여전히 중간 변수를 필요로하지 않는 솔루션에 개방적이지만 이것은 나에게 어떤 길을 가져옵니다.


23

OP가 자신의 답변에서 지적했듯이 하위 명령의 출력을 변수에 할당하면 문제가 해결됩니다. 는 $?상처가 남아 있습니다.

그러나 한 가지 경우는 여전히 잘못된 부정 (예 : 명령은 실패하지만 오류가 발생하지 않음), local변수 선언으로 당신을 당혹스럽게 할 수 있습니다 .

local myvar=$(subcommand)것입니다 항상 반환 0!

bash(1) 이것을 지적하십시오 :

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

간단한 테스트 사례는 다음과 같습니다.

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

출력 :

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

고맙습니다. 불일치 VAR=...local VAR=...정말 불쾌했습니다.
Jonny

13

다른 사람들이 말했듯 local이 항상 0을 반환합니다. 해결책은 변수를 먼저 선언하는 것입니다.

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

산출:

$ testcase
False returned false!
$ 

4

명령 대체 실패시 종료하려면 -e다음과 같이 서브 쉘에서 명시 적으로 설정할 수 있습니다 .

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

재미있는 포인트!

나는 전혀 친구 해요 없기 때문에, 그 운 좋게 발견 한 적이 없다 set -e(대신 내가 선호 trap ... ERR:)하지만 이미 테스트 trap ... ERR도 내에서 오류를 잡을 수없는 $(...)(또는 oldfashioned 역 따옴표를).

문제는 (따라서) 여기에서 서브 쉘이 호출되고 -e명시 적으로 현재 쉘을 의미 한다고 생각합니다 .

이 시점에서 염두에 둔 다른 솔루션 만 읽기를 사용하는 것입니다.

 ls -l ghost_under_bed | read name

이것은 던져 ERR지고 -e껍질 과 함께 종료됩니다. 유일한 문제 : 이것은 한 줄의 출력을 가진 명령 (또는 줄을 연결하는 것을 통해 파이프)하는 경우에만 작동합니다.


1
읽기 트릭에 대해 확실하지 않아서 시도하면 변수가 바인딩되지 않습니다. 나는 파이프의 다른 쪽이 효과적으로 서브 쉘이기 때문에 이것이 가능하다고 생각합니다. 이름을 사용할 수 없습니다.
Danny Staple
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.