Bash入門ガイド:基本文法・デバッグ・実務で使えるセキュアなスクリプト作成法

Bashとは

Bash(Bourne Again SHell)は、Unix系オペレーティングシステム上で広く使われているコマンドラインインタープリタ(シェル)であり、スクリプト言語です。Bash は GNU プロジェクトの一部として開発され、既存の Bourne shell(/bin/sh)互換性を保ちながら数多くの拡張機能を備えています。対話的なコマンド実行や、運用自動化・バッチ処理を目的としたシェルスクリプト作成の両方に適しています。

歴史と立ち位置

  • 作成者と沿革:Bash は 1989 年に Brian Fox により GNU プロジェクトの一環として最初に書かれ、その後 Chet Ramey が長年にわたり主要なメンテナとなっています。

  • 目的:Bourne shell(sh)の互換を維持しつつ、KornShell(ksh)やCシェル(csh)の便利な機能のいくつかを取り入れた拡張を提供することが目的でした。

  • ライセンス:Bash は GNU General Public License(GPL)の下で配布され、自由に利用・改変・再配布が可能です。

  • 普及:多くの Linux ディストリビューションや macOS(過去のバージョン)でデフォルトシェルとして使われるなど、幅広い環境で標準的に利用されています(近年は zsh などがデフォルトに採用されるケースもあります)。

Bash の基本的な役割

  • コマンド実行:ユーザーが入力したコマンドを解釈し、プログラムを起動する。

  • スクリプト実行:条件分岐、ループ、関数などを用いた処理の自動化。

  • 環境管理:環境変数、エイリアス、パス設定、初期化ファイルによるユーザー環境の構築。

  • ジョブ制御:バックグラウンド処理やフォアグラウンド復帰などの制御。

インタラクティブと非インタラクティブ

Bash は対話モード(端末からコマンドを直接受け取る)と非対話モード(スクリプト実行)を区別します。起動時にはログインシェル/非ログインシェル、インタラクティブ/非インタラクティブの組み合わせによって読み込まれる初期化ファイルが変わります。代表的な初期化ファイルは以下です。

  • /etc/profile(ログイン時にシステム全体で読み込まれることがある)

  • ~/.bash_profile, ~/.bash_login, ~/.profile(ログインシェル)

  • ~/.bashrc(対話的非ログインシェルや明示的に読み込む場合)

  • /etc/bash.bashrc(ディストリビューションにより存在)

基本的な文法と機能(代表項目)

ここでは実務でよく使う文法や機能を取り上げます。例は分かりやすさのために最小限にしています。

変数と展開

変数代入はシンプルに「NAME=value」。展開は「$NAME」または「${NAME}」。デフォルト値や代替表現も可能です:

${VAR:-default}  # VAR が未設定または空のとき default を使う
${VAR:=default}     # VAR が未設定なら代入して default を返す
${VAR:+alt}         # VAR が設定されているときに alt を返す
${VAR:?error}       # VAR が未設定ならエラーで停止(スクリプト内で有用)

クオートと展開の制御

引用(クオート)は非常に重要です。特にファイル名や引数に空白や特殊文字が含まれる場合は常に二重引用符を使う習慣を付けると安全です。

  • "$var" — ほとんどの展開は行われるが、ワード分割とグロブは抑制される。

  • 'single quotes' — 展開もエスケープも無効(文字列をそのまま扱う)。

  • `backticks` と $(...) — コマンド置換。$(...) の使用が推奨される(ネストが容易)。

算術演算

算術評価には $(( ... )) や (( ... )) を使います。

count=3
((count++))
sum=$((a + b * 2))

配列と連想配列

Bash にはインデックス配列と連想配列(Bash 4 以降)が存在します。

arr=(one two three)
echo "${arr[1]}"        # two

declare -A amap
amap[key]=value
echo "${amap[key]}"

リダイレクトとパイプ、プロセス代替

標準入出力とエラー出力の制御は必須です。

  • コマンド > file(上書き)、>>(追記)、2> error.log(stderr)

  • パイプ:cmd1 | cmd2

  • プロセス代替:<(cmd) や >(cmd) — コマンドの出力をファイル名として扱える(Bash 拡張)。

  • here-documents:<

条件分岐とテスト

