1. 序章

エラーはLinux管理とBashスクリプトの通常の部分です。エラーは特別で潜在的に重要なシステム状態を示します。 それにもかかわらず、いくつかのエラーの処理をスキップしたい場合があります。

このチュートリアルでは、Bashでエラーとそれらを無視する方法について説明します。 特に、エラーとは何かを定義し、それらを分類します。 次に、それらが一般的に、およびBashでどのように識別されるかを確認します。 その後、エラーを無視して抑制する方法をいくつか示します。 最後に、いくつかの発言は特定のケースを示しています。

このチュートリアルのコードは、GNU Bash5.1.4を使用したDebian11(Bullseye)でテストしました。 これはPOSIXに準拠しており、そのような環境で機能するはずです。

2. エラー

エラーが発生することがよくありますが、例外的な状態です。 このため、エラーの別名は例外です。 例外は、目的の目標を達成しなかったソフトウェアの状態を表します。

状況が異なるため、エラーを原因別に大まかに並べ替えることができます。

2.1. ハードウェアエラー

ソフトウェア製品の最低レベルは、その上で実行されるハードウェアです。 電気的および機械的な問題はこの記事の範囲外ですが、それらが一般的にソフトウェア表現を持っているという事実は重要です。

次のシナリオを検討してください。

  • ハードディスクに障害が発生すると、読み取りまたは書き込みエラーが発生します
  • 物理メモリが破損しており、さまざまな理由でアプリケーションがクラッシュします
  • プロセッサが過熱し、再起動を強制する

重要なことに、ハードウェアエラーはソフトウェアを介して避けられないことがよくあります。 一部、同じことが次のエラーカテゴリにも当てはまります。

2.2. オペレーティングシステムエラー

オペレーティングシステム(OS)はすべてのアプリケーションのベースであるため、OSエラーはそれらに簡単に影響を与える可能性があります。 広い意味では、オペレーティングシステムは、カーネル、デバイスドライバー、API、ユーザーインターフェイス、およびファイルシステムの組み合わせであると見なされます。 したがって、これらのコンポーネントのエラーはすべてOSエラーです。

実際、オペレーティングシステムはそれ自体がソフトウェア製品です。 とはいえ、ハードウェアと同様に、それらのエラーは無視するのが難しいです。

たとえば、OSファイルが破損していると、アプリケーションを実行できなくなる可能性があります。 別のケースでは、ドライバーの欠落または構成ミスが発生する可能性があり、デバイスアクセス中にエラーが発生します。 このような状況が原因で、アプリケーションは、そもそもエラーを引き起こさないにもかかわらず、エラーをスローする可能性があります。

2.3. アプリケーションエラー

もちろん、OSが問題なく機能していても、アプリケーションがOSを誤用したり、誤って構成したりする可能性があります。

これは多くの場合、ユーザー入力が悪いことが原因です。 たとえば、存在しないファイルにアクセスしようとすると、アプリケーション例外が発生します。 また、OS自体が破損し、有効な操作ができなくなる場合もあります。

一方、ユーザー入力を誤って処理しました。 アプリケーションが問題を報告する代わりに、OSがプロセスによる不正なアクションの実行を防止する場合があります。 ユーザーが他の誰かのファイルを削除しようとするシナリオを想像してみてください。 ファイルシステムのアクセス許可は、アプリケーションのチェックに関係なく、OSによって適用されます。

さらに、アプリケーションは意図的に誤動作する可能性がありますが、予期しない環境の結果としても発生する可能性があります。 つまり、アプリケーション自体に不良または不完全なコードが含まれている可能性があります。

2.4. プログラミングエラー

これまでに説明したエラーは主に私たちの制御の及ばないものでしたが、ソースコードへのアクセスによってそれが変わる可能性があります。 特に、開発中にチェックと境界を実装できます。これにより、実行時の問題を防ぐことができます。

もちろん、開発には独自の課題があります

  • プログラミング言語のルールが破られている構文エラー
  • 無限ループ、不良ブール条件、不正なフローなどの論理エラー
  • メモリの誤割り当てなどのランタイムエラー

これらのエラーがどの程度予防可能、回避可能、無視できるかは、プログラミング言語だけでなく、その詳細によっても異なります。

エラーに対処するには、まずエラーが発生したことを知る必要があります。 どのように? それは起源に依存します。

