mcpField
B
bash-defensive-patterns

bash-defensive-patterns

github.com/ovachiever開発5643 回インストール48 週間前に更新
リポジトリ

bash-defensive-patterns について

自動化、CI/CD、システム管理、デプロイ用スクリプトを書く開発者向けの、防御的なBash記述パターンをまとめたスキルです。エラー処理、安全な操作、ログ、再実行性を扱います。

主な機能

  1. 01厳格モードとエラートラップ
  2. 02変数と配列の安全な処理
  3. 03堅牢な引数解析
  4. 04構造化ログとクリーンアップ
  5. 05ドライランと冪等性のパターン

活用シーン

  1. 01CI/CD自動化スクリプトの作成
  2. 02より安全なデプロイツールの構築
  3. 03一時ファイルと障害への対処

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ovachiever/droid-tings --skill bash-defensive-patterns

インストール後、Claude Code で/bash-defensive-patternsを入力して読み込まれたか確認します。

詳細

Bash の防御的なパターン

防御的プログラミングの手法、エラー処理、安全性に関するベストプラクティスを使って、本番環境に対応できる Bash スクリプトを作成するための包括的なガイダンスです。よくある落とし穴を防ぎ、信頼性を確保します。

このスキルを使用する場面

  • 本番環境向けの自動化スクリプトを作成するとき
  • CI/CD パイプラインのスクリプトを構築するとき
  • システム管理用ユーティリティを作成するとき
  • エラーに強いデプロイ自動化を開発するとき
  • エッジケースを安全に処理する必要があるスクリプトを作成するとき
  • 保守しやすいシェルスクリプトライブラリを構築するとき
  • 包括的なログ記録とモニタリングを実装するとき
  • さまざまなプラットフォームで動作する必要があるスクリプトを作成するとき

防御的プログラミングの基本原則

1. 厳格モード

エラーを早期に検出するため、すべてのスクリプトの先頭で Bash の厳格モードを有効にします。

#!/bin/bash
set -Eeuo pipefail  # Exit on error, unset variables, pipe failures

主なフラグ:

  • set -E:関数内でも ERR トラップを継承します
  • set -e:エラー(コマンドがゼロ以外の値を返す)が発生したら終了します
  • set -u:未定義の変数を参照したら終了します
  • set -o pipefail:パイプ内の最後のコマンドだけでなく、いずれかのコマンドが失敗した場合にパイプを失敗とします
2. エラートラップとクリーンアップ

スクリプトの終了時やエラー発生時に、適切なクリーンアップを実行します。

#!/bin/bash
set -Eeuo pipefail

trap 'echo "Error on line $LINENO"' ERR
trap 'echo "Cleaning up..."; rm -rf "$TMPDIR"' EXIT

TMPDIR=$(mktemp -d)
# Script code here
3. 変数の安全な取り扱い

単語分割やグロブ展開の問題を防ぐため、常に変数を引用符で囲みます。

# Wrong - unsafe
cp $source $dest

# Correct - safe
cp "$source" "$dest"

# Required variables - fail with message if unset
: "${REQUIRED_VAR:?REQUIRED_VAR is not set}"
4. 配列の取り扱い

複雑なデータを安全に処理するには、配列を使用します。

# Safe array iteration
declare -a items=("item 1" "item 2" "item 3")

for item in "${items[@]}"; do
    echo "Processing: $item"
done

# Reading output into array safely
mapfile -t lines < <(some_command)
readarray -t numbers < <(seq 1 10)
5. 条件式の安全な使用

Bash 固有の機能には [[ ]] を、POSIX では [ ] を使用します。

# Bash - safer
if [[ -f "$file" && -r "$file" ]]; then
    content=$(<"$file")
fi

# POSIX - portable
if [ -f "$file" ] && [ -r "$file" ]; then
    content=$(cat "$file")
fi

# Test for existence before operations
if [[ -z "${VAR:-}" ]]; then
    echo "VAR is not set or is empty"
fi

基本的なパターン

パターン 1:スクリプトのディレクトリを安全に検出する
#!/bin/bash
set -Eeuo pipefail

# Correctly determine script directory
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"

echo "Script location: $SCRIPT_DIR/$SCRIPT_NAME"
パターン 2:包括的な関数テンプレート
#!/bin/bash
set -Eeuo pipefail

# Prefix for functions: handle_*, process_*, check_*, validate_*
# Include documentation and error handling

