Bash 入門

Bash のループ構造

Bashのループ構造(繰り返し処理)は、反復的なタスクの自動化、効率的なデータ処理、そしてよりダイナミックで強力なスクリプトの作成を可能にします。

本章では、Bashで利用可能な3つの主要なループ構造(forwhileuntil)について深く掘り下げます。それらの構文、機能、および実際のアプリケーションを探索し、Bashスクリプト内でこれらを効率的に活用するためのスキルを習得しましょう。

1. for ループ

for ループは、一連のアイテム(シーケンス)をイテレート(巡回)するために設計されています。このシーケンスは、文字列のリスト、数値の範囲、またはコマンドの出力結果など多岐にわたります。Bashは、異なるシナリオに適した数種類の for ループ形式を提供しています。

1.1 基本的な for ループとリスト

最も一般的な for ループの形式は、アイテムのリストをイテレートするものです。

for item in item1 item2 item3 ... itemN
do
  # 各アイテムに対して実行されるコード
  echo "アイテムを処理中: $item"
done

構文解析:

  • for item in item1 item2 ... itemN: この行でループを開始します。提供されたリスト(item1、item2など)内の各アイテムを順番に走査します。各イテレーションにおいて、バリアブル(変数)item にはリスト内の現在の要素の値が代入されます。
  • do: ループ本体(実行するコードブロック)の開始をマークします。
  • echo "アイテムを処理中: $item": リスト内の各アイテムに対して実行されるコードです。ここでは単にメッセージを表示していますが、任意の有効なBashコマンドを含めることができます。$item は変数展開であり、現在の item の値に置き換えられます。
  • done: ループ本体の終了をマークします。

例:

#!/bin/bash
for fruit in apple banana cherry
do
  echo "私は $fruit が好きです"
done

出力:

私は apple が好きです
私は banana が好きです
私は cherry が好きです

1.2 ブレース展開(Brace Expansion)を組み合わせた for ループ

ブレース展開は、数値や文字のシーケンスを生成する便利な方法を提供します。

for i in {1..5}
do
  echo "数字: $i"
done

構文解析:

  • {1..5}: これがブレース展開です。1から5まで(1と5を含む)の数値シーケンスを生成します。Bashはループが開始される前にこれを展開するため、このループは実質的に for i in 1 2 3 4 5 と同等です。

出力:

数字: 1
数字: 2
数字: 3
数字: 4
数字: 5

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

for i in {1..10..2}
do
  echo "数字: $i"
done

構文解析:

  • {1..10..2}: 1から10までの数値シーケンスを、ステップ2(2ずつ増加)で生成します。したがって、生成されるシーケンスは 1, 3, 5, 7, 9 となります。

出力:

数字: 1
数字: 3
数字: 5
数字: 7
数字: 9

1.3 コマンド置換を組み合わせた for ループ

コマンド置換を利用すると、あるコマンドの実行結果を for ループのイテレートリストとして使用できます。

for file in $(ls *.txt)
do
  echo "ファイルを処理中: $file"
done

構文解析:

  • *$(ls .txt): これがコマンド置換です。Bashは ls *.txt コマンドを実行し、その出力をこの部分に置き換えます。この例では、カレントディレクトリ内の拡張子が .txt のすべてのファイルをリストアップし、for ループがそれらのファイル名を巡回します。

例:
カレントディレクトリに以下のファイルがあると仮定します:file1.txt, file2.txt, image.png, data.txt

出力:

ファイルを処理中: file1.txt
ファイルを処理中: file2.txt
ファイルを処理中: data.txt

重要: コマンド置換も機能しますが、後述する「グロブ(Globbing)」を使用する方が、スペースや特殊文字を含むファイル名をより安全に扱えるため、一般的により良い選択とされます。

1.4 ワイルドカード(Globbing)を組み合わせた for ループ

グロブは、パターンマッチングに基づいてファイル名を特定する強力な方法です。

for file in *.txt
do
  echo "ファイルを処理中: $file"
done

構文解析:

  • *.txt: これはワイルドカードパターンです。* は任意の文字シーケンスにマッチします。したがって、.txt で終わるすべてのファイルにマッチします。Bashはループ開始前にこのパターンをファイルリストに展開します。この方法は、ls を使用したコマンド置換よりも安全で信頼性が高く、特にスペースを含むファイル名を扱う際に有効です。

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

BashはC言語スタイルの for ループもサポートしており、数値イテレーションを行う際に非常に有用です。

for (( i=1; i<=5; i++ ))
do
  echo "数字: $i"
done

構文解析:

  • (( i=1; i<=5; i++ )): ループの制御構造を定義します。
    • i=1: ループカウンタ i を 1 に初期化します。
    • i<=5: ループの継続条件です。i が 5 以下の間、ループは実行されます。
    • i++: 各イテレーションの後に、ループカウンタ i の値を 1 増加させます。

出力:

数字: 1
数字: 2
数字: 3
数字: 4
数字: 5

