bash - 終了 - シェルスクリプト 例外処理




BashにTRY CATCHコマンドがありますか? (7)

BashにTRY CATCHコマンドがありますか?

いいえ。

Bashには、多くのプログラミング言語で見つかるほど多くの贅沢品がありません。

bashにはtry/catchはありません。 しかし、 &&または||を使用して同様の動作を実現できます。 。

||を使う :

command1が失敗した場合、 command2は次のように実行されます

command1 || command2

同様に、 &&を使用して、 command1が成功command1command2が実行されます

try/catchの最も近い近似は次のとおりです

{ # try

    command1 &&
    #save your output

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

また、bashにはいくつかのエラー処理メカニズムが含まれています

set -e

単純なコマンドが失敗した場合、スクリプトを直ちに停止します。 私はこれがデフォルトの動作であったはずだと思います。 このようなエラーはほとんど常に予期せぬことを示しているので、次のコマンドを実行し続けるのは本当に「普通」ではありません。

そして、なぜそうでif...elseどうして? それはあなたの親友です。

私はシェルスクリプトを書いており、ターミナルアプリがインストールされていることを確認する必要があります。 私はTRY / CATCHコマンドを使用して、これ以上の方法がない限りこれを実行します。


bashがエラー状態を検出した場合( -eフラグを設定しない限り)、 bashは実行中の実行を中止しません。 try/catchを提供するプログラミング言語は、この特別な状況のために "救済"を禁止するためにこれを行います(したがって、通常 "例外"と呼ばれます)。

bashでは、問題のコマンドだけが0より大きい終了コードで終了し、エラー状態を示します。 あなたはもちろんそのことを確認することができますが、何の自動救済もないので、 try / catchは理にかなっていません。 そのコンテキストが欠けているだけです。

しかし、あなたが決めた時点で終了することができるサブシェルを使用て、 救済策をシミュレートすることができます:

(
  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代わりにコマンドを試してみてください。 失敗した場合 (終了コードが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)に制限され、適切な例外オブジェクトは使用できません。

あなたの擬似例外と一緒に渡すためにもっと情報が必要な場合は、サブシェルの標準出力を使うことができますが、これはちょっと複雑ですし、別の質問かもしれません;-)

上記の-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"
)
...

おそらく動作する類似のソリューションがたくさんあります。 以下は、try / catchを実行するための簡単で実用的な方法であり、コメントに説明が記載されています。

#!/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は適切な言語サポートのtry / catch構文を持っていません。 -e引き数を指定してbashを起動するか、コマンド内に0以外の終了コードがある場合は、スクリプト内でset -eを使用してbashプロセス全体を中止することができます。 ( set +eset +e 、失敗したコマンドを一時的に許可することもできます)。

したがって、try / catchブロックをシミュレートする1​​つの手法は、 -e有効にして作業を実行するサブプロセスを起動することです。 メインプロセスで、サブプロセスの戻りコードを確認します。

Bashはheredoc文字列をサポートしているので、これを処理するために2つの別々のファイルを書く必要はありません。 以下の例では、TRY heredocは、 -e enabledを使用して別の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ブロックではありませんが、それはあなたのために同様のかゆみを引っかくかもしれません。


私がここで見つけたいくつかの答えに基づいて、私は私自身のプロジェクトのための小さなヘルパーファイルを作りました:

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__!"
}

このコードは私のbashの定型文/フレームワークの一部です。 それはさらに、バックトレースと例外(といくつかの他の素敵な機能)を伴うエラー処理のようなもので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にあります







error-handling