3. エラーコード

問題の診断は簡単ではありません。 OSエラーは、起動時に報告される可能性があります。 ハードウェアエラーは、悪臭によって示される場合もあります。

一方、アプリケーションには、ステータスまたは終了コードがあります。 終了コードにより、アプリケーションのユーザーはその最終ステータスを知ることができます。 重要なのは、エラーがない場合に広く受け入れられているステータス番号は0です。

エラーにはテキストによる説明が伴うことがよくありますが、常にそうであるとは限りません。 一般的なPOSIXエラーコードとその説明を次に示します。

  • EBADF = 9、不正なファイル記述子
  • EFAULT = 14、不正なアドレス
  • EIO = 5、I/Oエラー

アプリケーションは説明を省略してエラー(コード)を返すことができます。

本質的に、開発者はさまざまな条件や状況を検討して計画することができます。 ただし、考えられるすべてのシナリオを網羅できることはめったにありません。 重要なのは、OSが不正なプロセスを終了する可能性があることです。これにより、終了コードも発生します。

以下に示すように、Bashでエラーが発生すると、終了コードが発生する可能性もあります。

4. Bashエラー

実行する前に、Bashスクリプト行を解釈する必要があります。 したがって、各コマンド構成には戻りコードがあります。

この戻りコードとその他の条件に基づいて、Bashは特定のコマンド構成が失敗したかどうかを判別します。

cd (ディレクトリの変更)の例を次に示します。

$ cd DoesNotExist
-bash: cd: DoesNotExist: No such file or directory
$ echo $?
1

コマンドがゼロ以外のステータスで終了する場合、コマンドは失敗しました。 これは通常、直接的な影響はありませんが、エラーが検出されない場合は有害になる可能性があります。 隠れた問題を回避するために、 -eフラグを指定してsetを使用します。これを使用すると、コマンド構成が失敗すると、ゼロ以外のコードですぐに終了します。 set -e は実稼働環境で推奨されることが多いため、突然の終了は避けられないように思われるかもしれません。 ただし、それらを回避する方法があります。これについては、以下で説明します。

5. Bashエラー処理

OSとハードウェアは、アプリケーションを保持するスケルトンとして想像できます。 スケルトンが危険にさらされている場合、これらのアプリケーションは正しく機能しない可能性があります。 彼らは状況を改善するために必要なツールを持っていません。 アプリケーションは問題をチェックすることしかできません。 したがって、これらのシナリオではなく、その結果のみを扱います。

開発者として、私たちは主に対処できる問題に関心を持っています。 呼び出したソフトウェアの実行ステータスコードを処理しながら、独自の終了コードとプログラミングエラーを制御します。 例として、次のスクリプトを取り上げます。

read input
if [[ "$input" == 'hello' ]]; then
exit 0
fi
exit 1

read を使用して、ユーザーからの入力を取得します。 その後、入力は事前定義された文字列と比較されます。 スクリプトがコースを実行している限り、独自のステータスコードを返します。 ただし、スクリプトが途中で強制終了された場合、この事実はPOSIXのステータス137で示されます。

スクリプトがgreetと呼ばれた場合。 sh、これがPiping「hello」入力の後に終了コードを確認する方法です。

$ echo hello | bash greet.sh
$ echo $?
0

Bashでは、 $?変数は最後のコマンドのステータスコードを格納します。 実際、これはスクリプト内のすべてのアプリケーション呼び出しに有効です。

ゼロはBashの終了コードとして成功を示すため、論理演算子を使用して呼び出しを連鎖させることができます

$ echo bye | bash greet.sh && echo Success. || echo Failure.
Failure.

スクリプトに「さようなら」をパイプした後、ゼロ以外の(失敗した)エラーコードが返されます。 これは、 && (および)構成はスキップされますが、 || (または)がトリガーされます。

6. Bashのエラーを無視する

最も重要なことは、その中のパスに関係なく、行全体の終了コードがゼロ(成功)であるということです。

$ echo bye | bash greet.sh && echo Success. || echo Failure.
Failure.
$ echo $?
0

実際、これは、特定のコマンドのスクリプトの突然の終了を防ぐため、set-eが機能しているときに便利です。 このメソッドは、次のコマンドが実行された場合にのみ機能することに注意してください && || 成功を返します。 echo を使用しますが、サイレントサクセスの代替オプションは、(nullユーティリティ)です。