validate_file() {
    local -r file="$1"
    local -r message="${2:-File not found: $file}"

    if [[ ! -f "$file" ]]; then
        echo "ERROR: $message" >&2
        return 1
    fi
    return 0
}

process_files() {
    local -r input_dir="$1"
    local -r output_dir="$2"

    # Validate inputs
    [[ -d "$input_dir" ]] || { echo "ERROR: input_dir not a directory" >&2; return 1; }

    # Create output directory if needed
    mkdir -p "$output_dir" || { echo "ERROR: Cannot create output_dir" >&2; return 1; }

    # Process files safely
    while IFS= read -r -d '' file; do
        echo "Processing: $file"
        # Do work
    done < <(find "$input_dir" -maxdepth 1 -type f -print0)

    return 0
}
パターン 3:一時ファイルを安全に扱う
#!/bin/bash
set -Eeuo pipefail

trap 'rm -rf -- "$TMPDIR"' EXIT

# Create temporary directory
TMPDIR=$(mktemp -d) || { echo "ERROR: Failed to create temp directory" >&2; exit 1; }

# Create temporary files in directory
TMPFILE1="$TMPDIR/temp1.txt"
TMPFILE2="$TMPDIR/temp2.txt"

# Use temporary files
touch "$TMPFILE1" "$TMPFILE2"

echo "Temp files created in: $TMPDIR"
パターン 4:堅牢な引数解析
#!/bin/bash
set -Eeuo pipefail

# Default values
VERBOSE=false
DRY_RUN=false
OUTPUT_FILE=""
THREADS=4

usage() {
    cat <<EOF
Usage: $0 [OPTIONS]

Options:
    -v, --verbose       Enable verbose output
    -d, --dry-run       Run without making changes
    -o, --output FILE   Output file path
    -j, --jobs NUM      Number of parallel jobs
    -h, --help          Show this help message
EOF
    exit "${1:-0}"
}

# Parse arguments
while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -d|--dry-run)
            DRY_RUN=true
            shift
            ;;
        -o|--output)
            OUTPUT_FILE="$2"
            shift 2
            ;;
        -j|--jobs)
            THREADS="$2"
            shift 2
            ;;
        -h|--help)
            usage 0
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "ERROR: Unknown option: $1" >&2
            usage 1
            ;;
    esac
done

# Validate required arguments
[[ -n "$OUTPUT_FILE" ]] || { echo "ERROR: -o/--output is required" >&2; usage 1; }
パターン 5:構造化ログ
#!/bin/bash
set -Eeuo pipefail

# Logging functions
log_info() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*" >&2
}

log_warn() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*" >&2
}

log_error() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
}

log_debug() {
    if [[ "${DEBUG:-0}" == "1" ]]; then
        echo "[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $*" >&2
    fi
}

# Usage
log_info "Starting script"
log_debug "Debug information"
log_warn "Warning message"
log_error "Error occurred"
パターン 6:シグナルを使ったプロセスのオーケストレーション
#!/bin/bash
set -Eeuo pipefail

# Track background processes
PIDS=()

cleanup() {
    log_info "Shutting down..."

    # Terminate all background processes
    for pid in "${PIDS[@]}"; do
        if kill -0 "$pid" 2>/dev/null; then
            kill -TERM "$pid" 2>/dev/null || true
        fi
    done

    # Wait for graceful shutdown
    for pid in "${PIDS[@]}"; do
        wait "$pid" 2>/dev/null || true
    done
}

trap cleanup SIGTERM SIGINT

# Start background tasks
background_task &
PIDS+=($!)

another_task &
PIDS+=($!)

# Wait for all background processes
wait
パターン 7:ファイル操作を安全に行う
#!/bin/bash
set -Eeuo pipefail

# Use -i flag to move safely without overwriting
safe_move() {
    local -r source="$1"
    local -r dest="$2"

    if [[ ! -e "$source" ]]; then
        echo "ERROR: Source does not exist: $source" >&2
        return 1
    fi

    if [[ -e "$dest" ]]; then
        echo "ERROR: Destination already exists: $dest" >&2
        return 1
    fi

    mv "$source" "$dest"
}

# Safe directory cleanup
safe_rmdir() {
    local -r dir="$1"

    if [[ ! -d "$dir" ]]; then
        echo "ERROR: Not a directory: $dir" >&2
        return 1
    fi

    # Use -I flag to prompt before rm (BSD/GNU compatible)
    rm -rI -- "$dir"
}

