シェルスクリプト完全ガイド — POSIX/Bashの違いから基本文法・セキュリティ・実践例まで

Shell Scriptとは — 概要と定義

Shell Script(シェルスクリプト)とは、Unix系/Linux系OS上で動作する「シェル」に対して一連のコマンドを順序立てて記述したテキストファイルです。シェルはユーザーとカーネルの仲介役となるコマンドインタプリタであり、シェルスクリプトはそのインタプリタに解釈されて実行されます。日常の運用自動化、ログ処理、簡易なツール作成、ジョブの連携などに広く用いられます。

シェルとシェルスクリプトの種類

  • /bin/sh(POSIX準拠を目指した基本的なシェル) — 互換性を重視する場合の標準インターフェース。
  • Bash(Bourne Again SHell)— 機能が豊富でスクリプティングで最も広く使われる。
  • zsh, KornShell (ksh) など — 対話型機能や拡張機能が強力。
  • 環境によっては dash が /bin/sh に使われる(Debian系)など、/bin/sh が何を指すかは環境依存。

基本構成と実行方法

シェルスクリプトは通常テキストファイルに保存し、先頭にシバン(shebang)と呼ばれる行を置きます。例えば:

#!/usr/bin/env bash
echo "Hello, world!"

シバンは実行時にどのインタプリタでスクリプトを実行するかを指定します。実行権限を付与し(chmod +x script.sh)、./script.sh のように実行します。あるいは sh script.sh のように指定して実行することもできます。

基本文法のポイント

  • コメント:# で始まる行はコメント
  • 変数:VAR="value"、展開は $VAR や ${VAR}
  • クォート:シングルクォート(')はリテラル、ダブルクォート(")は展開を許可
  • コマンド置換:$(cmd) または `cmd`(POSIXでは $(...) を推奨)
  • リダイレクト:>, >>, 2>, >&、パイプ:|
  • 制御構造:if/then/elif/else、case、for、while、until
  • 関数定義:f() { ...; } または function f { ...; }

重要な概念(ポータビリティとPOSIX)

シェルスクリプトは「どのシェルで書かれているか」によって挙動が変わります。POSIXシェル(/bin/sh)準拠にすれば異なる実装間での互換性が高まりますが、Bash固有の拡張(配列、拡張グロブ、bash固有の文字列展開など)を使うと移植性は落ちます。移植性を重視するには POSIX 準拠の文法に制限するか、スクリプト冒頭に明示的に bash を指定してください(#!/usr/bin/env bash)。

よく使う構文例

変数のデフォルトやチェック:

name=${1:-"anonymous"}   # 引数がなければ "anonymous"
: "${REQUIRED_VAR:?REQUIRED_VAR is not set}"  # 変数未設定ならメッセージを出して終了

ループ・条件分岐:

for f in *.log; do
  if [[ -s "$f" ]]; then
    echo "processing $f"
  fi
done

関数とエラーハンドリング:

cleanup() {
  rm -f "$tmpfile"
}
trap cleanup EXIT

set -euo pipefail  # -e: エラーで終了、-u: 未設定変数エラー、-o pipefail: パイプの途中の失敗を検出

セキュリティと安全性のベストプラクティス

  • 外部入力やユーザー引数は必ずクォートして扱う("$var")。
  • eval を使わない(どうしても必要な場合は極めて注意)。
  • IFS(内部フィールド区切り)やロケールによる分割に注意。read する際は read -r を使い、バックスラッシュの扱いを抑制する。
  • コマンドインジェクションを避ける。外部データをコマンドラインとして直接渡さない。
  • set -euo pipefail と trap を組み合わせて予期せぬ状態での継続を防ぐ。

デバッグとテスト

  • set -x を付けると実行コマンドがトレース表示される(デバッグ用)。
  • shellcheck を使った静的解析で典型的なミスを検出する(未引用・コマンド失敗の無視など)。
  • bats などのテストフレームワークでユニットテストを自動化する。

ユーティリティと外部コマンドの使い分け

シェルは多くの外部ツール(sed、awk、grep、cut、xargs、find 等)と組み合わせて強力な処理を行います。ただし、外部プロセスの呼び出しはオーバーヘッドがあるため、可能なら組み込みのビルトインを使うか、処理量が多い場合は Python や Perl、Go といった適切な言語に置き換える判断も重要です。

実運用での使い方(自動化・監視・運用)

  • Cron で定期実行するスクリプト
  • システム起動時やタイマーで systemd タイマーから呼ぶスクリプト
  • SSH 経由でリモートコマンドを実行する際のラッパースクリプト
  • ログローテーションやバックアップジョブの自動化

よくある落とし穴

  • echo の挙動はシェルや実装によって異なる(-e の解釈など)。portable な出力には printf を使うのが安全。
  • 配列やプロセス置換など Bash 固有機能を使うと /bin/sh 実行時に動かない。
  • 大きなデータ処理はシェルより言語処理系へ任せた方が性能・可読性が良い場合が多い。

実践的な小さな例:ログファイルの圧縮スクリプト

#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob

dir="/var/log/myapp"
for f in "$dir"/*.log; do
  gzip -9 "$f" && echo "Compressed $f"
done

この例では nullglob を有効にしてマッチしないワイルドカードを空にし、set -euo pipefail で堅牢性を高めています。

いつシェルスクリプトを使うべきか、いつ別の言語を使うべきか

  • 短くてOSコマンドの組み合わせ中心の自動化やラッパー、ちょっとした運用タスク:シェルスクリプトが有利。
  • 複雑な文字列処理、大量データ処理、堅牢なライブラリ管理が必要な処理:Python、Perl、Go などを検討。

まとめ

シェルスクリプトはシステム管理や運用自動化において非常に有用で、シンプルなタスクなら最短で成果を出せます。一方で移植性、セキュリティ、メンテナンス性に注意を払う必要があります。POSIX 準拠や明示的なシバン、適切なクォーティング、set オプション、静的解析(ShellCheck)やテストの導入などを実践することで、安全で再利用可能なスクリプトを作成できます。

参考文献