基本操作

Linuxシェルスクリプト入門|日常業務を自動化する

Linux シェルスクリプト Bash

Linuxシェルスクリプト入門
日常業務を自動化する

Bashシェルスクリプトの基本構文から実践的な自動化スクリプトまで解説。変数、条件分岐、繰り返し、関数を使って日常業務を効率化しましょう。

こんな人向けの記事です

  • シェルスクリプトを初めて書く
  • 日常のLinux作業を自動化したい
  • Bashの基本構文を体系的に学びたい

Step 1シェルスクリプトとは

シェルスクリプトは、Linuxのコマンドをファイルにまとめて一括実行するためのプログラムです。普段ターミナルで手動実行しているコマンドを自動化できます。

シェルスクリプトのメリット:
  • 作業の自動化: 定型作業をワンコマンドで実行
  • ミスの防止: 手動入力によるヒューマンエラーを削減
  • 再現性: 同じ手順を何度でも正確に再実行
  • 記録: 手順がスクリプトとして残るためドキュメント代わりになる

最初のスクリプトを書く

シェルスクリプトの1行目にはシバン(shebang)を記述します。これはスクリプトをどのシェルで実行するかを指定する宣言です。

hello.sh
#!/bin/bash
# これはコメントです
echo "Hello, Shell Script!"
echo "現在の日時: $(date)"
echo "現在のユーザー: $(whoami)"
echo "現在のディレクトリ: $(pwd)"

スクリプトの実行方法

ターミナル
# 実行権限を付与
chmod +x hello.sh

# 方法1: 直接実行
./hello.sh

# 方法2: bashコマンドで実行(権限付与不要)
bash hello.sh

# 方法3: sourceで現在のシェルで実行
source hello.sh
注意: ./hello.sh で実行するには、必ず chmod +x で実行権限を付与してください。権限がないと「Permission denied」エラーになります。
実行方法特徴用途
./script.shサブシェルで実行一般的なスクリプト実行
bash script.sh権限不要でサブシェル実行テスト・デバッグ時
source script.sh現在のシェルで実行環境変数の設定など

Step 2変数と環境変数

変数はデータを格納する入れ物です。Bashでは変数の宣言に型指定は不要で、すべて文字列として扱われます。

変数の基本

variables.sh
#!/bin/bash

# 変数の代入(=の前後にスペースを入れない)
name="Taro"
age=25
message="こんにちは"

# 変数の参照($を付ける)
echo "名前: $name"
echo "年齢: ${age}歳"
echo "$message, ${name}さん!"

# コマンドの結果を変数に格納
today=$(date +%Y-%m-%d)
file_count=$(ls | wc -l)
echo "今日の日付: $today"
echo "ファイル数: $file_count"
注意: 変数の代入時に = の前後にスペースを入れるとエラーになります。name = "Taro" は間違いで、name="Taro" が正しい書き方です。

特殊変数

変数意味
$0スクリプト名./backup.sh
$1, $2...引数(1番目、2番目...)./backup.sh /home
$#引数の個数3
$@すべての引数(個別)"arg1" "arg2" "arg3"
$?直前のコマンドの終了ステータス0(成功)
$$現在のプロセスID12345

環境変数

env_vars.sh
#!/bin/bash

# よく使う環境変数
echo "ホームディレクトリ: $HOME"
echo "ユーザー名: $USER"
echo "パス: $PATH"
echo "シェル: $SHELL"
echo "ホスト名: $HOSTNAME"

# 環境変数の設定(exportで子プロセスに引き継ぐ)
export MY_APP_ENV="production"
export MY_APP_PORT=8080

# 環境変数の確認
env | grep MY_APP

# 変数と環境変数の違い
local_var="ローカル"      # このシェルだけで有効
export global_var="グローバル"  # 子プロセスでも有効
変数と環境変数の違い:
  • 変数: 現在のシェルでのみ有効。子プロセスには引き継がれない
  • 環境変数: exportで宣言し、子プロセスにも引き継がれる
  • 永続化: ~/.bashrc~/.bash_profile に記述すると起動時に自動設定

Step 3条件分岐(if・case)

条件分岐を使うと、状況に応じて異なる処理を実行できます。ファイルの存在確認やコマンドの成否判定などに活用します。

if文の基本

condition.sh
#!/bin/bash

# 基本的なif文
score=85

if [ $score -ge 80 ]; then
    echo "優秀です"
elif [ $score -ge 60 ]; then
    echo "合格です"
else
    echo "再試験です"
fi

# 文字列の比較
name="admin"
if [ "$name" = "admin" ]; then
    echo "管理者としてログイン"
fi

# ファイルの存在確認
if [ -f "/etc/hosts" ]; then
    echo "/etc/hosts は存在します"
fi

if [ -d "/var/log" ]; then
    echo "/var/log ディレクトリが存在します"
fi

テスト演算子