# Atomic file writes
atomic_write() {
    local -r target="$1"
    local -r tmpfile
    tmpfile=$(mktemp) || return 1

    # Write to temp file first
    cat > "$tmpfile"

    # Atomic rename
    mv "$tmpfile" "$target"
}
パターン 8:冪等性を備えたスクリプト設計
#!/bin/bash
set -Eeuo pipefail

# Check if resource already exists
ensure_directory() {
    local -r dir="$1"

    if [[ -d "$dir" ]]; then
        log_info "Directory already exists: $dir"
        return 0
    fi

    mkdir -p "$dir" || {
        log_error "Failed to create directory: $dir"
        return 1
    }

    log_info "Created directory: $dir"
}

# Ensure configuration state
ensure_config() {
    local -r config_file="$1"
    local -r default_value="$2"

    if [[ ! -f "$config_file" ]]; then
        echo "$default_value" > "$config_file"
        log_info "Created config: $config_file"
    fi
}

# Rerunning script multiple times should be safe
ensure_directory "/var/cache/myapp"
ensure_config "/etc/myapp/config" "DEBUG=false"
パターン 9:コマンド置換を安全に行う
#!/bin/bash
set -Eeuo pipefail

# Use $() instead of backticks
name=$(<"$file")  # Modern, safe variable assignment from file
output=$(command -v python3)  # Get command location safely

# Handle command substitution with error checking
result=$(command -v node) || {
    log_error "node command not found"
    return 1
}

# For multiple lines
mapfile -t lines < <(grep "pattern" "$file")

# NUL-safe iteration
while IFS= read -r -d '' file; do
    echo "Processing: $file"
done < <(find /path -type f -print0)
パターン 10:ドライランのサポート
#!/bin/bash
set -Eeuo pipefail

DRY_RUN="${DRY_RUN:-false}"

run_cmd() {
    if [[ "$DRY_RUN" == "true" ]]; then
        echo "[DRY RUN] Would execute: $*"
        return 0
    fi

    "$@"
}

# Usage
run_cmd cp "$source" "$dest"
run_cmd rm "$file"
run_cmd chown "$owner" "$target"

高度な防御的テクニック

名前付きパラメーターのパターン
#!/bin/bash
set -Eeuo pipefail

process_data() {
    local input_file=""
    local output_dir=""
    local format="json"

    # Parse named parameters
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --input=*)
                input_file="${1#*=}"
                ;;
            --output=*)
                output_dir="${1#*=}"
                ;;
            --format=*)
                format="${1#*=}"
                ;;
            *)
                echo "ERROR: Unknown parameter: $1" >&2
                return 1
                ;;
        esac
        shift
    done

    # Validate required parameters
    [[ -n "$input_file" ]] || { echo "ERROR: --input is required" >&2; return 1; }
    [[ -n "$output_dir" ]] || { echo "ERROR: --output is required" >&2; return 1; }
}
依存関係の確認
#!/bin/bash
set -Eeuo pipefail

check_dependencies() {
    local -a missing_deps=()
    local -a required=("jq" "curl" "git")

    for cmd in "${required[@]}"; do
        if ! command -v "$cmd" &>/dev/null; then
            missing_deps+=("$cmd")
        fi
    done

    if [[ ${#missing_deps[@]} -gt 0 ]]; then
        echo "ERROR: Missing required commands: ${missing_deps[*]}" >&2
        return 1
    fi
}

check_dependencies

ベストプラクティスのまとめ

  1. 常に厳格モードを使用する - set -Eeuo pipefail
  2. すべての変数を引用符で囲む - "$variable" を使うと単語分割を防げます
  3. [[ ]] の条件式を使う - [ ] より堅牢です
  4. エラートラップを実装する - エラーを検出し、適切に処理します
  5. すべての入力を検証する - ファイルの存在、権限、形式を確認します
  6. 再利用できるよう関数を使う - 意味の分かる名前を接頭辞として付けます
  7. 構造化ログを実装する - タイムスタンプとレベルを含めます
  8. ドライランモードをサポートする - ユーザーが変更を事前に確認できるようにします
  9. 一時ファイルを安全に扱う - mktemp を使い、trap でクリーンアップします
  10. 冪等性を考慮して設計する - スクリプトを安全に再実行できるようにします
  11. 要件を文書化する - 依存関係と最小バージョンを記載します
  12. エラー経路をテストする - エラー処理が正しく機能することを確認します
  13. command -v を使う - 実行可能ファイルの確認には which より安全です
  14. echo より printf を優先する - システム間でより予測しやすく動作します

リソース

コメント