[インデックス 12751] ファイルの概要
このコミットは、Go言語プロジェクトのsrc/run.bash
スクリプトに対する小さな修正であり、特にdoc/codewalk
ディレクトリ内のコードウォーク関連のビルドプロセスにおいて、スクリプトの堅牢性を向上させることを目的としています。具体的には、set -e
コマンドを新しいコードウォークブロックに追加することで、コマンドが失敗した場合にスクリプトが即座に終了するように変更されています。これにより、潜在的なエラーが検出されずに後続の処理が実行されることを防ぎ、ビルドプロセスの信頼性が向上します。
コミット
- コミットハッシュ:
072646cd172137bacf62008e4aa0f28fd7d58e95
- Author: Rob Pike r@golang.org
- Date: Mon Mar 26 17:03:04 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/072646cd172137bacf62008e4aa0f28fd7d58e95
元コミット内容
run.bash: set -e in new codewalk block
Otherwise we won't fail if something goes wrong.
This shell programming stuff is tricky.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5905062
変更の背景
この変更の背景には、シェルスクリプトの実行におけるエラーハンドリングの重要性があります。元のrun.bash
スクリプトの特定のブロック(特にdoc/codewalk
関連の処理)では、内部で実行されるコマンドが失敗した場合でも、スクリプト全体が終了せずに処理を続行してしまう可能性がありました。これは、ビルドプロセスやテスト実行において、エラーがサイレントに無視され、誤った結果や不完全な成果物が生成されるリスクを意味します。
コミットメッセージにある「Otherwise we won't fail if something goes wrong. This shell programming stuff is tricky.」という記述は、この問題意識を明確に示しています。シェルスクリプトは、その性質上、コマンドの連続実行が基本であり、各コマンドの終了ステータスを適切にチェックしないと、予期せぬ動作を引き起こすことがあります。このコミットは、このような潜在的な問題を回避し、スクリプトの信頼性と堅牢性を高めるために行われました。
前提知識の解説
シェルスクリプトと終了ステータス
シェルスクリプトでは、実行される各コマンドは終了時に「終了ステータス(exit status)」を返します。
- 0: コマンドが成功したことを示します。
- 0以外: コマンドが失敗したことを示します。通常、1は一般的なエラー、2は誤った使用法など、特定の意味を持つことがあります。
デフォルトでは、シェルスクリプトは途中のコマンドが0以外の終了ステータスを返しても、その後のコマンドの実行を続けます。これは、エラーが発生してもスクリプトが停止しないことを意味し、デバッグを困難にしたり、エラーが連鎖してさらに大きな問題を引き起こしたりする可能性があります。
set -e
コマンド
set -e
は、Bashなどのシェルで利用できるオプションの一つで、「exit immediately」を意味します。このコマンドがスクリプト内で有効になっている場合、0以外の終了ステータスを返すコマンドが実行されると、スクリプトは即座に終了します。
set -e
の挙動の例外:
set -e
は非常に強力ですが、いくつかの例外があります。
while
やuntil
の条件式: ループの条件式として使われるコマンドが失敗しても、スクリプトは終了しません。if
やelif
の条件式: 条件式として使われるコマンドが失敗しても、スクリプトは終了しません。&&
や||
の一部:command1 && command2
やcommand1 || command2
のように論理演算子で連結されたコマンドの場合、command1
が失敗しても、その失敗が全体の結果に影響しない限り、スクリプトは終了しません。- パイプラインの最後のコマンド以外:
command1 | command2 | command3
のようなパイプラインでは、set -e
は通常、パイプラインの最後のコマンドの終了ステータスのみを考慮します。ただし、set -o pipefail
と組み合わせることで、パイプライン内の任意のコマンドが失敗した場合にスクリプトを終了させることができます。 - コマンドリストの最後のコマンド以外:
(command1; command2)
のように括弧で囲まれたコマンドリストでは、最後のコマンド以外の失敗は無視されることがあります。 !
で否定されたコマンド:! command
のように否定されたコマンドが失敗しても、スクリプトは終了しません。
これらの例外を理解することは、set -e
を効果的に使用するために重要です。
技術的詳細
このコミットでは、src/run.bash
スクリプト内の特定のセクションにset -e
が追加されています。このセクションは、doc/codewalk
ディレクトリに関連する処理、具体的にはpig.go
とurlpoll.go
というGoプログラムのビルド(go build
)と、その後に生成されたバイナリの削除(rm -f
)を行っています。
変更前は、このブロック内でgo build
コマンドが失敗した場合(例えば、コンパイルエラーが発生した場合)、スクリプトはエラーを検出せずにrm -f
コマンドの実行を続行し、さらにその後のスクリプトの処理も続行してしまっていました。これは、ビルドが失敗したにもかかわらず、スクリプト全体としては成功したかのように見えてしまう「サイレントな失敗」を引き起こす可能性がありました。
set -e
をこのブロックの先頭に追加することで、以下のような挙動に変わります。
go build pig.go
が実行されます。- もし
go build pig.go
が0以外の終了ステータスを返した場合(つまり、ビルドに失敗した場合)、set -e
の指示により、スクリプトは即座にその時点で終了します。 - これにより、失敗したビルドにもかかわらず、
rm -f
やその後の無関係な処理が実行されることを防ぎ、エラーを早期に検出し、開発者に通知することができます。
この変更は、シェルスクリプトのベストプラクティスの一つであり、特に自動化されたビルドやテストスクリプトにおいて、エラーの早期発見と処理の信頼性向上に大きく貢献します。コミットメッセージの「This shell programming stuff is tricky.」という言葉は、このようなシェルスクリプト特有の挙動と、それを適切に扱うことの難しさを示唆しています。
コアとなるコードの変更箇所
--- a/src/run.bash
+++ b/src/run.bash
@@ -76,6 +76,7 @@ make clean
(xcd ../doc/codewalk
# TODO: test these too.
+set -e
go build pig.go
go build urlpoll.go
rm -f pig urlpoll
コアとなるコードの解説
変更はsrc/run.bash
ファイルの76行目付近にあります。
(xcd ../doc/codewalk
で始まるブロックは、サブシェル内で../doc/codewalk
ディレクトリに移動し、その中で一連のコマンドを実行することを示しています。このサブシェルは、親スクリプトの環境に影響を与えずに特定の操作を行うための一般的なパターンです。
追加された行は以下の通りです。
+set -e
このset -e
コマンドは、このサブシェル内で実行されるすべてのコマンドに対して「エラーが発生したら即座に終了する」というポリシーを適用します。
具体的には、このブロック内で実行される以下のコマンドに影響を与えます。
go build pig.go
:pig.go
のビルドgo build urlpoll.go
:urlpoll.go
のビルドrm -f pig urlpoll
: ビルドされたバイナリの削除
もしgo build pig.go
またはgo build urlpoll.go
のいずれかがコンパイルエラーなどで失敗し、0以外の終了ステータスを返した場合、set -e
が有効になっているため、スクリプトはそこで直ちに実行を停止します。これにより、ビルドの失敗が後続の処理に影響を与えたり、見過ごされたりするのを防ぎます。
この修正は、Goプロジェクトのビルドおよびテストインフラストラクチャの堅牢性を高めるための、小さくも重要な改善です。
関連リンク
- Gerrit Change-ID:
https://golang.org/cl/5905062
(これはGoプロジェクトが内部でコードレビューに使用しているGerritシステムへのリンクです。GitHubのコミットとGerritの変更は通常関連付けられています。)
参考にした情報源リンク
- Bash
set -e
の解説: - Go言語の
go build
コマンド: - シェルスクリプトのサブシェル: