Linuxのbashスクリプトでエラーをキャッチする方法は?

次のスクリプトを作成しました:

# !/bin/bash # OUTPUT-COLORING red="\e[0;31m" green="\e[0;32m" NC="\e[0m" # No Color # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { cd $1 if [ $? = 0 ] then echo -e "${green}$1${NC}" else echo -e "${red}$1${NC}" fi } # EXE directoryExists "~/foobar" directoryExists "/www/html/drupal" 

スクリプトは機能しますが、私の横にありますエコー、

cd $1 

実行に失敗した場合の出力もあります。

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory 

これをキャッチすることは可能ですか?

コメント

  • 参考までに、これをはるかに簡単に行うこともできます。 test -d /path/to/directory(またはbashの[[ -d /path/to/directory ]])は、指定されたターゲットがディレクトリであるかどうかを示し、静かに実行します。
  • @Patrick、’ディレクトリかどうかをテストするだけで、cdディレクトリに入れることができるかどうかはテストしません。
  • @StephaneChazelasはい。関数名はdirectoryExistsです。
  • 詳細な回答はこちらをご覧ください: Bashスクリプトでエラーを発生させます

回答

スクリプトは実行時にディレクトリを変更するため、スクリプトは機能しません。一連の相対パス名。その後、cdを使用する機能ではなく、ディレクトリの存在を確認するだけでよいとコメントしたため、回答でcdまったく。改訂。 tputman terminfoの色の使用:

#!/bin/bash -u # OUTPUT-COLORING red=$( tput setaf 1 ) green=$( tput setaf 2 ) NC=$( tput setaf 0 ) # or perhaps: tput sgr0 # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { # was: do the cd in a sub-shell so it doesn"t change our own PWD # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then if [ -d "$1" ] ; then # was: echo "${green}$1${NC}" printf "%s\n" "${green}$1${NC}" else # was: echo "${red}$1${NC}" printf "%s\n" "${red}$1${NC}" # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}" fi } 

(編集済みテキスト内のエスケープシーケンスに作用する可能性のある問題のあるechoの代わりに、より脆弱なprintfを使用します。)

コメント

  • これにより、ファイル名に円記号が含まれている場合の問題も修正されます(xpg_echoがオンになっていない場合)。

回答

set -eを使用して、エラー時終了モードを設定します。単純なコマンドがゼロ以外のステータス(失敗を示す)を返す場合、シェルが終了します。

set -eが常に起動するとは限らないことに注意してください。テスト位置のコマンドは失敗する可能性があります(例:if failing_commandfailing_command || fallback)。サブシェル内のコマンドは、親ではなく、サブシェルを終了するだけです。set -e; (false); echo foofoo

または、さらに、bash(およびkshとzshですが、プレーンshではありません)、コマンドがゼロ以外のステータスを返した場合に実行されるコマンドを、ERRトラップで指定できます。 trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR(false); …のような場合、ERRトラップはサブシェルで実行されるため、「親を終了させることはできません。

コメント

  • 最近、少し実験して、トラップを使用せずに適切なエラー処理を簡単に実行できる||の動作を修正する便利な方法を発見しました。を参照してください。 私の答え。その方法についてどう思いますか?
  • @ sam.kozin私はしません’回答を詳細に確認する時間がないので、原則として良さそうです。移植性以外に、ksh / bash / zsh ‘のERRトラップに勝る利点は何ですか?
  • 関数の実行前に設定された別のトラップを上書きするリスクがないため、おそらく唯一の利点は構成可能性です。’これは’後で他のスクリプトから調達して使用する、いくつかの一般的な関数を作成しています。別の利点ERR疑似信号がすべての主要なシェルでサポートされているため、それほど重要ではありませんが、POSIXとの完全な互換性がある可能性があります。レビューありがとうございます! =)
  • @ sam.kozin以前のコメントを書くのを忘れました:これをコードレビューに投稿して、 チャットルームのリンク。
  • 提案をありがとうございます。’フォローしてみますそれ。 ‘コードレビューについて知りませんでした。

回答

@Gilles “の回答を拡張するには

確かに、set -eは機能しませんサブシェルで実行した場合でも、コマンドの後に||演算子を使用するとコマンド内。たとえば、これは機能しません:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_1.sh: line 16: some_failed_command: command not found # <-- inner # <-- outer set -e outer() { echo "--> outer" (inner) || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

ただし、||演算子は、クリーンアップの前に外部関数から戻らないようにするために必要です。

これを修正するために使用できる小さなトリックがあります。バックグラウンドで内部コマンドを実行し、すぐにそれを待つ。waitビルトインは、内部コマンドの終了コードを返します。これで、iv id = “8a710934b9″の後に||を使用しています。 >

、内部関数ではないため、set -eは後者の内部で適切に機能します:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_2.sh: line 27: some_failed_command: command not found # --> cleanup set -e outer() { echo "--> outer" inner & wait $! || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

