Pipefail과 비슷한 방식으로 백틱 실패 시 bash를 종료하려면 어떻게 해야 합니까?

Pipefail과 비슷한 방식으로 백틱 실패 시 bash를 종료하려면 어떻게 해야 합니까?

따라서 나는 오류가 발생하지 않도록 bash 스크립트를 최대한 강화하는 것을 좋아합니다(Python/Ruby와 같은 언어에 대한 위임이 불가능한 경우).

이러한 정신으로 다음 내용을 포함하는 strict.sh가 있습니다.

set -e
set -u
set -o pipefail

다른 스크립트에 소스를 추가합니다. 그러나 파이프라인 오류가 발생하는 동안:

false | echo it kept going | true

수신되지 않습니다:

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

출력은 다음과 같습니다

출력은 ''

False는 0이 아닌 상태를 반환하고 표준 출력을 반환하지 않습니다. 파이프라인에서는 실패하지만 이 오류는 포착되지 않습니다. 실제로 나중에 사용하기 위해 변수에 저장된 계산이고 값이 공백으로 설정된 경우 나중에 문제가 발생할 수 있습니다.

그렇다면 bash가 백틱 내에서 0이 아닌 반환 코드를 종료하기에 충분한 이유로 처리하도록 하는 방법이 있습니까?

답변1

사용되는 정확한 언어단일 UNIX 사양설명하기의미set -e예:

이 옵션이 켜져 있으면간단한 명령나열된 이유로 인해 실패했습니다.쉘 오류의 결과또는 종료 상태 값 >0을 반환하고 [조건부 또는 부정 명령]이 아닌 경우 쉘은 즉시 종료되어야 합니다.

그러한 명령이서브쉘. 실용적인 관점에서 서브쉘이 할 수 있는 일은 종료하고 상위 쉘에 0이 아닌 상태를 반환하는 것뿐입니다. 상위 쉘이 순차적으로 종료되는지 여부는 0이 아닌 이 상태가 상위 쉘에서 실패하는 간단한 명령으로 변환되는지 여부에 따라 달라집니다.

당신이 가진 것은 문제가 있는 상황입니다:명령 대체. 이 상태는 무시되므로 상위 쉘이 종료되지 않습니다. ~처럼당신은 발견했습니다, 종료 상태에 대해 생각하는 한 가지 방법은 간단한 명령 대체를 사용하는 것입니다.: 그 다음에할당의 종료 상태는 할당의 마지막 명령 대체의 종료 상태입니다..

마지막 대체 상태만 고려되므로 단일 명령 대체가 있는 경우에만 예상대로 작동합니다. 예를 들어, 다음 명령은 성공했습니다(표준 및 내가 본 모든 구현에 따르면).

a=$(false)$(echo foo)

주의가 필요한 또 다른 상황은 다음과 같습니다.명시적 서브쉘: (somecommand). 위의 설명에 따르면 서브쉘이 0이 아닌 상태를 반환할 수도 있지만 이는 상위 쉘에서는 간단한 명령이 아니기 때문에 상위 쉘이 계속 진행되어야 합니다. 사실, 내가 아는 모든 쉘은 이 시점에서 부모를 반환합니다. 이는 현재 디렉토리 변경과 같은 작업을 로컬로 유지하기 위해 괄호를 사용하는 등 많은 경우에 유용하지만 (cd /some/dir && somecommand)서브쉘에서 닫히거나 서브쉘이 다음과 같은 방식으로 0이 아닌 상태를 반환하는 경우 사양을 위반합니다 set -e. 예를 들어, !true 명령에 사용됩니다 . 예를 들어 This should be displayed다음 예에서는 ash, bash, pdksh, ksh93 및 zsh가 모두 표시되지 않고 종료됩니다.

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파이프의 마지막 요소에서 부정을 닫거나 사용하면 ATT ksh 및 zsh 이외의 쉘이 종료되지 않는 방식으로 0이 아닌 상태가 반환됩니다.

배쉬의pipefailset -e옵션을 사용하면 파이프의 요소가 0이 아닌 상태를 반환하는 경우 파이프가 즉시 종료됩니다.

set -e문제를 더 복잡하게 만들기 위해 bash는 명령 대체에서 닫혀 있지만아니요bash가 POSIX 모드에 있지 않은 경우(즉 , bash가 시작될 때 환경에 설정되거나 bash가 로 호출됨 ) 일반 서브셸(즉, 내부 ``또는 $()내부에는 없음 ) . 이 경우 현재 설정은 상위에서 가져옵니다. shell 상속된 호출 시간(명령 대체 또는 서브셸 여부).()set -o posixPOSIXLY_CORRECTshe

불행하게도 이것이 시사하는 바는 POSIX 사양이 옵션을 지정하는 작업을 제대로 수행하지 못한다는 것입니다 -e. 다행스럽게도 기존 쉘의 동작은 대부분 일관됩니다.

답변2

(솔루션을 찾은 내 자신의 질문에 대답) 한 가지 해결책은 항상 중간 변수에 할당하는 것입니다. 이는 반환 코드( $?)를 설정합니다.

그래서

ABC=`exit 1`
echo $?

출력하지만(또는 1존재하는 경우 종료) 다음과 같습니다.set -e

echo `exit 1`
echo $?

0빈 줄 다음에 출력됩니다. echo의 반환 코드(또는 백틱 출력으로 실행되는 다른 명령)는 0 반환 코드를 대체합니다.

나는 여전히 중간 변수가 필요하지 않은 솔루션에 열려 있지만 이것이 나에게 약간의 도움을 주었습니다.

답변3

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

답변4

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

function testcase()
{
    local MYRESULT

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

    return 0
}

산출:

$ testcase
False returned false!
$ 

관련 정보