Bash 入門

Bash 変数

バリアブル(変数)は、あらゆるプログラミング言語の基盤となるビルディングブロックであり、Bashも例外ではありません。データを保存し、操作することを可能にすることで、スクリプトをより動的で再利用可能なものにします。Bashにおけるバリアブルの仕組みを理解することは、単純なシステム管理から複雑なオートメーション(自動化)プロセスまで、幅広いタスクを処理する効率的なスクリプトを書くために不可欠です。

本章では、Bashバリアブルの世界を深く掘り下げ、宣言、代入、スコープ、および具体的な使用方法についてカバーします。

バリアブルは、ラベルが貼られた「収納ボックス」のようなものです。データを中に入れ、必要な時にラベル名(変数名)を使ってデータを取り出して使用できます。

1. バリアブルの宣言と代入

Bashでは、バリアブルを使用する前に「データ型」(整数、ストリングなど)を明示的に宣言する必要はありません。名前に値を代入するだけで、Bashが自動的にそのバリアブルを作成します。変数名はケースセンシティブ(大文字・小文字を区別)であり、アルファベットまたはアンダースコア(_)で始まり、その後にアルファベット、数字、またはアンダースコアを続ける必要があります。

1.1 バリアブル代入の構文

バリアブルに値を代入するには、以下の構文を使用します:

variable_name=value

極めて重要: イコール(=)の両側にスペースを入れてはいけません。スペースを入れると、Bashはそれを独立したコマンドや引数として誤って解釈し、エラーの原因となります。

例 1:ストリング(文字列)を代入する

NAME="田中太郎"
echo $NAME # 出力: 田中太郎

例 2:整数(インテジャー)を代入する

AGE=30
echo $AGE # 出力: 30

例 3:コマンドの出力をバリアブルに代入する
これは「コマンド置換」によって行われます(詳細は後述)。

CURRENT_DATE=$(date)
echo $CURRENT_DATE # 出力例: Tue Oct 27 10:30:00 UTC 2023

例 4:空の値を代入する

EMPTY_VAR=
echo "このバリアブルは空です: [$EMPTY_VAR]" # 出力: このバリアブルは空です: []

1.2 バリアブル値へのアクセス

バリアブルに保存された値にアクセスして読み取るには、通常、変数名の直前に $ 記号を付けます。

echo $variable_name

あるいは、変数名を中括弧 {} で囲むこともできます。これは、曖昧さを回避したい場合や、バリアブルを他のテキストと直接連結したい場合に非常に有効です。

echo ${variable_name}

例 1:基本的なアクセス

CITY="東京"
echo "私は $CITY に住んでいます" # 出力: 私は 東京 に住んでいます

例 2:中括弧を使用した曖昧さの解消

NUMBER=5
echo "この数字は ${NUMBER}0 です" # 出力: この数字は 50 です
echo "この数字は $NUMBER0 です"   # 出力: (恐らく何も表示されません。システムが NUMBER0 という名前の変数を探すが未定義のため)

1.3 バリアブル命名規則

Bashは命名に柔軟性を持っていますが、一定のコンベンション(規約)に従うことで、コードの可読性とメンテナンス性が向上します。

  • 記述的な名前を使用する: 名前はバリアブルの用途を明確に示すべきです。
  • 大文字・小文字の使い分け: 環境変数(システムレベル)は通常すべて大文字(UPPER_CASE)を使用し、スクリプト内部専用のローカル変数は小文字(lower_case)を使用するのが一般的です。
  • 単語の区切り: 複数の単語を使う場合はアンダースコアで区切ります(例:user_namefile_path)。

2. バリアブルのスコープ (Scope)

バリアブルの「スコープ」は、スクリプトのどの部分でその値にアクセスできるかを決定します。Bashには主に2種類のスコープがあります:ローカル(Local)とグローバル(Global)です。

2.1 ローカルバリアブル (Local Variables)

ローカル変数は、それらが定義された関数またはコードブロックの内部でしかアクセスできません。ローカル変数を宣言するには、local キーワードを明示的に使用する必要があります。

local variable_name=value

スコープ外からローカル変数にアクセスしようとすると、未定義(空)であるか、同名のグローバル変数の値を読み取ることになります。

例:

my_function() {
  local MY_VAR="関数内部の変数"
  echo "関数内での読み取り: $MY_VAR"
}

MY_VAR="関数外部の変数"
my_function # 関数を呼び出し。「関数内での読み取り: 関数内部の変数」と表示される
echo "関数外での読み取り: $MY_VAR" # 「関数外での読み取り: 関数外部の変数」と表示される

この例では、MY_VARグローバルに定義されていると同時に、my_function の内部でローカル変数としても定義されています。関数実行中、関数は自身のローカル変数のみを参照し、実行終了後もグローバル変数の値は元のまま保持されます。

2.2 グローバルバリアブル (Global Variables)

グローバル変数は、関数内部を含むスクリプト全体のどこからでもアクセス可能です。デフォルトでは、関数の外部で宣言されたバリアブルはすべてグローバル変数となります。

例:

GLOBAL_VAR="これはグローバル変数です"

my_function() {
  echo "関数内での読み取り: $GLOBAL_VAR"
}

echo "関数外での読み取り: $GLOBAL_VAR"
my_function # 「関数内での読み取り: これはグローバル変数です」と表示される

