SJ blog
tools
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

シェルスクリプト入門:自動化で作業時間を半分にする

Bashスクリプトの基本文法から実践的なユースケースまで解説。ファイル処理の自動化・デプロイスクリプト・定期実行など、すぐに使える自動化パターンを紹介します。

一言結論

シェルスクリプトは冒頭にset -euoを書いてエラーを即時検出し、関数・引数バリデーション・ログ出力の3点を最初から実装することで、「動いているが壊れたら原因不明」なスクリプトを防げる。

なぜシェルスクリプトか

  • 追加インストール不要(Bash はほぼ全ての Linux/macOS に標準装備)
  • CLIツールをパイプで組み合わせて強力なパイプラインを構築
  • cron との組み合わせで定期実行が簡単
  • CI/CD の yaml から呼び出せる

基本文法

変数・引数

#!/usr/bin/env bash
set -euo pipefail  # エラー時即終了・未定義変数エラー・パイプエラー検知

# 変数
NAME="World"
echo "Hello, ${NAME}!"

# スクリプト引数
# $1 $2 ... → 位置引数
# $# → 引数の数
# $0 → スクリプト名自体
if [[ $# -lt 1 ]]; then
  echo "Usage: $0 <filename>" >&2
  exit 1
fi

FILE="$1"

条件分岐

# ファイル・ディレクトリの存在確認
if [[ -f "$FILE" ]]; then
  echo "ファイルあり"
elif [[ -d "$FILE" ]]; then
  echo "ディレクトリあり"
else
  echo "存在しない"
fi

# 文字列比較
if [[ "$ENV" == "production" ]]; then
  echo "本番環境"
fi

# コマンドの成功/失敗
if git status &>/dev/null; then
  echo "Gitリポジトリ内"
fi

ループ

# ファイル一覧をループ
for file in src/**/*.ts; do
  echo "処理中: $file"
done

# 配列
ENVS=("dev" "staging" "prod")
for env in "${ENVS[@]}"; do
  echo "デプロイ: $env"
done

# 数値ループ
for i in {1..5}; do
  echo "試行 $i"
done

関数

# 関数定義
log() {
  local level="$1"
  shift
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}

log INFO "デプロイ開始"
log ERROR "接続失敗"

実践パターン1: ファイル処理

#!/usr/bin/env bash
# 古いログファイルを圧縮してアーカイブ
set -euo pipefail

LOG_DIR="/var/log/myapp"
ARCHIVE_DIR="/backup/logs"
DAYS=30

mkdir -p "$ARCHIVE_DIR"

# 30日以上前のログを探して圧縮
find "$LOG_DIR" -name "*.log" -mtime +$DAYS | while read -r logfile; do
  basename=$(basename "$logfile")
  gzip -c "$logfile" > "${ARCHIVE_DIR}/${basename}.gz"
  rm "$logfile"
  log INFO "アーカイブ完了: $basename"
done

log INFO "完了。$(find "$ARCHIVE_DIR" -name '*.gz' | wc -l) ファイルをアーカイブ"

実践パターン2: デプロイスクリプト

#!/usr/bin/env bash
set -euo pipefail

BRANCH="${1:-main}"
DEPLOY_DIR="/var/www/myapp"
BACKUP_DIR="/backup/deploy/$(date +%Y%m%d_%H%M%S)"

log() { echo "[$(date '+%H:%M:%S')] $*"; }

# バックアップ
log "バックアップ中..."
cp -r "$DEPLOY_DIR" "$BACKUP_DIR"

# デプロイ
log "ブランチ $BRANCH をプル..."
cd "$DEPLOY_DIR"
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"

# ビルド
log "ビルド中..."
npm ci --silent
npm run build

# ヘルスチェック
log "ヘルスチェック..."
sleep 3
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health)

if [[ "$HTTP_STATUS" != "200" ]]; then
  log "ヘルスチェック失敗($HTTP_STATUS)。ロールバック中..."
  cp -r "$BACKUP_DIR/." "$DEPLOY_DIR"
  exit 1
fi

log "デプロイ成功!"

実践パターン3: 環境セットアップ

#!/usr/bin/env bash
# 新しいマシンの開発環境を一発セットアップ
set -euo pipefail

# コマンドの存在確認
require() {
  command -v "$1" &>/dev/null || {
    echo "必要なコマンドがありません: $1" >&2
    exit 1
  }
}

require curl
require git

# Node.js (fnm経由)
if ! command -v fnm &>/dev/null; then
  echo "fnm をインストール中..."
  curl -fsSL https://fnm.vercel.app/install | bash
  export PATH="$HOME/.local/share/fnm:$PATH"
fi

fnm install 22
fnm use 22

# dotfilesのセットアップ
DOTFILES="$HOME/dotfiles"
if [[ ! -d "$DOTFILES" ]]; then
  git clone https://github.com/yourname/dotfiles "$DOTFILES"
fi

# シンボリックリンクの作成
CONFIGS=(".bashrc" ".gitconfig" ".tmux.conf")
for config in "${CONFIGS[@]}"; do
  ln -sf "$DOTFILES/$config" "$HOME/$config"
done

echo "セットアップ完了!"

よく使うイディオム

# スクリプト自身のディレクトリを取得
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# 一時ファイルを作って終了時に削除
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT

# コマンドの出力を変数に格納
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

# 並列実行
for task in task1 task2 task3; do
  process_task "$task" &
done
wait  # 全バックグラウンドプロセスの完了を待つ

# デフォルト値
: "${PORT:=3000}"    # PORT が未定義なら 3000
: "${ENV:=development}"

デバッグ

bash -x script.sh   # 実行されるコマンドをすべて表示
bash -n script.sh   # 構文チェックのみ(実行しない)

# スクリプト内で部分的にデバッグ
set -x   # ここからデバッグ出力
# ... 怪しい部分 ...
set +x   # デバッグ出力を止める

まとめ

  1. 必ず先頭に set -euo pipefail を書く
  2. 変数は常に "${VAR}" と二重引用符で囲む(スペース対策)
  3. ファイルパスは find + while read で安全に処理
  4. trap でクリーンアップを必ず実装
  5. shellcheck でスクリプトの問題を自動検出
# shellcheck のインストールと使用
sudo apt install shellcheck   # Ubuntu/Debian
shellcheck script.sh

参考: Bash リファレンスマニュアル / ShellCheck / Google Shell Style Guide