はじめに#
シェルスクリプトを本格的に活用するには、条件分岐やループといった制御構文の理解が不可欠です。単純なコマンドの羅列だけでは、状況に応じた処理の切り替えや大量のデータに対する繰り返し処理を行うことができません。
本記事では、シェルスクリプトの制御構文を体系的に解説します。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
|
実行結果は以下のようになります。
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
|
実行結果は以下のようになります。
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
|
実行結果は以下のようになります。
比較演算子#
シェルスクリプトの比較演算子は、数値比較、文字列比較、ファイル判定の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文: パターンマッチングによる多分岐
これらの制御構文を組み合わせることで、ファイル処理の自動化、システム監視、バックアップスクリプトなど、実務で求められる多様な処理を実現できます。
次のステップとして、関数の定義や配列の操作、デバッグ手法など、より高度なシェルスクリプトテクニックを学ぶことで、保守性と再利用性の高いスクリプトを作成できるようになります。
参考リンク#