この記事の狙い
Bash スクリプトで条件分岐や文字列判定を行うとき、**「glob パターン」と「正規表現」**が混在しやすく、思わぬバグにつながります。
ここでは [[ … ]] におけるパターン一致と正規表現一致の違いを整理し、extglob を含む glob 拡張と、外部コマンドの grep -E をどう使い分けるかを解説します。
前提と対象
- Bash 4+
set -Eeuo pipefail前提- 文字列判定の書き方を混乱なく整理したい人向け
TL;DR(最小比較表)
| 機能 | 使いどころ | 記法 | 特徴 |
|---|---|---|---|
| glob パターン | ファイル名や単純な文字列一致 | [[ $s == a* ]] | * ? [...] が使える。シンプルで速い |
| extglob | より柔軟なパターン | `[[ $s == @(jpg | png) ]]` |
| 正規表現 | 複雑な文字列マッチ | [[ $s =~ ^[0-9]{3}$ ]] | ERE(拡張正規表現)、グループや量指定子が使える |
| grep -E | 外部入力や複数行処理 | grep -E 'regex' file | 標準入力やファイルを対象に、正規表現で行単位検索 |
基本 1: [[ … == pattern ]] のパターン一致
s="report_2025.log"
[[ $s == *.log ]] && echo "ログファイル"
[[ $s == report_* ]] && echo "レポート系"
*→ 任意の文字列?→ 任意の1文字[...]→ 文字クラス(例:[0-9],[abc])- 正規表現ではないので
+{n}()などは使えない
基本 2: extglob の有効化
shopt -s extglob
f="image.png"
[[ $f == +(*.jpg|*.png) ]] && echo "画像ファイル"
?(pat)→ 0回または1回*(pat)→ 0回以上+(pat)→ 1回以上@(pat1|pat2)→ どちらか!(pat)→ pat 以外
ファイル名処理で
case文と組み合わせると強力。
基本 3: [[ … =~ regex ]] の正規表現
id="abc123"
if [[ $id =~ ^[a-z]+[0-9]+$ ]]; then
echo "英字+数字のID"
fi
- **拡張正規表現(ERE)**が使える
- マッチ部分は
${BASH_REMATCH[0]}、グループは${BASH_REMATCH[1]}で取得 - 左辺は必ずクォート不要(文字列展開により正規表現が壊れるため)
txt="error: 42 not found"
if [[ $txt =~ ([0-9]+) ]]; then
echo "番号=${BASH_REMATCH[1]}"
fi
基本 4: grep -E の使い所
# ログからエラー行を抽出
grep -E 'ERROR|WARN' app.log
# 数字だけの行
grep -E '^[0-9]+$' values.txt
- 複数行の入力やファイル対象に便利
- スクリプト内での 1 文字列判定には 外部プロセスコストが重いので
[[ =~ ]]の方がよい
使い分けの実務指針
- 単純一致(接尾辞/接頭辞/拡張子) → glob
[[ $file == *.csv ]] - ちょっと複雑なファイルパターン → extglob
[[ $file == +(*.jpg|*.png|*.gif) ]] - 文字種や繰り返しなどの検証 →
[[ =~ ]][[ $var =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] - ログや標準入力のフィルタリング →
grep -Egrep -E '^ERROR' logfile | cut -d' ' -f2-
“コピペ可”テストブロック
#!/usr/bin/env bash
set -Eeuo pipefail
s1="data.csv"
s2="user42"
s3="note.txt"
# glob
[[ $s1 == *.csv ]] || { echo "glob failed"; exit 1; }
# extglob
shopt -s extglob
[[ $s3 == +(*.txt|*.md) ]] || { echo "extglob failed"; exit 1; }
# regex
[[ $s2 =~ ^user[0-9]+$ ]] || { echo "regex failed"; exit 1; }
# grep (行処理)
out="$(printf '123\nabc\n' | grep -E '^[0-9]+$')"
[[ "$out" == "123" ]] || { echo "grep failed"; exit 1; }
echo "PASS"
アンチパターン → 改善
| 悪い例 | 問題 | 良い例 |
|---|---|---|
if [ "$s" == *.log ]; then | [ ] では glob 展開せず文字列比較になる | [[ $s == *.log ]] |
[[ "$s" =~ "$regex" ]] | クォートすると文字列リテラル扱いになる | [[ $s =~ $regex ]] |
| `grep pattern file | while read …` | NUL や空白に弱い |
なんでも grep | 外部プロセス多発で遅い | 単純な一致は Bash 内蔵演算子で |
運用メモ(実務)
- 拡張子・簡単なフィルタは glob/
extglobに統一 → 速い - 入力検証やフォーマットチェックは
[[ =~ ]]に統一 → 外部コマンド不要 - 大規模テキスト処理は
grep -Eに譲る → 行単位処理に強い case文は複数分岐に便利で、glob/extglobを自然に使える
互換性と移植性
- glob と
caseは POSIX 互換、移植性が高い extglobは Bash/ksh/zsh のみ(sh/dash では不可)[[ =~ ]]は Bash 拡張(dash では不可)grep -Eは POSIX(ただし BSD 系はegrepに相当)
セキュリティと安全設計
- ユーザー入力を正規表現に直結しない → 不正な正規表現でエラーや DoS を引き起こす
- glob 展開時はクォート必須(
"$file"vs$file)で空白・ワイルドカード暴発を防ぐ - 大量ログ解析時は
grep -Eを使い、while read -rで安全に処理