同じことを実現する別の方法は、感嘆符の構文を使用することです。

set -e
! echo bye | bash greet.sh
echo Success.

set -e 環境でエラーが発生した場合でも、上記のスクリプトを実行しても強制的に終了することはありません。

さらに別のオプションは、正常に実行されるコマンドへのpipeingです。

set -e
echo bye | bash greet.sh | echo $?
echo Success.

echo $? 1 を示していますが、 0 を返すため、成功します。

最後に、コマンドを実行する前に -e 設定をオフにして、次の後に再度有効にすることもできます。

set -e
# code here
set +e
echo bye | bash greet.sh
set -e
echo Success.

さらに、失敗したコマンドが出力する可能性のあるstdoutおよびstderrエラーメッセージを抑制することもできます。

$ badcommand
-bash: badcommand: command not found
$ badcommand >/dev/null 2>&1

終了コード処理と出力の無視を組み合わせて使用すると、サイレントに失敗するコマンドを実行できます。

説明したすべてのメソッドは、中かっこで囲むことにより、コードのブロック全体に適用できます

set -e
! {
echo First command.
badcommand
echo Third command.
}
echo Success.

次に、いくつかの例外的なケースを見ていきます。

7. 備考

ほとんどのシナリオでは、エラーが発生する可能性がある時期を確実に予測できます。 ただし、一部のコマンドには固有の例外が組み込まれています。

7.1. ヒアドキュメント

1つの構成は、readヒアドキュメントを使用しています。

$ read -d '' var << EOI
Line one.
Line two.
EOI
$ echo $?
1

エラーコード1? エラーは何ですか? 区切り文字が検出されない場合、読み取りはゼロ以外のエラーコードを返すことがわかりました。 この場合、区切り文字( -d )は空の文字列()、つまりNULです。 ただし、ヒアドキュメントは NUL で終わらないため、ゼロ以外(失敗)を返します。

7.2. Pipefail

言及する価値のあるもう1つの落とし穴は、setpipefailオプションです。 有効にすると、パイプラインの戻りコードは、ゼロ以外の状況で終了する最後のコマンドの値になります。 これは、パイプに沿って障害が発生すると、パイプチェーンとコマンドが即座に終了することを意味します。

set -e
badcommand | echo
# script does not exit
set -o pipefail
badcommand | echo
# script exits

最初に、ゼロ以外の終了コードを成功したコマンドにパイプすると、全体的に成功します。 pipefail を設定した後、同じ行でエラーが生成され、スクリプトが強制的に終了します。

7.3. 未定義の変数

さらに別のsetフラグは-uです。 -uを設定すると、未定義の変数への参照が強制的にエラーになります

set -e
set -u
echo $undefined
# script exits

通常の方法に加えて、このエラーを回避できる特別な構成があります: $ {undefined:-SOME_VALUE}。 このように変数を逆参照すると、変数値が返されるか、未定義の場合はSOME_VALUEが返されます。 したがって、未定義の値を使用しないため、 set-uでエラーが生成されません。

7.4. コンパイルと解釈

プログラミング構文エラーに関しては、ソースコードファイルの処理中にエラーが発生した場合、コンパイラとインタプリタはエラーコードを返します。

$ touch empty.c
$ gcc empty.c
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
$ echo $?
1
$ echo [ > bad.sh
$ bash bad.sh
empty: line 1: [: missing `]'
$ echo $?
2

7.5. トラップ

エラーを無視するように特別に設計されていませんが、trapを使用して突然の終了を回避できます。 ある意味で、トラップはエラーのデフォルト処理を防止または追加します。 罠にはまりすぎないように、簡単なスクリプト例を次に示します。

set -e
trap 'echo "Inside trap"; echo "Line $LINENO."' ERR
echo 'Before bad command.'
badcommand
echo 'Unreachable.'

このスクリプトの出力が示すように、スクリプトが終了またはエラーになる前に、複数のコマンドを実行できます。

$ bash script.sh
Before bad command.
./script.sh: line 5: badcommand: command not found
Inside trap.
Line 5.

このセクションのエラーを回避するために、前のセクションで説明した方法を利用できます。

8. 概要

この記事では、Bashのエラーについて説明しました。

結論として、Bashには標準的ですが多様なエラーメカニズムがあり、エラーを処理して無視するために使用できます。