1.6 練習問題:for ループ

  1. 数値リストの出力: for ループとブレース展開を使用して、10から20までの数字を出力してください。
  2. ディレクトリ内のファイル処理: ディレクトリ内のすべての .jpg ファイルを巡回し、その名前とサイズ(ls -l コマンドを使用)を出力するスクリプトを作成してください。スペースを含むファイル名を正しく処理するように注意してください。
  3. 合計値の計算: C言語スタイルの for ループを使用して、1から100までの数値の合計を計算してください。

2. while ループ

while ループは、与えられた条件が真(true)である限り、コードブロックを実行し続けます。

while [ condition ]
do
  # 条件が真の間、実行されるコード
  # 重要:無限ループを避けるため、必ず条件を偽にするためのコードを含めてください
done

構文解析:

  • while [ condition ]: while ループを開始します。角括弧内の condition(条件)が真である限り、ループ内のコードが実行されます。[test コマンドと同等であるため、角括弧の両側には必ずスペースを入れてください。
  • do: ループ本体の開始。
  • done: ループ本体の終了。

2.1 示例:カウントダウンタイマー

#!/bin/bash
count=5

while [ $count -gt 0 ]
do
  echo "カウントダウン: $count"
  count=$((count - 1)) # count の値をデクリメント
  sleep 1             # 1秒待機
done

echo "リフトオフ(発射)!"

構文解析:

  • count=5: 変数 count を 5 に初期化。
  • while [ $count -gt 0 ]: -gt は "greater than"(より大きい)演算子です。count が 0 より大きい間、ループは継続します。
  • count=$((count - 1)): 算術式展開を使用して count を 1 減らします。
  • sleep 1: スクリプトを1秒間停止させ、視覚的なカウントダウン効果を作ります。

出力:

カウントダウン: 5
カウントダウン: 4
カウントダウン: 3
カウントダウン: 2
カウントダウン: 1
リフトオフ(発射)!

2.2 示例:ファイルを1行ずつ読み込む

#!/bin/bash
file="mydata.txt"

# ファイルが存在しない場合は作成して内容を書き込む
if [ ! -f "$file" ]; then
  echo "1行目" > "$file"
  echo "2行目" >> "$file"
  echo "3行目" >> "$file"
fi

while IFS= read -r line
do
  echo "読み込み: $line"
done < "$file"

構文解析:

  • while IFS= read -r line:
    • IFS=: 内部フィールド区切り文字(IFS)を空文字列に設定します。これにより、行頭や行末の空白文字が削除されるのを防ぎます。
    • read -r line: 入力から1行読み取り、変数 line に格納します。-r オプションはバックスラッシュによるエスケープを無効化します。
  • done < "$file": ファイルの内容を while ループの標準入力リダイレクトします。

重要:while IFS= read -r line 構造は、Bashでファイルを1行ずつ読み込む際の推奨される方法です。

3. until ループ

until ループは while ループの逆です。与えられた条件が偽(false)である間、コードブロックを実行し続け、条件が真(true)になった時点で終了します。

until [ condition ]
do
  # 条件が偽の間、実行されるコード
  # 重要:無限ループを避けるため、必ず条件を真にするためのコードを含めてください
done

3.1 示例:ファイルの作成を待機する

#!/bin/bash
file="myfile.txt"

until [ -f "$file" ]
do
  echo "$file の作成を待機中..."
  sleep 5 # 5秒待機
done

echo "$file が作成されました。処理を続行します。"

構文解析:

  • until [ -f "$file" ]: -f はファイルの存在をチェックするテスト演算子です。myfile.txt が存在しない間、ループは継続されます。

3.2 示例:カウントアップ

#!/bin/bash
target=10
current=1

until [ $current -gt $target ]
do
  echo "現在の値: $current"
  current=$((current + 1))
done

echo "目標値に到達しました!"

出力:

現在の値: 1
...
現在の値: 10
目標値に到達しました!

4. 実戦ケーススタディ:ループ構造をシステム管理に活用する

システム管理タスクにおいて、ループは非常に強力です。複数のディレクトリのディスク使用量を監視し、しきい値(スレッショルド)を超えた場合にアラートを出すスクリプトを作成してみましょう。

#!/bin/bash

# 監視対象のディレクトリとアラートしきい値を定義
directories="/var /tmp /home"
threshold=90  # パーセント

# 各ディレクトリを走査
for dir in $directories
do
  # ディスク使用率のパーセンテージを取得
  usage=$(df -h "$dir" | awk 'NR==2 {print $5}' | tr -d '%')
  
  # 使用率がしきい値を超えているかチェック
  if [ "$usage" -gt "$threshold" ]; then
    echo "警告: $dir のディスク使用率は $usage% です。しきい値 $threshold% を超えています。"
  fi
done

構文解析:

  • usage=$(df -h "$dir" | awk 'NR==2 {print $5}' | tr -d '%'):
    • df -h "$dir": ディスク使用量を人間が読みやすい形式で取得。
    • awk 'NR==2 {print $5}': 出力の2行目(データ行)の5列目(使用率%)を抽出。
    • tr -d '%': パーセント記号を削除して数値のみにします。
  • if [ "$usage" -gt "$threshold" ]: 取得した使用率としきい値を比較します。

この例のように、for ループを使用することで、1つのスクリプトで複数のリソースを効率的に管理・監視することが可能になります。