if、case、test([ ]、[[ ]])で条件判定を行います。[[ ... ]] は Bash 固有の拡張で、文字列比較やパターンマッチ、安全な複合条件に便利です。

if [[ -f "$file" && "$user" == "root" ]]; then
  echo "ok"
fi

case "$var" in
  start) echo "start" ;;
  stop)  echo "stop" ;;
esac

ループと関数

for、while、until があり、関数は単純に名前で定義します。

for i in {1..5}; do
  echo "$i"
done

myfunc() {
  local arg="$1"
  echo "arg=$arg"
}

デバッグと堅牢化

スクリプトの信頼性を高めるために次のオプションを使います。

  • set -e(エラーが発生したら即座に終了) — ただし副作用や複合コマンドへの注意が必要。

  • set -u(未設定変数の参照でエラー)

  • set -o pipefail(パイプの途中で失敗したコマンドを検出)

  • set -x(実行されるコマンドを表示しデバッグ)

  • trap 'handler' ERR EXIT INT などで終了時やシグナル時のクリーンアップを行う。

セキュリティ上の注意点

Bash スクリプトでの脆弱性は多くは不適切な入力処理や引用不足に起因します。代表的な注意点:

  • 外部入力をコマンドラインで直接渡すとコマンドインジェクションの恐れがある。必ずクオートするか、安全に処理する(例:配列で引数を分ける)。

  • PATH の扱いに注意。スクリプト実行時に意図しない実行ファイルが呼ばれないようフルパス指定や PATH の固定を検討する。

  • eval の使用は極力避ける。どうしても必要なら入力の厳密な検証を行う。

  • IFS(内部フィールド区切り)やワイルドカード(グロブ)の扱いを誤るとワード分割に起因する不具合が発生する。

移植性(ポータビリティ)と Bashisms

シェルスクリプトを書いて配布する場合、どのシェルで実行されるかを意識する必要があります。POSIX sh 準拠で書くと多くの環境で動作しますが、Bash 固有の機能(配列、連想配列、[[ ]]、(( ))、プロセス代替など)に依存すると移植性が低下します。配布目的のスクリプトは「#!/bin/sh」を用い、POSIX 準拠で書くか、Bash 専用であることを明確にして「#!/usr/bin/env bash」を使うのが一般的です。

実務上のベストプラクティス(要点)

  • スクリプト冒頭にシバン(shebang)を入れる:#!/usr/bin/env bash(環境依存を減らす)

  • 変数は常に引用する:"$var"

  • コマンドの失敗を検出するために set -euo pipefail を用いる。ただし set -e の振る舞いには落とし穴があるためケースバイケースで。

  • ユーザー入力や外部データは必ず検証・サニタイズする。

  • 自動化コードはテスト可能にし、必要ならばユニット化(bats など)を検討する。

  • 静的解析ツール(ShellCheck)を使ってコード品質と安全性を向上させる。

代表的なよく使うコマンド・構文例

# ファイル一覧をループで処理
for f in /var/log/*.log; do
  gzip "$f"
done

# コマンドの出力を変数に入れる
result=$(grep 'ERROR' /var/log/syslog | wc -l)

# エラー時にメッセージを出して終了
: "${CONFIG_FILE:?CONFIG_FILE is required}"

代替シェルと共存

Bash の他にも zsh、ksh、dash、fish など多くのシェルがあります。目的により選択が変わります。例えば:

  • zsh:対話的に高機能、プラグインやテーマが豊富(macOS の新しいデフォルト)

  • dash:Debian 系で /bin/sh として使われることがあり、非常に軽量で高速、起動スクリプトの実行速度向上に寄与

  • ksh:古くからの標準的なスクリプト環境を提供

サーバーの起動スクリプト等では sh(POSIX)互換性が重視される一方、日常の作業や複雑な自動化には Bash の利便性が活かされます。

まとめ

Bash は強力で柔軟なシェル兼スクリプト言語です。適切に使えばシステム管理や開発ワークフローの自動化を大幅に効率化できますが、同時にクオートや外部入力の扱い、set オプションの性質などに注意しないと脆弱性やバグを招きやすい面もあります。移植性が必要なスクリプトでは POSIX 準拠を心がけ、Bash 専用の利便性を活かす場合は Bash の持つ機能を正しく理解して使うのが重要です。

参考文献