2.3 スコープ使用上のアドバイス

  • 関数内部でローカル変数を使用することで、命名の競合を防ぎ、関数を独立して動作(外部環境への干渉を防止)させることができます。
  • 関数内部でグローバル変数を変更すると、意図しない副作用が発生する可能性があります。特に、その変数が他の場所でも使用されている場合に注意が必要です。
  • 可能な限り関数内ではローカル変数を使用し、コードのモジュール化を高め、副作用を回避しましょう。

3. コマンド置換

コマンド置換を使用すると、コマンドの実行出力をキャプチャして、それを直接バリアブルに代入できます。Bashには主に2つの方法があります。

  • 方法 1:バッククォート (`) を使用する
variable_name=`command`
  • 方法 2:$() を使用する (推奨)
variable_name=$(command)

$(...) 構文の使用を強く推奨します。理由は、可読性が高く、ネスト(入れ子)が容易だからです。バッククォートはエスケープ処理でエラーが発生しやすく、多層のネストが困難です。

例 1:現在のディレクトリを取得する

CURRENT_DATE=$(date)
echo "現在の時刻は:$CURRENT_DATE"

例 2:ディレクトリ内のファイルリストを取得する

FILES=$(ls)
echo "現在のディレクトリ内のファイル:$FILES"

例 3:引数を持つ複雑なコマンドの出力をキャプチャする

FREE_MEMORY=$(free -m | grep Mem | awk '{print $4}')
echo "空きメモリ:$FREE_MEMORY MB"

3.1 ネストされたコマンド置換

$(...) 構文では、複数のコマンド置換をネストさせることができます。これは複雑な操作を行う際に非常に便利です。

例:

CURRENT_USER=$(whoami)
HOME_DIR=$(eval echo ~$CURRENT_USER)
echo "$CURRENT_USER のホームディレクトリは:$HOME_DIR"

この例では、まず whoami でユーザー名をキャプチャし、次に eval echo ~$CURRENT_USER を使ってチルダ ~ を動的に展開し、そのユーザーのホームディレクトリパスを取得しています。

4. 特殊変数

Bashには、あらかじめ定義された特別な意味を持つバリアブルがいくつか存在します。これらはスクリプト自体、受け取った引数、実行環境に関する情報を取得するのに役立ちます。

4.1 位置パラメータ

位置パラメータは、スクリプトや関数に渡された外部引数を保持するバリアブルです。

  • $0: スクリプト自体の名前。
  • $1, $2, $3...: スクリプトや関数に渡された1番目、2番目、3番目以降の引数。
  • $#: 引数の総数。
  • $*: すべての引数を1つのストリングとして保持(スペース区切り)。
  • $@: すべての引数をリスト(配列形式)として保持。ループ処理の際、$* よりも安全で一般的です。

サンプルスクリプト:

#!/bin/bash
echo "スクリプト名: $0"
echo "第1引数: $1"
echo "第2引数: $2"
echo "引数の総数: $#"
echo "全引数 (単一ストリング): $*"
echo "全引数 (個別の要素): $@"

# ループで引数にアクセス("$@" の使用を強く推奨)
for arg in "$@"; do
  echo "抽出された引数: $arg"
done

4.2 その他の一般的な特殊変数

  • $?: 直前に実行されたコマンドの終了ステータス。0 は成功、それ以外はエラー。
  • $$: 現在実行中のスクリプトのプロセスID(PID)。
  • $!: バックグラウンドで最後に実行されたプロセスのPID。
  • $_: 直前のコマンドの最後の引数。

例 1:コマンドの終了ステータスを確認する

ls -l non_existent_file
if [ $? -eq 0 ]; then
  echo "コマンド成功"
else
  echo "コマンド失敗、エラー発生"
fi

5. 総合実戦:バリアブルを使ったシステム情報表示スクリプト

バリアブル、コマンド置換、特殊変数を組み合わせて、システム情報を自動的に収集・表示するスクリプトを作成します。

#!/bin/bash

# 1. コマンド置換を使ってシステム情報を収集し、バリアブルに代入
HOSTNAME=$(hostname)
KERNEL_VERSION=$(uname -r)
UPTIME=$(uptime | awk -F',' '{print $1}')
CURRENT_USER=$(whoami)
CURRENT_DIR=$(pwd)

# 2. 情報をフォーマットしてターミナルに表示
echo "システム実行情報レポート:"
echo "-------------------"
echo "ホスト名: $HOSTNAME"
echo "カーネルバージョン: $KERNEL_VERSION"
echo "稼働時間: $UPTIME"
echo "現在のログインユーザー: $CURRENT_USER"
echo "現在のワーキングディレクトリ: $CURRENT_DIR"
echo "-------------------"

# 3. スクリプト自身のPIDを表示
echo "このモニタリングスクリプトのPID: $$"

# 4. スクリプトに渡された引数の数を表示
echo "受信した追加引数の数: $#"

# 5. 直前の命令(echo)の終了ステータスをチェック
if [ $? -eq 0 ]; then
  echo "スクリプトの実行が正常に終了しました。"
else
  echo "警告:スクリプト実行中にエラーが発生した可能性があります。"
fi

このスクリプトは、$(command) を通じてホスト名や稼働時間などの重要なデータをキャプチャし、それらを安全にバリアブルに保存しています。その後、整形して出力し、$$$? といった特殊なシステム変数の活用例を示しています。