カテゴリ演算子意味
数値比較-eq等しい(equal)
-ne等しくない(not equal)
-gtより大きい(greater than)
-ge以上(greater or equal)
-ltより小さい(less than)
-le以下(less or equal)
文字列比較=等しい
!=等しくない
-z空文字列
ファイル-f通常ファイルが存在する
-dディレクトリが存在する
-r読み取り可能
-w書き込み可能

case文

case文は、1つの値に対して複数のパターンマッチングを行う場合に便利です。

case_example.sh
#!/bin/bash

# 引数で動作を切り替えるスクリプト
case "$1" in
    start)
        echo "サービスを起動します"
        ;;
    stop)
        echo "サービスを停止します"
        ;;
    restart)
        echo "サービスを再起動します"
        ;;
    status)
        echo "サービスの状態を確認します"
        ;;
    *)
        echo "使い方: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

# ファイル拡張子で処理を分岐
filename="report.pdf"
case "$filename" in
    *.txt)
        echo "テキストファイルです" ;;
    *.pdf)
        echo "PDFファイルです" ;;
    *.jpg|*.png|*.gif)
        echo "画像ファイルです" ;;
    *)
        echo "不明なファイル形式です" ;;
esac

Step 4繰り返し(for・while)

繰り返し処理は、複数のファイルに対する一括操作やログの監視など、自動化の中核となる構文です。

for文

for_loop.sh
#!/bin/bash

# リストを使ったfor文
for fruit in apple banana cherry; do
    echo "フルーツ: $fruit"
done

# 連番の生成
for i in {1..5}; do
    echo "番号: $i"
done

# ステップ付き連番(1から10まで2刻み)
for i in {1..10..2}; do
    echo "奇数: $i"
done