これは、このアイデアに基づいて構築された汎用関数です。localキーワード、つまり、すべてのlocal x=yx=y

に置き換えます。 # [CLEANUP=cleanup_cmd] run cmd [args...] # # `cmd` and `args...` A command to run and its arguments. # # `cleanup_cmd` A command that is called after cmd has exited, # and gets passed the same arguments as cmd. Additionally, the # following environment variables are available to that command: # # - `RUN_CMD` contains the `cmd` that was passed to `run`; # - `RUN_EXIT_CODE` contains the exit code of the command. # # If `cleanup_cmd` is set, `run` will return the exit code of that # command. Otherwise, it will return the exit code of `cmd`. # run() { local cmd="$1"; shift local exit_code=0 local e_was_set=1; if ! is_shell_attribute_set e; then set -e e_was_set=0 fi "$cmd" "$@" & wait $! || { exit_code=$? } if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then set +e fi if [ -n "$CLEANUP" ]; then RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@" return $? fi return $exit_code } is_shell_attribute_set() { # attribute, like "x" case "$-" in *"$1"*) return 0 ;; *) return 1 ;; esac }  

使用例:

 #!/bin/sh set -e # Source the file with the definition of `run` (previous code snippet). # Alternatively, you may paste that code directly here and comment the next line. . ./utils.sh main() { echo "--> main: $@" CLEANUP=cleanup run inner "$@" echo "<-- main" } inner() { echo "--> inner: $@" sleep 0.5; if [ "$1" = "fail" ]; then oh_my_god_look_at_this fi echo "<-- inner" } cleanup() { echo "--> cleanup: $@" echo " RUN_CMD = "$RUN_CMD"" echo " RUN_EXIT_CODE = $RUN_EXIT_CODE" sleep 0.3 echo "<-- cleanup" return $RUN_EXIT_CODE } main "$@"  

例の実行:

 $ ./so_3 fail; echo "exit code: $?" --> main: fail --> inner: fail ./so_3: line 15: oh_my_god_look_at_this: command not found --> cleanup: fail RUN_CMD = "inner" RUN_EXIT_CODE = 127 <-- cleanup exit code: 127 $ ./so_3 pass; echo "exit code: $?" --> main: pass --> inner: pass <-- inner --> cleanup: pass RUN_CMD = "inner" RUN_EXIT_CODE = 0 <-- cleanup <-- main exit code: 0  

このメソッドを使用するときに注意する必要があるのは、comから行われるシェル変数のすべての変更だけです。 runに渡したコマンドは、コマンドがサブシェルで実行されるため、呼び出し元の関数に伝播されません。

回答

catchの正確な意味はわかりませんが、報告して続行してください。それ以上の処理を中止しますか?

cdは失敗時にゼロ以外のステータスを返すため、次のことができます。

cd -- "$1" && echo OK || echo NOT_OK 

失敗した場合は単に終了できます:

cd -- "$1" || exit 1 

または、独自のメッセージをエコーして終了します:

cd -- "$1" || { echo NOT_OK; exit 1; } 

失敗時にcdによって提供されるエラーを抑制します:

cd -- "$1" 2>/dev/null || exit 1 

標準では、コマンドはSTDERR(ファイル記述子2)にエラーメッセージを表示する必要があります。したがって、2>/dev/nullは、STDERRを/dev/nullで知られる「ビットバケット」にリダイレクトすると言います。

(忘れないでください変数を引用し、cdのオプションの終わりをマークします。

コメント

  • @Stephane Chazelasの引用とオプションの終了の通知のポイントはよく理解されています。編集していただきありがとうございます。

回答

実際あなたの場合、ロジックを改善できると思います。

cdの代わりに、存在するかどうかを確認し、存在するかどうかを確認してから、ディレクトリに移動します。

if [ -d "$1" ] then printf "${green}${NC}\\n" "$1" cd -- "$1" else printf "${red}${NC}\\n" "$1" fi 

ただし、発生する可能性のあるエラーを解決することが目的の場合は、cd -- "$1" 2>/dev/nullですが、これにより、将来のデバッグが困難になります。テストするかどうかを確認できます。フラグ:ドキュメントの場合はバッシュ

コメント

  • この回答は失敗します$1変数を引用すると、その変数が失敗します空白またはその他のシェルメタ文字が含まれています。また、ユーザーがcdにアクセスする権限を持っているかどうかも確認できません。
  • 実際には、特定のディレクトリが存在するかどうかを確認しようとしていましたが、必ずしもそのディレクトリにcdする必要はありません。 。しかし、’よくわからなかったので、cdしようとするとエラーが発生するのではないかと思ったので、キャッチしてみませんか? ‘ ‘がまさに必要なものであるかどうか[-d $ 1]がわかりませんでした。だから、どうもありがとうございました! (I ‘ mはJavaをプロラムするために使用され、ifステートメントのディレクトリをチェックすることはJavaでは一般的ではありません)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です