Bashの変数は、設定値、ファイルパス、コマンド結果、配列などを名前付きで保持します。基本形はname=valueで、等号の前後に空白を置きません。参照は$nameまたは${name}です。最も多い不具合は、変数展開を引用せず空白やワイルドカードで分割されること、未設定と空文字を混同すること、コマンド文字列を組み立てて再評価することです。本稿では代入、引用、既定値、整数、配列、環境変数を安全な設計と一緒に説明します。
代入の基本
変数名は英字またはアンダースコアで始め、その後に英数字とアンダースコアを使うのが基本です。大文字名は環境変数やシェルの予約的な変数と衝突しやすいため、スクリプト内部の変数は説明的な小文字名にします。代入文に空白を入れると、Bashは左側をコマンド名として解釈します。
project_name='sample site'
retry_count=3
printf 'project=%s retries=%d\n' "$project_name" "$retry_count"
値に空白やシェル記号がある場合は引用します。シングルクォート内では変数展開されず、ダブルクォート内では展開されます。引用符は通常値の一部にはなりません。コピーしたスマートクォートはBashの引用符ではないため使いません。
参照は原則としてダブルクォート
ファイルパスを$pathのまま渡すと、空白で複数引数に分かれ、*や?がファイル名へ展開される可能性があります。"$path"なら一つの引数として保てます。変数の直後に英数字を続けるときは、境界を明確にするため波括弧を使います。
base='report'
file_path='./Quarterly Report.txt'
printf '%s\n' "${base}_2026.txt"
stat -- "$file_path"
配列の全要素を展開する"${array[@]}"など、引用の中でも複数単語を生む意図的な例があります。単に「常に引用」で終わらず、展開後にコマンドへ何個の引数を渡したいかを考えます。
コマンド結果を代入する
コマンド置換$(command)は標準出力を変数へ入れ、末尾の改行を取り除きます。終了状態は代入文直後に確認できますが、代入と別のコマンドを同じ行へ複雑に組み合わせると分かりにくくなります。巨大出力やバイナリをシェル変数へ入れず、ストリームやファイルとして処理します。Bash変数はNUL文字を保持できません。
if kernel_release=$(uname -r); then
printf 'kernel=%s\n' "$kernel_release"
else
printf '%s\n' 'could not read kernel release' >&2
fi
コマンド置換の出力をもう一度コマンドとして実行しません。外部入力はデータとして検証し、コマンド名や引数は配列で構築します。
未設定と空文字
Bashでは未設定の変数と、設定済みだが値が空の変数を区別できます。[[ -v name ]]は設定有無、[[ -z $name ]]は文字列長0を調べます。set -uが有効な場合、未設定の通常参照はエラーになるので${name-}など安全な形を使います。値0は空文字ではなく、算術と文字列の条件を混同しません。
if [[ -v config_path ]]; then
if [[ -n $config_path ]]; then
printf 'configured path: %s\n' "$config_path"
else
printf '%s\n' 'config_path is set but empty'
fi
else
printf '%s\n' 'config_path is unset'
fi
既定値の:-と-
${variable:-default}は変数が未設定または空ならdefaultを展開します。コロンなしの${variable-default}は未設定のときだけdefaultを使い、空文字はそのままです。空文字を明示的な「無効」設定として使う場合、この差が重要です。既定値展開は元変数へ代入しません。
display_name=${name:-Anonymous}
printf 'display=%s\n' "$display_name"
:=、:?、:+の用途
${var:=default}:未設定または空ならdefaultをvarへ代入して展開する。位置パラメーターなど代入できない対象には使えない。${var:?message}:未設定または空ならメッセージを標準エラーへ出し、非対話シェルを終了させる。秘密値をメッセージへ含めない。${var:+alternate}:varが設定済みかつ空でなければalternateを使う。オプションを条件付き追加する用途では配列も検討する。- コロンを外すと空文字をnullとして扱うかどうかが変わるため、設定仕様へ明記する。
output_dir=${OUTPUT_DIR:?OUTPUT_DIR must be configured}
この形は簡潔ですが、ライブラリとしてsourceされるコードで使うと呼び出し元シェルを終了させる可能性があります。再利用関数ではエラーを返し、トップレベルで終了判断をする方が制御しやすい場合があります。
readonlyとlocal
readonlyは変数を再代入できないようにし、定数的な設定の誤変更を防ぎます。関数内ではlocalで変数のスコープを限定します。ただしBashのlocalは動的スコープの性質があり、呼び出した関数から見える場合があります。グローバル変数への暗黙依存を避け、引数と戻り値を明示します。local value=$(command)のように宣言とコマンド置換を同時にすると、終了状態がlocalコマンドで隠れることがあるため分けます。
readonly app_name='example'
read_config() {
local value
if ! value=$(some_config_reader); then
return 1
fi
printf '%s\n' "$value"
}
整数計算
Bashの算術評価は(( expression ))や$(( expression ))を使います。数値だからとexprの文字列コマンドを組み立てる必要はありません。外部入力を算術式へ直接入れる前に、10進整数など許可形式を正規表現で検証します。Bash算術では変数名がさらに参照されるため、信頼できない文字列を式として評価しないことが重要です。先頭0の解釈や整数範囲も確認します。
count='12'
if [[ $count =~ ^[0-9]+$ ]]; then
next=$(( 10#$count + 1 ))
printf 'next=%d\n' "$next"
else
printf '%s\n' 'count must be decimal digits' >&2
exit 2
fi
10#はBash算術で10進数として解釈させる形ですが、空文字や符号付き値など要件に合わせた検証が必要です。金額や小数、巨大整数にはBash整数演算が向かないため、専用言語やdecimalライブラリを使います。
インデックス配列
複数の引数を安全に保持するには配列を使います。各要素は引用して追加し、外部コマンドへ渡すときは"${args[@]}"で要素境界を保ちます。一つのコマンド文字列を作ってevalする方法より安全でレビューしやすい構成です。
args=(--format long)
if [[ ${verbose:-0} == 1 ]]; then
args+=(--verbose)
fi
some_report_command "${args[@]}" -- "$input_path"
"${args[*]}"は一つの結合文字列になるため、コマンド引数転送には使いません。空配列、空文字要素、空白や改行を含む要素をテストします。
連想配列
Bashのdeclare -Aは文字列キーの連想配列を作ります。設定表や件数集計に使えますが、キーを未引用で扱ったり、存在しない要素をset -u下で参照したりしないようにします。順序を保証する構造ではないため、表示順が必要なら別のキー配列を持つかsortします。Bash版要件も確認します。
declare -A ports=( [web]=443 [ssh]=22 )
printf 'web port=%s\n' "${ports[web]}"
シェル変数と環境変数
通常の代入で作ったシェル変数は、同じシェルで使えますが子プロセス環境へは自動で渡りません。export nameすると、以後起動する子へ環境変数として渡されます。子が変更しても親シェルの値は変わりません。一コマンドだけへ渡すならNAME=value commandの形が使えます。
app_mode='test'
export app_mode
APP_LOCALE='ja_JP.UTF-8' some_read_only_report
環境変数は同一利用者の診断情報、クラッシュレポート、プロセス起動設定などへ露出し得るため、秘密情報の永続保管には向きません。PATH、IFS、BASH_ENV、LD_PRELOADなど挙動に影響する変数を、管理スクリプトで信頼できない環境から無検証に継承しません。
設定値の優先順位を決める
実用スクリプトは、既定値、設定ファイル、環境変数、コマンドライン引数を組み合わせることがあります。どれが優先するかを文書化し、各段階で形式を検証します。空文字を「削除」「自動」「無効」のどれとするかも決めます。最終的に採用した値の出所だけをログへ残し、秘密値そのものは出しません。設定ファイルは所有者と権限を確認し、シェルコードとしてsourceする方式は任意コード実行になるため、単純データ形式を優先します。
入力検証の基本
- 列挙値は許可リストとの完全一致で確認し、部分一致で本番/検証を決めない。
- 数値は範囲と形式を確認し、算術式として自由入力を評価しない。
- パスは絶対/相対、基準ディレクトリ、シンボリックリンク、存在、所有者、ファイル種別を確認する。
- ホスト名やURLは構文だけでなく、許可ドメイン、TLS、接続先資産IDを確認する。
- 出力先は既存ファイルとの衝突を確認し、未引用変数で複数対象へ広げない。
- エラーメッセージへ値を丸ごと埋め込まず、機密性と制御文字を考慮する。
デバッグとset -x
set -xは展開後のコマンドを標準エラーへ表示するため、変数にトークンや個人情報があると漏えいします。本番で無条件に有効化せず、秘密処理の前で無効にし、ログ保存先と閲覧権限を確認します。必要なら値をマスキングした構造化ログを自分で出します。set -u、set -e、pipefailも挙動を変えるため、魔法の安全設定として追加せず、条件分岐とエラー処理をテストします。
よくある失敗
name = valueと空白を入れ、nameコマンドがないというエラーになる。- 未引用の
$fileで空白が分割され、ワイルドカードが複数ファイルへ展開される。 ${var:-default}と${var-default}を混同し、空文字設定を上書きする。- local宣言とコマンド置換を同時に行い、失敗の終了状態を見落とす。
- 文字列でコマンドを組み立てevalし、外部入力がシェル構文として実行される。
- exportした秘密値やset -xの出力をCI・監視・サポートログへ残す。
Bash変数は、引用、未設定/空の区別、スコープ、型に相当する検証を組み合わせると安全に使えます。文字列は"$var"、複数引数は"${array[@]}"、既定値は要件に合わせた:-などを使い、値をシェルコードとして再評価しないことが最重要です。

コメント