変数スコープと戻りステータス

ShellBeginner
オンラインで実践に進む

はじめに

スクリプトを作成するときは、データへのアクセス方法と、関数が成功または失敗をどのように伝えるかを管理する必要があります。

2 つの異なる場所で同じ変数名を使うと、一方の変数がもう一方を誤って上書きする可能性があります。これはプログラミングでよくあるバグです。この問題は、スコープ(変数を関数内に限定する仕組み)を使って解決します。

また、関数には、処理が成功したか失敗したかをメインスクリプトに伝える方法が必要です。Bash では、これを 戻りステータスコードで行います。

この実験では、大規模なスクリプトで重要になる 2 つの考え方を扱います。変数が意図しない場所に影響を与えないようにすることと、成功または失敗のステータスを使って実行フローを制御することです。

この実験では、次のことを学びます。

  1. 変数スコープ:local を使って変数の競合を防ぐ。
  2. 戻りステータス:終了コード(成功は 0、失敗は 0 以外)を使ってスクリプトのフローを制御する。

変数スコープ:グローバルとローカル

Bash では、デフォルトで変数は グローバルです。つまり、関数内で定義した変数にもスクリプト内のどこからでもアクセスでき、変更もできます。そのため、スクリプトのある部分が別の部分で必要なデータを誤って変更してしまうなど、見つけにくいバグにつながることがあります。

この問題を防ぐには、local キーワードを使います。local 変数は、作成された関数の中でのみ存在します。

これは、シェルスクリプトのトレードオフの 1 つです。Bash はすぐに使い始められる便利さがある一方で、スクリプト内のデータが予期せず別の場所に漏れやすくもなっています。スコープは、変数を参照できる範囲を制限するルールです。

この違いを確認してみましょう。

scope.sh という名前のスクリプトを作成します。

cd ~/project
nano scope.sh

次のコードを追加します。

#!/bin/bash

## This is a Global variable
city="New York"

print_city() {
  ## This is a Local variable. It shares the name 'city', but it is a separate variable.
  local city="London"
  echo "Inside function: $city"
}

echo "Before function call: $city"

## Run the function
print_city

## Check if the Global variable was changed
echo "After function call: $city"

保存(Ctrl+O、Enter)して終了(Ctrl+X)します。

スクリプトを実行可能にして、実行します。

chmod +x scope.sh
./scope.sh

実行結果:

Before function call: New York
Inside function: London
After function call: New York

関数の外側にある city 変数が "New York" のままであることを確認してください。関数は独自の city の local コピー("London")を使っているため、グローバル変数には影響しません。

もし local を使わなかった場合、関数はグローバル変数を上書きし、最後の行には "London" と表示されます。

このため、スクリプトが大きくなるほど local が重要になります。短いデモではグローバル変数が問題ないように見えるかもしれません。しかし、長期間保守するスクリプトでは、同じ変数名を再利用すると、原因を特定しにくい微妙なバグが発生する可能性があります。

戻りステータスコード

多くのプログラミング言語では、関数は文字列や数値などのデータを返します。一方、Bash の return は、成功または失敗を示す ステータスコードを返すためだけに使います。

  • 0:成功。
  • 1~255:失敗(エラー)。

return と echo を混同しないでください。

  • echo は画面にテキストを出力します(取得すれば変数に格納することもできます)。
  • return は、処理が「成功した」または「失敗した」ことをシステムに伝えるため、数値を内部的に返します。

この違いは、シェルプログラミングの基本です。テキストの出力とステータスの通知は、別々の情報経路です。関数は echo で人が読める情報を表示できますが、return の値は一般的なデータを渡すためではなく、実行フローを制御するために使います。

ここでは、コード 5 を返して失敗をシミュレートする関数を作成します。

status.sh を作成します。

nano ~/project/status.sh

次のコードを追加します。

#!/bin/bash

check_file() {
  echo "Checking for file..."
  ## We simulate a specific error by returning Code 5
  return 5
}

check_file

保存して終了します。

スクリプトを実行可能にして、実行します。

chmod +x status.sh
./status.sh

出力:

Checking for file...

テキストは表示されましたが、数値の 5 はどこにあるのでしょうか。5 は画面に表示されずに返されています。次のステップでは、この値を取得して使う方法を学びます。

戻りコードを取得する($?)

最後に実行したコマンドまたは関数の戻りコードを確認するために、Bash には $? という特殊な変数があります。

check_file の実行が完了すると、$? には自動的に値 5 が入ります。

重要なルール:コマンドの実行直後に、すぐ $? を読み取る必要があります。別のコマンド(単純な echo も含みます)を実行すると、$? はその新しいコマンドのステータスで更新されます。

status.sh を変更して、コードを取得します。

ファイルを開きます。

nano status.sh

スクリプトを次の内容に更新します。

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file

## Capture the return code immediately into a variable
ret_val=$?

echo "The function returned: $ret_val"

保存して終了します。もう一度実行します。

./status.sh

出力:

Checking for file...
The function returned: 5

これで、エラーコードを ret_val 変数に取得できました。

戻りコードを取得すると、スクリプトの残りの処理で状況に応じた対応ができるようになります。後続の if 文で値を確認し、処理を続行するか、再試行するか、停止するかを判断できます。これにより、大規模なスクリプトでも関数同士が連携でき、個別のコード断片として動作するだけではなくなります。

戻りステータスに応じて処理する

ステータスコードを取得できたので、if 文を使ってエラーを適切に処理できます。

  • コードが 0 なら、すべて正常です。
  • コードが 5 なら、何が問題だったか(例:「ファイルが見つからない」)を正確に把握できます。

標準的な Linux コマンドも、このように動作します。たとえば grep は、一致する内容を見つけると 0 を返し、見つからないと 1 を返します。

status.sh を更新して、条件分岐を追加します。

nano status.sh

次の条件分岐を追加します。

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file
ret_val=$?

## Logic based on the return code
if [ $ret_val -eq 0 ]; then
  echo "Success: File found."
elif [ $ret_val -eq 5 ]; then
  echo "Error: File missing (Code 5)."
else
  echo "Unknown Error: Code $ret_val"
fi

保存して終了します。実行します。

./status.sh

出力:

Checking for file...
Error: File missing (Code 5).

これで、基本的なエラー処理を実装できました。ファイルの確認に失敗すると、スクリプトはその失敗を認識し、何もせずにクラッシュしたり、状況を確認せず処理を続けたりする代わりに、具体的なエラーメッセージを表示できます。

まとめ

この実験では、スクリプト作成で重要な 2 つの概念を練習しました。

  1. 変数スコープ(local):local を使うと、関数内の変数がグローバル変数を上書きしないため、スクリプトを予測しやすくなります。
  2. 戻りステータス(return、$?):終了コードを使うと、関数からメインスクリプトへ成功または失敗を伝えられるため、エラーに適切に対応するスクリプトを作成できます。

グローバル変数を明示的に変更する必要がない限り、関数内の変数には常に local を使ってください。