はじめに

シェルスクリプトを本格的に活用するには、条件分岐やループといった制御構文の理解が不可欠です。単純なコマンドの羅列だけでは、状況に応じた処理の切り替えや大量のデータに対する繰り返し処理を行うことができません。

本記事では、シェルスクリプトの制御構文を体系的に解説します。if文による条件分岐、testコマンドと[[ ]]による条件評価、数値・文字列・ファイル判定の比較演算子、for/while/untilによるループ処理、そしてcase文によるパターンマッチまでを網羅します。

これらの制御構文をマスターすることで、ファイルの存在確認、ユーザー入力の検証、ログファイルの一括処理など、実務で求められる柔軟なシェルスクリプトを書けるようになります。

動作確認環境

本記事のシェルスクリプトは以下の環境で動作確認しています。

項目 内容
OS Ubuntu 24.04 LTS
シェル bash 5.3
カーネル Linux 6.8

WSL2、VirtualBox上のLinux環境、macOS(zsh互換モード)、その他の主要ディストリビューション(AlmaLinux、Debian等)でも同様に動作します。

シェルスクリプトの制御構文の全体像

シェルスクリプトで使用できる主な制御構文を以下に整理します。

graph TB
    A[シェルスクリプト<br/>制御構文] --> B[条件分岐]
    A --> C[ループ]
    A --> D[パターンマッチ]
    B --> B1[if文]
    B --> B2[test/[[ ]]]
    C --> C1[for文]
    C --> C2[while文]
    C --> C3[until文]
    D --> D1[case文]
構文 用途
if 条件による処理分岐 ファイル存在確認後の処理切り替え
for リストに対する繰り返し 複数ファイルの一括処理
while 条件が真の間繰り返し ログ監視、ユーザー入力待ち
until 条件が偽の間繰り返し 特定条件の成立を待つ
case パターンによる多分岐 コマンドラインオプションの処理

if文による条件分岐

if文はシェルスクリプトで最も基本的な制御構文です。条件式の評価結果に基づいて処理を分岐させます。

if文の基本構文

if文の基本的な構文は以下のとおりです。

1
2
3
4
5
#!/bin/bash

if 条件式; then
    # 条件が真のときの処理
fi

条件式には、コマンドの終了ステータス(0で成功、1以上で失敗)が使われます。終了ステータスが0のとき「真」、それ以外のとき「偽」と評価されます。

簡単なif文の例

ファイルが存在するかを確認する例を見てみましょう。

1
2
3
4
5
6
7
#!/bin/bash

FILE="/etc/passwd"

if [ -f "$FILE" ]; then
    echo "$FILE は存在します"
fi

実行結果は以下のようになります。

1
/etc/passwd は存在します

if-else文

条件が偽の場合の処理を追加するには、elseを使います。

1
2
3
4
5
6
7
8
9
#!/bin/bash

FILE="/tmp/test.txt"

if [ -f "$FILE" ]; then
    echo "$FILE は存在します"
else
    echo "$FILE は存在しません"
fi

if-elif-else文

複数の条件を順に評価する場合は、elifを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash

SCORE=75

if [ "$SCORE" -ge 90 ]; then
    echo "評価: A"
elif [ "$SCORE" -ge 80 ]; then
    echo "評価: B"
elif [ "$SCORE" -ge 70 ]; then
    echo "評価: C"
elif [ "$SCORE" -ge 60 ]; then
    echo "評価: D"
else
    echo "評価: F"
fi

実行結果は以下のようになります。

1
評価: C

testコマンドと条件評価

シェルスクリプトの条件評価には、testコマンドまたはその省略形である[ ]、拡張版の[[ ]]を使用します。

testコマンドの基本

testコマンドは条件を評価し、終了ステータスを返します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

# testコマンドを使用
if test -f "/etc/passwd"; then
    echo "ファイルが存在します"
fi

# [ ] は testコマンドの省略形
if [ -f "/etc/passwd" ]; then
    echo "ファイルが存在します"
fi

[ ]と[[ ]]の違い