# ファイルを一括処理
for file in /var/log/*.log; do
    echo "ログファイル: $file ($(wc -l < "$file") 行)"
done

# C言語スタイルのfor文
for ((i=0; i<5; i++)); do
    echo "カウント: $i"
done

while文

while_loop.sh
#!/bin/bash

# 基本的なwhile文
count=1
while [ $count -le 5 ]; do
    echo "カウント: $count"
    count=$((count + 1))
done

# ファイルを1行ずつ読み込む
while IFS= read -r line; do
    echo "行: $line"
done < /etc/hostname

# CSVファイルの処理
while IFS=',' read -r name age city; do
    echo "名前: $name, 年齢: $age, 都市: $city"
done << 'CSV'
田中,30,東京
鈴木,25,大阪
佐藤,35,福岡
CSV

# 無限ループ(Ctrl+Cで停止)
# while true; do
#     echo "監視中... $(date)"
#     sleep 5
# done
ループ制御:
  • break: ループを即座に終了する
  • continue: 現在の回をスキップして次の回へ進む
  • break 2: 2重ループの外側まで一気に抜ける
loop_control.sh
#!/bin/bash

# breakとcontinueの例
for i in {1..10}; do
    # 偶数はスキップ
    if [ $((i % 2)) -eq 0 ]; then
        continue
    fi
    # 7を超えたら終了
    if [ $i -gt 7 ]; then
        break
    fi
    echo "値: $i"
done
# 出力: 1, 3, 5, 7

Step 5関数の定義と活用

関数を使うと、よく使う処理をまとめて名前を付け、何度でも呼び出せます。スクリプトの可読性と保守性が大幅に向上します。

関数の基本構文

functions.sh
#!/bin/bash

# 関数の定義(2つの書き方)
# 方法1: function キーワード
function greet() {
    echo "こんにちは、$1さん!"
}

# 方法2: キーワード省略(POSIX互換)
say_goodbye() {
    echo "さようなら、$1さん!"
}

# 関数の呼び出し
greet "太郎"
say_goodbye "花子"

引数と戻り値

func_args.sh
#!/bin/bash

# 複数の引数を受け取る関数
create_user() {
    local username="$1"
    local role="$2"
    echo "ユーザー作成: $username (役割: $role)"
}
create_user "admin" "管理者"

# 戻り値を使う関数(returnは0-255の整数のみ)
is_file_exists() {
    if [ -f "$1" ]; then
        return 0  # 成功(true)
    else
        return 1  # 失敗(false)
    fi
}

if is_file_exists "/etc/hosts"; then
    echo "ファイルが存在します"
fi

# 文字列を返す場合はechoを使う
get_timestamp() {
    echo "$(date +%Y%m%d_%H%M%S)"
}
timestamp=$(get_timestamp)
echo "タイムスタンプ: $timestamp"

# ローカル変数
calculate() {
    local result=$(( $1 + $2 ))
    echo $result
}
sum=$(calculate 10 20)
echo "合計: $sum"
注意: 関数内の変数は local を付けないとグローバルスコープになります。意図しない変数の上書きを防ぐため、関数内では必ず local を使いましょう。

Step 6実践スクリプト集

ここまでの知識を組み合わせて、実際の業務で使える自動化スクリプトを紹介します。

バックアップスクリプト

backup.sh
#!/bin/bash

# === 設定 ===
BACKUP_SOURCE="/home/user/documents"
BACKUP_DEST="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DEST}/backup_${DATE}.tar.gz"
LOG_FILE="${BACKUP_DEST}/backup.log"
KEEP_DAYS=30

# === 関数 ===
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

check_directory() {
    if [ ! -d "$1" ]; then
        mkdir -p "$1"
        log_message "ディレクトリを作成: $1"
    fi
}

# === メイン処理 ===
log_message "=== バックアップ開始 ==="

# バックアップ先の確認
check_directory "$BACKUP_DEST"

# バックアップの実行
if tar -czf "$BACKUP_FILE" -C "$(dirname "$BACKUP_SOURCE")" "$(basename "$BACKUP_SOURCE")" 2>> "$LOG_FILE"; then
    SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    log_message "成功: $BACKUP_FILE ($SIZE)"
else
    log_message "エラー: バックアップに失敗しました"
    exit 1
fi

# 古いバックアップの削除
deleted=$(find "$BACKUP_DEST" -name "backup_*.tar.gz" -mtime +${KEEP_DAYS} -delete -print | wc -l)
if [ "$deleted" -gt 0 ]; then
    log_message "${KEEP_DAYS}日以前のバックアップを${deleted}件削除しました"
fi

log_message "=== バックアップ完了 ==="

ログ監視スクリプト

log_monitor.sh
#!/bin/bash

# === 設定 ===
WATCH_LOG="/var/log/syslog"
ALERT_KEYWORDS=("ERROR" "CRITICAL" "FATAL" "panic")
CHECK_INTERVAL=10
ALERT_LOG="/tmp/alert_$(date +%Y%m%d).log"

# === 関数 ===
send_alert() {
    local keyword="$1"
    local line="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] ALERT [$keyword]: $line" >> "$ALERT_LOG"
    echo -e "\033[31m[ALERT]\033[0m $keyword が検出されました"
}

check_log() {
    local last_line_count="$1"
    local current_line_count=$(wc -l < "$WATCH_LOG")

    if [ "$current_line_count" -gt "$last_line_count" ]; then
        local new_lines=$(tail -n $((current_line_count - last_line_count)) "$WATCH_LOG")

        for keyword in "${ALERT_KEYWORDS[@]}"; do
            while IFS= read -r line; do
                send_alert "$keyword" "$line"
            done <<< "$(echo "$new_lines" | grep -i "$keyword")"
        done
    fi

    echo "$current_line_count"
}

# === メイン処理 ===
echo "=== ログ監視開始: $WATCH_LOG ==="
echo "監視キーワード: ${ALERT_KEYWORDS[*]}"
echo "チェック間隔: ${CHECK_INTERVAL}秒"
echo "Ctrl+C で停止"

line_count=$(wc -l < "$WATCH_LOG")

while true; do
    line_count=$(check_log "$line_count")
    sleep "$CHECK_INTERVAL"
done

システム情報レポートスクリプト

system_report.sh
#!/bin/bash

# === システム情報レポート生成 ===
REPORT_FILE="/tmp/system_report_$(date +%Y%m%d).txt"

separator() {
    echo "========================================" >> "$REPORT_FILE"
}

section() {
    echo "" >> "$REPORT_FILE"
    separator
    echo " $1" >> "$REPORT_FILE"
    separator
}

{
    echo "システム情報レポート"
    echo "生成日時: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "ホスト名: $(hostname)"

    section "OS情報"
    uname -a

    section "ディスク使用量"
    df -h | grep -v tmpfs

    section "メモリ使用量"
    free -h

    section "CPU情報"
    nproc
    echo "CPU数: $(nproc)"

    section "ネットワーク"
    ip addr show 2>/dev/null || ifconfig 2>/dev/null

    section "稼働時間"
    uptime

    section "ログインユーザー"
    who

} > "$REPORT_FILE" 2>&1

echo "レポートを生成しました: $REPORT_FILE"
echo "ファイルサイズ: $(du -h "$REPORT_FILE" | cut -f1)"
スクリプト作成のベストプラクティス:
  • シバンを書く: 必ず #!/bin/bash を1行目に記述
  • コメントを残す: 処理の目的や注意点を記述
  • 変数を引用符で囲む: "$var" のようにダブルクォートで囲む
  • エラーハンドリング: set -e でエラー時に即停止させる
  • ローカル変数: 関数内では local を使う

シェルスクリプト学習チェックリスト

  • #!/bin/bash の意味と役割を理解した
  • 変数の代入・参照ができる
  • 環境変数と通常の変数の違いを理解した
  • if文とテスト演算子で条件分岐が書ける
  • case文でパターンマッチングができる
  • for文・while文でループ処理が書ける
  • 関数の定義・引数・戻り値を扱える
  • 実用的なバックアップスクリプトが書ける