[bash] Bash에 TRY CATCH 명령이 있습니까?

쉘 스크립트를 작성 중이며 터미널 앱이 설치되어 있는지 확인해야합니다. 더 깔끔한 방법이 없다면 TRY / CATCH 명령을 사용 하여이 작업을 수행하고 싶습니다.



답변

Bash에 TRY CATCH 명령이 있습니까?

아니.

배쉬에는 많은 프로그래밍 언어에서 찾을 수있는만큼 사치가 없습니다.

try/catchbash 에는 없습니다 . 그러나, 하나는 사용하여 비슷한 동작을 달성 할 수있다 &&||.

사용 ||:

경우 command1실패 후 command2다음과 같이 실행

command1 || command2

마찬가지로, 사용 &&, command2경우에 실행 command1성공

가장 가까운 근사값은 try/catch다음과 같습니다.

{ # try

    command1 &&
    #save your output

} || { # catch
    # save log for exception 
}

또한 bash에는 몇 가지 오류 처리 메커니즘이 포함되어 있습니다.

set -e

간단한 명령이 실패하면 스크립트가 중지됩니다.

또한 왜 안되나요 if...else? 가장 친한 친구입니다.


답변

여기에서 찾은 몇 가지 답변을 바탕으로 내 프로젝트의 소스로 사용할 작은 도우미 파일을 만들었습니다.

trycatch.sh

#!/bin/bash

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function throwErrors()
{
    set -e
}

function ignoreErrors()
{
    set +e
}

다음은 사용중인 모습의 예입니다.

#!/bin/bash
export AnException=100
export AnotherException=101

# start with a try
try
(   # open a subshell !!!
    echo "do something"
    [ someErrorCondition ] && throw $AnException

    echo "do something more"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # automaticatly end the try block, if command-result is non-null
    echo "now on to something completely different"
    executeCommandThatMightFail

    echo "it's a wonder we came so far"
    executeCommandThatFailsForSure || true # ignore a single failing command

    ignoreErrors # ignore failures of commands until further notice
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
    executeCommand3ThatFailsForSure

    echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
    # now you can handle
    case $ex_code in
        $AnException)
            echo "AnException was thrown"
        ;;
        $AnotherException)
            echo "AnotherException was thrown"
        ;;
        *)
            echo "An unexpected exception was thrown"
            throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
        ;;
    esac
}


답변

bash에서 거의 완벽한 try & catch 구현을 개발하여 다음과 같은 코드를 작성할 수 있습니다.

try
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

try-catch 블록을 자체 안에 중첩시킬 수도 있습니다!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

코드는 내 배쉬 보일러 플레이트 / 프레임 워크 의 일부입니다 . 또한 역 추적 및 예외를 포함한 오류 처리 (및 기타 유용한 기능)를 사용하여 try & catch 아이디어를 확장합니다.

try & catch를 담당하는 코드는 다음과 같습니다.

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # BACKWARDS COMPATIBILE WAY:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # so that we may continue with a "catch"
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

자유롭게 사용하고 포크하고 기여하십시오 -GitHub있습니다 .


답변

당신은 사용할 수 있습니다 trap:

try { block A } catch { block B } finally { block C }

다음과 같이 번역됩니다.

(
  set -Ee
  function _catch {
    block B
    exit 0  # optional; use if you don't want to propagate (rethrow) error to outer shell
  }
  function _finally {
    block C
  }
  trap _catch ERR
  trap _finally EXIT
  block A
)


답변

아마도 비슷한 많은 솔루션이 있습니다. 아래는 주석에 설명과 함께 시도 / 캐치를 수행하는 간단하고 효과적인 방법입니다.

#!/bin/bash

function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}

# this subshell is a scope of try
# try
(
  # this flag will make to exit from current subshell on any error
  # inside it (all functions run inside will also break on any error)
  set -e
  a
  b
  # do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
  echo "We have an error"
  # We exit the all script with the same error, if you don't want to
  # exit it and continue, just delete this line.
  exit $errorCode
fi


답변

bash( -e플래그 를 설정하지 않는 한) 오류 상태를 감지 한 경우 실행중인 실행을 중단하지 않습니다 . 이러한 특수 상황 ( “일반적으로”예외 “)으로 인해”베일 아웃 ” try/catch방지 하기 위해 이를 제공하는 프로그래밍 언어가이를 수행합니다 .

에서 bash, 대신, 문제의 유일한 명령은 오류 상태를 나타내는, 0보다 종료 코드의 큰 함께 종료됩니다. 당신은 물론 그 확인할 수 있지만 자동 없기 때문에 구제 아무것도를하는 시도 / 캐치는 이해가되지 않습니다. 그 맥락이 부족합니다.

그러나 결정한 시점에서 종료 될 수있는 하위 쉘을 사용하여 제거 를 시뮬레이션 할 수 있습니다.

(
  echo "Do one thing"
  echo "Do another thing"
  if some_condition
  then
    exit 3  # <-- this is our simulated bailing out
  fi
  echo "Do yet another thing"
  echo "And do a last thing"
)   # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
  echo "Bail out detected"
fi

대신의 some_condition와 함께 if당신은 또한 단지 명령을 시도 할 수 있고, 경우에 그것은 실패 (0이 아닌 종료 코드의 이상을 가지고), 구제 :

(
  echo "Do one thing"
  echo "Do another thing"
  some_command || exit 3
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

불행히도이 기술을 사용하면 255 개의 다른 종료 코드 (1..255)로 제한되며 적절한 예외 개체를 사용할 수 없습니다.

시뮬레이션 된 예외와 함께 전달하기 위해 더 많은 정보가 필요한 경우 서브 쉘의 stdout을 사용할 수 있지만 약간 복잡하고 또 다른 질문입니다 😉

위에서 언급 한 -e플래그를 쉘에 사용하면 명시 적 exit진술을 제거 할 수도 있습니다 .

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...


답변

모두가 말했듯이 bash는 언어 지원 try / catch 구문이 없습니다. 명령에 종료 코드가 0이 아닌 경우 -e인수 와 함께 bash를 시작 하거나 set -e스크립트 내에서 사용 하여 전체 bash 프로세스를 중단 할 수 있습니다. ( set +e실패한 명령을 일시적으로 허용 할 수도 있습니다 .)

따라서 try / catch 블록을 시뮬레이트하는 한 가지 기술은 하위 프로세스를 시작하여 -e활성화 된 작업을 수행하는 것입니다 . 그런 다음 기본 프로세스에서 하위 프로세스의 리턴 코드를 확인하십시오.

Bash는 heredoc 문자열을 지원하므로이를 처리하기 위해 두 개의 별도 파일을 작성할 필요가 없습니다. 아래 예제에서 TRY heredoc은 -e활성화 된 별도의 bash 인스턴스에서 실행 되므로 명령이 0이 아닌 종료 코드를 반환하면 하위 프로세스가 중단됩니다. 그런 다음 주 프로세스로 돌아가서 catch 블록을 처리하기 위해 리턴 코드를 확인할 수 있습니다.

#!/bin/bash

set +e
bash -e <<TRY
  echo hello
  cd /does/not/exist
  echo world
TRY
if [ $? -ne 0 ]; then
  echo caught exception
fi

올바른 언어 지원 try / catch 블록은 아니지만 비슷한 가려움증이 생길 수 있습니다.