bashでは、[ ]の拡張版として[[ ]]が使用できます。

比較項目 [ ] [[ ]]
POSIX準拠 準拠 bash拡張
ワード分割 発生する 発生しない
パターンマッチ 不可 可能(=~, ==
論理演算子 -a, -o &&, `
空文字列対策 必要 不要

[[ ]]を使用する主なメリットを示す例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

VAR=""

# [ ] ではクォートが必須(空文字列でエラー回避)
if [ -z "$VAR" ]; then
    echo "VAR は空です([ ]使用)"
fi

# [[ ]] ではクォートがなくても安全
if [[ -z $VAR ]]; then
    echo "VAR は空です([[ ]]使用)"
fi

正規表現によるパターンマッチ

[[ ]]では=~演算子を使って正規表現によるマッチングができます。

1
2
3
4
5
6
7
8
9
#!/bin/bash

EMAIL="user@example.com"

if [[ "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有効なメールアドレスです"
else
    echo "無効なメールアドレスです"
fi

実行結果は以下のようになります。

1
有効なメールアドレスです

比較演算子

シェルスクリプトの比較演算子は、数値比較、文字列比較、ファイル判定の3種類に分類されます。

数値比較演算子

数値を比較するための演算子です。

演算子 意味 英語
-eq 等しい equal
-ne 等しくない not equal
-lt より小さい less than
-le 以下 less than or equal
-gt より大きい greater than
-ge 以上 greater than or equal

数値比較の例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash

NUM1=10
NUM2=20

if [ "$NUM1" -lt "$NUM2" ]; then
    echo "$NUM1$NUM2 より小さい"
fi

if [ "$NUM1" -ne "$NUM2" ]; then
    echo "$NUM1$NUM2 は等しくない"
fi

実行結果は以下のようになります。

1
2
10 は 20 より小さい
10 と 20 は等しくない

算術演算での比較

(( ))を使うと、より直感的な数値比較が可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash

NUM1=10
NUM2=20

if (( NUM1 < NUM2 )); then
    echo "$NUM1$NUM2 より小さい"
fi

if (( NUM1 != NUM2 )); then
    echo "$NUM1$NUM2 は等しくない"
fi

文字列比較演算子

文字列を比較するための演算子です。

演算子 意味
= または == 等しい
!= 等しくない
-z 空文字列(長さが0)
-n 空でない(長さが1以上)
< 辞書順で小さい
> 辞書順で大きい

文字列比較の例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

STR1="hello"
STR2="world"
EMPTY=""

# 文字列の等価比較
if [ "$STR1" = "hello" ]; then
    echo "STR1 は hello です"
fi

# 文字列の不等価比較
if [ "$STR1" != "$STR2" ]; then
    echo "STR1 と STR2 は異なります"
fi

# 空文字列の判定
if [ -z "$EMPTY" ]; then
    echo "EMPTY は空文字列です"
fi

# 空でないことの判定
if [ -n "$STR1" ]; then
    echo "STR1 は空ではありません"
fi

実行結果は以下のようになります。

1
2
3
4
STR1 は hello です
STR1 と STR2 は異なります
EMPTY は空文字列です
STR1 は空ではありません

ファイル判定演算子

ファイルやディレクトリの状態を確認するための演算子です。

演算子 意味
-e 存在する(ファイルまたはディレクトリ)
-f 通常ファイルとして存在する
-d ディレクトリとして存在する
-r 読み取り権限がある
-w 書き込み権限がある
-x 実行権限がある
-s サイズが0より大きい
-L シンボリックリンクである

ファイル判定の例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

FILE="/etc/passwd"
DIR="/tmp"
SCRIPT="$0"

# ファイル存在確認
if [ -f "$FILE" ]; then
    echo "$FILE は通常ファイルです"
fi

# ディレクトリ存在確認
if [ -d "$DIR" ]; then
    echo "$DIR はディレクトリです"
fi

# 読み取り権限確認
if [ -r "$FILE" ]; then
    echo "$FILE は読み取り可能です"
fi

# 実行権限確認
if [ -x "$SCRIPT" ]; then
    echo "$SCRIPT は実行可能です"
fi

ファイル比較演算子

2つのファイルを比較するための演算子もあります。

演算子 意味
-nt より新しい(newer than)
-ot より古い(older than)
-ef 同一ファイル(equal file)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

FILE1="/etc/passwd"
FILE2="/etc/shadow"

if [ "$FILE1" -nt "$FILE2" ]; then
    echo "$FILE1$FILE2 より新しい"
elif [ "$FILE1" -ot "$FILE2" ]; then
    echo "$FILE1$FILE2 より古い"
fi

論理演算子

複数の条件を組み合わせるための論理演算子について解説します。

[ ]内での論理演算子

演算子 意味
-a AND(かつ)
-o OR(または)
! NOT(否定)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

FILE="/etc/passwd"

# AND条件: ファイルが存在し、かつ読み取り可能
if [ -f "$FILE" -a -r "$FILE" ]; then
    echo "$FILE は存在し、読み取り可能です"
fi

# OR条件: どちらかが真
NUM=15
if [ "$NUM" -lt 10 -o "$NUM" -gt 20 ]; then
    echo "NUM は10未満または20超"
else
    echo "NUM は10以上20以下"
fi

# NOT条件
if [ ! -d "$FILE" ]; then
    echo "$FILE はディレクトリではありません"
fi

[[ ]]内での論理演算子

[[ ]]ではより直感的な&&||が使用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash

FILE="/etc/passwd"

# AND条件
if [[ -f "$FILE" && -r "$FILE" ]]; then
    echo "$FILE は存在し、読み取り可能です"
fi

# OR条件
NUM=15
if [[ $NUM -lt 10 || $NUM -gt 20 ]]; then
    echo "NUM は10未満または20超"
else
    echo "NUM は10以上20以下"
fi

複数の[ ]を組み合わせる

[ ]の外側で&&||を使用することもできます。

1
2
3
4
5
6
7
8
9
#!/bin/bash

FILE="/etc/passwd"

# && で複数の条件をチェーン
[ -f "$FILE" ] && [ -r "$FILE" ] && echo "$FILE は存在し、読み取り可能"

# || でエラーハンドリング
[ -f "/nonexistent" ] || echo "ファイルが存在しません"

forループ

forループはリストの各要素に対して繰り返し処理を実行します。

forループの基本構文

1
2
3
4
5
#!/bin/bash

for 変数 in リスト; do
    # 繰り返す処理
done

リストを使用したforループ

明示的なリストに対するループの例です。

1
2
3
4
5
6
#!/bin/bash

# 文字列リスト
for FRUIT in apple banana cherry; do
    echo "果物: $FRUIT"
done

実行結果は以下のようになります。

1
2
3
果物: apple
果物: banana
果物: cherry

数値範囲のforループ

bashでは{開始..終了}構文で数値範囲を指定できます。

1
2
3
4
5
6
#!/bin/bash

# 1から5までの数値
for i in {1..5}; do
    echo "カウント: $i"
done

実行結果は以下のようになります。

1
2
3
4
5
カウント: 1
カウント: 2
カウント: 3
カウント: 4
カウント: 5

ステップ(増分)を指定することも可能です。

1
2
3
4
5
6
#!/bin/bash

# 0から10まで2刻み
for i in {0..10..2}; do
    echo "偶数: $i"
done

実行結果は以下のようになります。

1
2
3
4
5
6
偶数: 0
偶数: 2
偶数: 4
偶数: 6
偶数: 8
偶数: 10

seqコマンドを使用したループ

変数で範囲を指定したい場合はseqコマンドを使用します。

1
2
3
4
5
6
7
8
#!/bin/bash

START=1
END=5

for i in $(seq $START $END); do
    echo "番号: $i"
done

C言語スタイルのforループ

bashではC言語スタイルのforループも使用できます。

1
2
3
4
5
#!/bin/bash

for ((i = 0; i < 5; i++)); do
    echo "インデックス: $i"
done

実行結果は以下のようになります。

1
2
3
4
5
インデックス: 0
インデックス: 1
インデックス: 2
インデックス: 3
インデックス: 4

ファイルに対するforループ

グロブパターンを使用してファイルを処理する例です。

1
2
3
4
5
6
7
8
9
#!/bin/bash

# カレントディレクトリの.shファイルを処理
for FILE in *.sh; do
    if [ -f "$FILE" ]; then
        echo "スクリプト: $FILE"
        head -1 "$FILE"  # 1行目(shebang)を表示
    fi
done

配列に対するforループ

配列の要素をループ処理する例です。

1
2
3
4
5
6
7
8
9
#!/bin/bash

# 配列を定義
SERVERS=("web01" "web02" "db01" "cache01")

# 配列の全要素をループ
for SERVER in "${SERVERS[@]}"; do
    echo "サーバー: $SERVER に接続中..."
done

実行結果は以下のようになります。

1
2
3
4
サーバー: web01 に接続中...
サーバー: web02 に接続中...
サーバー: db01 に接続中...
サーバー: cache01 に接続中...

コマンド出力に対するforループ

コマンドの出力結果をループ処理する例です。

1
2
3
4
5
6
#!/bin/bash

# ログインユーザーをループ
for USER in $(who | awk '{print $1}' | sort -u); do
    echo "ログイン中のユーザー: $USER"
done

whileループ

whileループは、条件が真の間、処理を繰り返します。

whileループの基本構文

1
2
3
4
5
#!/bin/bash

while 条件式; do
    # 繰り返す処理
done

カウンタを使用したwhileループ

1
2
3
4
5
6
7
8
#!/bin/bash

COUNT=1

while [ $COUNT -le 5 ]; do
    echo "カウント: $COUNT"
    COUNT=$((COUNT + 1))
done

実行結果は以下のようになります。

1
2
3
4
5
カウント: 1
カウント: 2
カウント: 3
カウント: 4
カウント: 5

ファイルを1行ずつ読み込む

whileループとreadコマンドを組み合わせてファイルを処理する典型的なパターンです。

1
2
3
4
5
6
7
8
#!/bin/bash

# /etc/passwdの各行を処理
while IFS=: read -r USER _ UID GID _ HOME SHELL; do
    if [ "$UID" -ge 1000 ] && [ "$UID" -lt 65534 ]; then
        echo "一般ユーザー: $USER (UID: $UID, ホーム: $HOME)"
    fi
done < /etc/passwd

この例では、IFS=:でフィールド区切り文字をコロンに設定し、/etc/passwdの各フィールドを変数に読み込んでいます。

パイプラインとwhileループ

コマンドの出力をパイプでwhileループに渡す例です。

1
2
3
4
5
6
7
8
9
#!/bin/bash

# プロセス一覧を処理
ps aux | while read -r USER PID CPU MEM VSZ RSS TTY STAT START TIME COMMAND; do
    # CPU使用率が高いプロセスを抽出
    if [[ "$CPU" =~ ^[0-9]+(\.[0-9]+)?$ ]] && (( $(echo "$CPU > 10" | bc -l) )); then
        echo "高CPU: $COMMAND (PID: $PID, CPU: $CPU%)"
    fi
done

無限ループとbreak

無限ループは特定の条件で終了させる必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash

COUNT=0

while true; do
    echo "処理中... ($COUNT)"
    COUNT=$((COUNT + 1))
    
    if [ $COUNT -ge 5 ]; then
        echo "5回実行したので終了します"
        break
    fi
    
    sleep 1
done

continueによるスキップ

ループ内で特定の条件をスキップする例です。

1
2
3
4
5
6
7
8
9
#!/bin/bash

for i in {1..10}; do
    # 3の倍数はスキップ
    if [ $((i % 3)) -eq 0 ]; then
        continue
    fi
    echo "数値: $i"
done

実行結果は以下のようになります。

1
2
3
4
5
6
7
数値: 1
数値: 2
数値: 4
数値: 5
数値: 7
数値: 8
数値: 10

untilループ

untilループは、条件が偽の間(つまり条件が真になるまで)処理を繰り返します。whileループの逆と考えることができます。

untilループの基本構文

1
2
3
4
5
#!/bin/bash

until 条件式; do
    # 繰り返す処理
done

untilループの例

1
2
3
4
5
6
7
8
#!/bin/bash

COUNT=1

until [ $COUNT -gt 5 ]; do
    echo "カウント: $COUNT"
    COUNT=$((COUNT + 1))
done

実行結果は以下のようになります。

1
2
3
4
5
カウント: 1
カウント: 2
カウント: 3
カウント: 4
カウント: 5

untilループの実践例: サービス起動待ち

特定の条件が満たされるまで待機するパターンです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

# Webサーバーが起動するまで待機
echo "Webサーバーの起動を待っています..."

until curl -s http://localhost:8080/health > /dev/null 2>&1; do
    echo "まだ起動していません。5秒後に再試行..."
    sleep 5
done

echo "Webサーバーが起動しました"

case文によるパターンマッチ

case文は、変数の値に基づいて複数の分岐を行う制御構文です。複数のif-elif文よりも読みやすく、パターンマッチングが可能です。

case文の基本構文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

case 変数 in
    パターン1)
        # パターン1に一致した場合の処理
        ;;
    パターン2)
        # パターン2に一致した場合の処理
        ;;
    *)
        # どのパターンにも一致しない場合の処理
        ;;
esac

基本的なcase文の例

ユーザー入力に基づいて処理を分岐する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

echo "好きな果物を入力してください (apple/banana/orange):"
read -r FRUIT

case "$FRUIT" in
    apple)
        echo "りんごを選びました"
        ;;
    banana)
        echo "バナナを選びました"
        ;;
    orange)
        echo "オレンジを選びました"
        ;;
    *)
        echo "不明な果物: $FRUIT"
        ;;
esac

パターンマッチングの活用

case文では、ワイルドカードを使用したパターンマッチングが可能です。

パターン 意味
* 任意の文字列
? 任意の1文字
[abc] a、b、cのいずれか1文字
[a-z] a〜zの範囲の1文字
` `
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

read -r -p "ファイル名を入力してください: " FILENAME

case "$FILENAME" in
    *.txt)
        echo "テキストファイルです"
        ;;
    *.sh)
        echo "シェルスクリプトです"
        ;;
    *.jpg|*.jpeg|*.png|*.gif)
        echo "画像ファイルです"
        ;;
    *.tar.gz|*.tgz)
        echo "tar.gzアーカイブです"
        ;;
    *)
        echo "その他のファイルタイプです"
        ;;
esac

コマンドラインオプションの処理

case文はコマンドラインオプションの解析によく使用されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash

show_help() {
    echo "使用法: $0 [オプション]"
    echo "オプション:"
    echo "  -h, --help      ヘルプを表示"
    echo "  -v, --version   バージョンを表示"
    echo "  -f, --file FILE ファイルを指定"
}

VERSION="1.0.0"
TARGET_FILE=""

while [ $# -gt 0 ]; do
    case "$1" in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--version)
            echo "バージョン: $VERSION"
            exit 0
            ;;
        -f|--file)
            if [ -n "$2" ]; then
                TARGET_FILE="$2"
                shift
            else
                echo "エラー: -f オプションにはファイル名が必要です"
                exit 1
            fi
            ;;
        -*)
            echo "不明なオプション: $1"
            show_help
            exit 1
            ;;
        *)
            echo "引数: $1"
            ;;
    esac
    shift
done

if [ -n "$TARGET_FILE" ]; then
    echo "対象ファイル: $TARGET_FILE"
fi

複数パターンの一致

|を使用して複数のパターンを1つの処理にまとめることができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash

echo "Yes/No で回答してください:"
read -r ANSWER

case "$ANSWER" in
    [Yy]|[Yy][Ee][Ss])
        echo "「はい」と回答しました"
        ;;
    [Nn]|[Nn][Oo])
        echo "「いいえ」と回答しました"
        ;;
    *)
        echo "Yes または No で回答してください"
        ;;
esac

この例では、大文字・小文字を問わず “y”, “yes”, “Y”, “YES”, “Yes” などすべてを許容しています。

実践的なシェルスクリプト例

ここまで学んだ制御構文を組み合わせた実践的なスクリプト例を紹介します。

ログファイル監視スクリプト

特定のキーワードを含むログエントリを監視するスクリプトです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash

# 設定
LOG_FILE="/var/log/syslog"
KEYWORDS=("error" "warning" "critical")
CHECK_INTERVAL=10

# ログファイルの存在確認
if [ ! -f "$LOG_FILE" ]; then
    echo "エラー: $LOG_FILE が見つかりません"
    exit 1
fi

if [ ! -r "$LOG_FILE" ]; then
    echo "エラー: $LOG_FILE を読み取れません"
    exit 1
fi

echo "ログ監視を開始します: $LOG_FILE"
echo "監視キーワード: ${KEYWORDS[*]}"
echo "チェック間隔: ${CHECK_INTERVAL}秒"

# 現在の行数を記録
LAST_LINE=$(wc -l < "$LOG_FILE")

while true; do
    sleep $CHECK_INTERVAL
    
    CURRENT_LINE=$(wc -l < "$LOG_FILE")
    
    if [ "$CURRENT_LINE" -gt "$LAST_LINE" ]; then
        # 新しい行を取得
        NEW_LINES=$((CURRENT_LINE - LAST_LINE))
        
        for KEYWORD in "${KEYWORDS[@]}"; do
            MATCHES=$(tail -n "$NEW_LINES" "$LOG_FILE" | grep -i "$KEYWORD")
            if [ -n "$MATCHES" ]; then
                echo "=== [$KEYWORD] を検出 $(date '+%Y-%m-%d %H:%M:%S') ==="
                echo "$MATCHES"
            fi
        done
        
        LAST_LINE=$CURRENT_LINE
    fi
done

ファイルバックアップスクリプト

指定されたディレクトリのバックアップを作成するスクリプトです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#!/bin/bash

# 設定
BACKUP_DIR="/backup"
MAX_BACKUPS=7

show_usage() {
    echo "使用法: $0 [オプション] <バックアップ対象ディレクトリ>"
    echo ""
    echo "オプション:"
    echo "  -d, --dest DIR   バックアップ先ディレクトリ (デフォルト: $BACKUP_DIR)"
    echo "  -n, --num NUM    保持するバックアップ数 (デフォルト: $MAX_BACKUPS)"
    echo "  -h, --help       このヘルプを表示"
}

# オプション解析
while [ $# -gt 0 ]; do
    case "$1" in
        -d|--dest)
            BACKUP_DIR="$2"
            shift 2
            ;;
        -n|--num)
            MAX_BACKUPS="$2"
            shift 2
            ;;
        -h|--help)
            show_usage
            exit 0
            ;;
        -*)
            echo "エラー: 不明なオプション: $1"
            show_usage
            exit 1
            ;;
        *)
            SOURCE_DIR="$1"
            shift
            ;;
    esac
done

# バックアップ対象の確認
if [ -z "$SOURCE_DIR" ]; then
    echo "エラー: バックアップ対象ディレクトリを指定してください"
    show_usage
    exit 1
fi

if [ ! -d "$SOURCE_DIR" ]; then
    echo "エラー: $SOURCE_DIR はディレクトリではありません"
    exit 1
fi

# バックアップ先ディレクトリの作成
if [ ! -d "$BACKUP_DIR" ]; then
    echo "バックアップ先ディレクトリを作成します: $BACKUP_DIR"
    mkdir -p "$BACKUP_DIR"
    if [ $? -ne 0 ]; then
        echo "エラー: ディレクトリを作成できませんでした"
        exit 1
    fi
fi

# バックアップファイル名を生成
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
SOURCE_NAME=$(basename "$SOURCE_DIR")
BACKUP_FILE="${BACKUP_DIR}/${SOURCE_NAME}_${TIMESTAMP}.tar.gz"

# バックアップ実行
echo "バックアップを開始します..."
echo "  対象: $SOURCE_DIR"
echo "  出力: $BACKUP_FILE"

tar -czf "$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"

if [ $? -eq 0 ]; then
    echo "バックアップが完了しました"
    
    # バックアップサイズを表示
    BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
    echo "  サイズ: $BACKUP_SIZE"
else
    echo "エラー: バックアップに失敗しました"
    exit 1
fi

# 古いバックアップの削除
echo ""
echo "古いバックアップを確認しています..."

BACKUP_COUNT=$(ls -1 "${BACKUP_DIR}/${SOURCE_NAME}_"*.tar.gz 2>/dev/null | wc -l)

if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
    DELETE_COUNT=$((BACKUP_COUNT - MAX_BACKUPS))
    echo "  $DELETE_COUNT 個の古いバックアップを削除します"
    
    ls -1t "${BACKUP_DIR}/${SOURCE_NAME}_"*.tar.gz | tail -n "$DELETE_COUNT" | while read -r OLD_BACKUP; do
        echo "  削除: $OLD_BACKUP"
        rm -f "$OLD_BACKUP"
    done
else
    echo "  削除対象はありません (現在: $BACKUP_COUNT / 上限: $MAX_BACKUPS)"
fi

echo ""
echo "処理が完了しました"

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

システムの各種情報を収集してレポートを生成するスクリプトです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/bin/bash

# レポートファイル
REPORT_FILE="/tmp/system_report_$(date '+%Y%m%d_%H%M%S').txt"

# レポート出力関数
print_section() {
    echo ""
    echo "========================================"
    echo " $1"
    echo "========================================"
}

# レポートヘッダー
{
    echo "システム情報レポート"
    echo "生成日時: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "ホスト名: $(hostname)"
    
    print_section "OS情報"
    if [ -f /etc/os-release ]; then
        grep -E "^(NAME|VERSION)=" /etc/os-release
    fi
    echo "カーネル: $(uname -r)"
    
    print_section "CPU情報"
    if [ -f /proc/cpuinfo ]; then
        grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2
        echo "CPUコア数: $(nproc)"
    fi
    
    print_section "メモリ情報"
    free -h | head -2
    
    print_section "ディスク使用状況"
    df -h | grep -E "^/dev/"
    
    # ディスク使用率警告
    echo ""
    echo "--- 使用率チェック ---"
    df -h | grep -E "^/dev/" | while read -r LINE; do
        USAGE=$(echo "$LINE" | awk '{print $5}' | tr -d '%')
        MOUNT=$(echo "$LINE" | awk '{print $6}')
        
        if [ "$USAGE" -ge 90 ]; then
            echo "警告: $MOUNT の使用率が ${USAGE}% です"
        elif [ "$USAGE" -ge 80 ]; then
            echo "注意: $MOUNT の使用率が ${USAGE}% です"
        fi
    done
    
    print_section "ネットワークインターフェース"
    ip -br addr show | grep -v "^lo"
    
    print_section "実行中のサービス数"
    if command -v systemctl > /dev/null 2>&1; then
        echo "アクティブなサービス: $(systemctl list-units --type=service --state=running --no-legend | wc -l)"
    fi
    
    print_section "最近のログインユーザー"
    last -5 | head -5
    
} > "$REPORT_FILE"

echo "レポートを生成しました: $REPORT_FILE"
echo ""
cat "$REPORT_FILE"

メニュー選択式スクリプト

対話的なメニューを提供するスクリプトです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/bin/bash

show_menu() {
    echo ""
    echo "===== システム管理メニュー ====="
    echo "1) システム情報を表示"
    echo "2) ディスク使用状況を表示"
    echo "3) メモリ使用状況を表示"
    echo "4) プロセス一覧を表示"
    echo "5) ネットワーク情報を表示"
    echo "q) 終了"
    echo "================================"
}

show_system_info() {
    echo ""
    echo "--- システム情報 ---"
    echo "ホスト名: $(hostname)"
    echo "OS: $(uname -o)"
    echo "カーネル: $(uname -r)"
    echo "アーキテクチャ: $(uname -m)"
    echo "現在時刻: $(date)"
    echo "稼働時間: $(uptime -p)"
}

show_disk_usage() {
    echo ""
    echo "--- ディスク使用状況 ---"
    df -h | grep -E "^/dev/|^Filesystem"
}

show_memory_usage() {
    echo ""
    echo "--- メモリ使用状況 ---"
    free -h
}

show_processes() {
    echo ""
    echo "--- プロセス一覧(CPU使用率上位10件)---"
    ps aux --sort=-%cpu | head -11
}

show_network_info() {
    echo ""
    echo "--- ネットワーク情報 ---"
    echo "インターフェース:"
    ip -br addr show
    echo ""
    echo "デフォルトゲートウェイ:"
    ip route | grep default
}

# メインループ
while true; do
    show_menu
    read -r -p "選択してください: " CHOICE
    
    case "$CHOICE" in
        1)
            show_system_info
            ;;
        2)
            show_disk_usage
            ;;
        3)
            show_memory_usage
            ;;
        4)
            show_processes
            ;;
        5)
            show_network_info
            ;;
        q|Q)
            echo "終了します"
            exit 0
            ;;
        *)
            echo "無効な選択です。1-5 または q を入力してください"
            ;;
    esac
    
    echo ""
    read -r -p "Enterキーで続行..."
done

制御構文のベストプラクティス

シェルスクリプトの制御構文を使う際のベストプラクティスをまとめます。

変数のクォーティング

変数は常にダブルクォートで囲むことで、空文字列やスペースを含む値でも安全に処理できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

# 良い例
FILE="my file.txt"
if [ -f "$FILE" ]; then
    echo "ファイルが存在します"
fi

# 悪い例(スペースを含むと問題が発生)
# if [ -f $FILE ]; then  # エラーになる

終了ステータスの確認

重要なコマンドの後は終了ステータスを確認しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

# 方法1: $? を使用
mkdir /tmp/test_dir
if [ $? -ne 0 ]; then
    echo "ディレクトリの作成に失敗しました"
    exit 1
fi

# 方法2: && と || を使用
mkdir /tmp/test_dir2 || { echo "作成失敗"; exit 1; }

set オプションの活用

スクリプトの冒頭で安全なオプションを設定しましょう。

1
2
3
4
5
6
#!/bin/bash
set -euo pipefail

# -e: コマンドがエラーで終了したらスクリプトを終了
# -u: 未定義の変数を使用したらエラー
# -o pipefail: パイプラインのエラーを検出

ループの適切な選択

処理内容に応じて適切なループを選択しましょう。

状況 推奨ループ
リストの各要素を処理 for
回数が決まった繰り返し for (C言語スタイル)
ファイルを1行ずつ読む while read
条件が真の間繰り返す while
条件が真になるまで待つ until

まとめ

本記事では、シェルスクリプトの制御構文について体系的に解説しました。

学習したポイントは以下のとおりです。

  • if文: 条件に基づく処理分岐の基本
  • testコマンドと[[ ]]: 条件評価の方法と違い
  • 比較演算子: 数値、文字列、ファイルの判定方法
  • 論理演算子: 複数条件の組み合わせ方
  • forループ: リストや範囲に対する繰り返し処理
  • whileループ: 条件が真の間の繰り返し
  • untilループ: 条件が真になるまでの繰り返し
  • case文: パターンマッチングによる多分岐

これらの制御構文を組み合わせることで、ファイル処理の自動化、システム監視、バックアップスクリプトなど、実務で求められる多様な処理を実現できます。

次のステップとして、関数の定義や配列の操作、デバッグ手法など、より高度なシェルスクリプトテクニックを学ぶことで、保守性と再利用性の高いスクリプトを作成できるようになります。

参考リンク