パターン・正規表現の使い分け|[[ ]] / extglob / grep -E

データとパラメータ展開
スポンサーリンク

この記事の狙い

Bash スクリプトで条件分岐や文字列判定を行うとき、**「glob パターン」と「正規表現」**が混在しやすく、思わぬバグにつながります。
ここでは [[ … ]] におけるパターン一致と正規表現一致の違いを整理し、extglob を含む glob 拡張と、外部コマンドの grep -E をどう使い分けるかを解説します。

前提と対象

  • Bash 4+
  • set -Eeuo pipefail 前提
  • 文字列判定の書き方を混乱なく整理したい人向け

TL;DR(最小比較表)

機能使いどころ記法特徴
glob パターンファイル名や単純な文字列一致[[ $s == a* ]]* ? [...] が使える。シンプルで速い
extglobより柔軟なパターン`[[ $s == @(jpgpng) ]]`
正規表現複雑な文字列マッチ[[ $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 文字列判定には 外部プロセスコストが重いので [[ =~ ]] の方がよい

使い分けの実務指針

  1. 単純一致(接尾辞/接頭辞/拡張子) → glob [[ $file == *.csv ]]
  2. ちょっと複雑なファイルパターン → extglob [[ $file == +(*.jpg|*.png|*.gif) ]]
  3. 文字種や繰り返しなどの検証[[ =~ ]] [[ $var =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
  4. ログや標準入力のフィルタリングgrep -E grep -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 filewhile 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 で安全に処理

参考リンク

スポンサーリンク
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする