スタートアップCTOによるITエンジニアのためのブログ

grepコマンドでの拡張正規表現(ERE)の使い方

grepはファイルやディレクトリから特定の文字列を検索する際に欠かせないコマンドですが、正規表現の使い方を曖昧なまま使っている方も多いのではないでしょうか。

本記事では、grepで利用できる正規表現、特に「拡張正規表現(ERE)」の便利な使い方について詳しく解説します。

grepの語源

正規表現は英語で Regular Expression と言います。grep コマンドは、global/regular expression/print の略で、直訳すると「(ファイル全体から)正規表現に一致する行を検索して表示する」という意味です。その名の通り、正規表現は grep の核となる機能です。

正規表現をマスターすると、ログ解析やソースコードの調査効率が劇的に向上します。

注) 本記事では、Linux環境で最も一般的に利用されている GNU grep をベースに解説します。

目次

  1. grepの基本的な使い方と3種類の正規表現
  2. 基本正規表現(BRE)と拡張正規表現(ERE)の違い
  3. エイリアスとしての egrep
  4. 拡張正規表現で使用可能なメタ文字
  5. 引用符(クォート)の重要性

grepの基本的な使い方と3種類の正規表現

grepコマンドは以下の書式で実行します。

# Bash
grep [オプション] 正規表現パターン ファイル名

指定した「正規表現パターン」にマッチする行を抽出し、標準出力に表示します。grepでは、主に以下の3種類の正規表現を切り替えて使用できます。

  • 基本正規表現(Basic Regular Expressions: BRE)
  • 拡張正規表現(Extended Regular Expressions: ERE)
  • Perl互換正規表現(Perl-Compatible Regular Expressions: PCRE)

使用する正規表現は、以下のオプションで指定します。

オプション正規表現の種類備考
-G基本正規表現 (BRE)デフォルト設定
-E拡張正規表現 (ERE)本記事のメイン
-PPerl互換正規表現 (PCRE)より高度な機能(後読みなど)が利用可能

本記事では、扱いやすく強力な 拡張正規表現(-E オプション) を中心に解説します。

基本正規表現(BRE)と拡張正規表現(ERE)の違い

GNU grep においては、BREとEREで表現できる機能自体に大きな差はありません。しかし、「メタ文字をそのまま書けるか、バックスラッシュでエスケープが必要か」 という記述方法に大きな違いがあります。

機能基本正規表現 (BRE)拡張正規表現 (ERE)
OR (選択)||
グループ化\( ... \)( ... )
1回以上の繰り返し\++
0か1回の出現\??
範囲指定 (n,m回)\{n,m\}{n,m}

BREではメタ文字(特別な意味を持つ記号)を使う際にバックスラッシュを多用するため、可読性が低下します。現代のエンジニアリングでは、特別な理由がない限り ERE (-E) を使うのが一般的です。

エイリアスとしてのegrep

かつては拡張正規表現を使うための独立した egrep コマンドが存在していましたが、現在は 非推奨(deprecated) となっており、内部的には grep -E のエイリアス(別名)として動作しています。

# Ubuntu等での egrep の実体を確認
$ cat /usr/bin/egrep
#!/bin/sh
exec grep -E "$@"

スクリプト等では将来的な互換性のために grep -E と書くべきですが、ターミナルで手動入力する際は、手軽な egrep も依然として重宝されています。

拡張正規表現で使用可能なメタ文字

よく使われるメタ文字(特殊記号)を解説します。

サンプルファイル (fruits.txt)

apple
orange
grape
melon

任意の1文字:ドット (.)

任意の単一文字にマッチします。

# 「任意の1文字 + e」を含む行を検索
grep -E '.e' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange
grape
melon

文字クラス:角括弧 ([...])

括弧内の「いずれかの1文字」にマッチします。

# m または l を含む行を検索
grep -E '[ml]' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
melon

否定文字クラス ([^...])

先頭に ^ を付けると、その文字以外の文字にマッチします。

# m でも l でもない文字を「含む」行を検索
grep -E '[^ml]' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange
grape
melon

melon も ‘e’ や ‘o’ などがマッチ対象になるため、行として表示されます。

POSIX文字クラス

特定の文字セットを名前で指定できます。[[:名前:]] のように二重の角括弧で囲む必要があります。

クラス名意味
[:alnum:]英数字
[:alpha:]英字
[:blank:]スペースとタブ
[:digit:]数字 (0-9)
[:lower:]英小文字
[:space:]空白文字(改行等含む)
grep -E '[[:alpha:]]' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange
grape
melon

否定文字クラスを使う場合は、外側の角括弧の先頭に ^ をつけます。

# 英単語以外の文字が含まれる行を探す
grep -E '[^[:alpha:]]' fruits.txt

行頭と行末(アンカー)

  • ^:行の先頭にマッチ
  • $:行の末尾にマッチ

キャレット(^)とドルサイン($)は行の先頭と最後を意味するメタ文字です。

# aで始まる行
grep -E '^a' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple

# eで終わる行
grep -E 'e$' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange
grape

量化子(繰り返し)

直前の文字が何回繰り返されるかを指定します。

  • ?:0回または1回
  • *:0回以上(制限なし)
  • +:1回以上
  • {n,m}:n回以上、m回以下

# applesなど、単数系だとe、複数系だとesで終わる行
grep -E 'es?$' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange
grape

grep -E 'ap*l' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
grep -E 'ap+l' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
# # pが1回以上、2回以下出現する箇所
grep -E 'ap{1,2}' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
grape

OR検索

パイプ記号 | で複数のパターンを繋ぎます。

grep -E 'apple|orange' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

apple
orange

後方参照

丸括弧 () でグループ化した部分を、後から順番に \1, \2 で参照できます。

# 同じ単語がスペースを挟んで繰り返される箇所を検索
echo 'hoge hoge' | grep -E '(hoge) \1'

上記コマンドの出力(マッチした箇所は赤字)

hoge hoge

補足:AND検索

grepにAND検索用のメタ文字は存在しませんが、いくつかの方法で「AかつB」の状態を作り出すことができます。

1. パイプ(|)でつなぐ。

最も一般的で確実な方法です。

# appとg両方の文字列を持つ行を検索
grep -E 'ap' fruits.txt | grep -E 'g'

上記コマンドの出力(マッチした箇所は赤字)

grape

この方法のメリットは、ANDしたい検索語が3つ、4つと増えてもパイプを足すだけでOKなことです。

デメリットとしては、-B-Aオプションでマッチした行の前後のコンテクストを表示したい場合は、パイプ後のgrepでその情報が失われてしまうために不向きなことです。また、検索語の出現順番は指定できません。

2. メタ文字のドットとスターを使う

1つの grep コマンドで完結させたい場合に便利ですが、検索語の順番が重要になります。

# gが現れた後に、何らかの文字が0文字以上続き、その後に apが現れる行
grep -E 'g.*ap' fruits.txt

上記コマンドの出力(マッチした箇所は赤字)

grape

引用符(クォート)の重要性

grepで正規表現を使う際は、必ずクォート('") で囲む習慣をつけましょう。

その理由は、「その文字列をシェル(bashやzshなど)に解釈させず、そのまま grep に渡すため」です。

主な理由は以下の2点に集約されます。

1. スペース(空白)を1つの塊として扱う

シェルはスペースを「コマンドの区切り」として認識します。

クォートなし

grep -E hello world file

シェルは「hello という文字を、worldfile という2つのファイルから探せ」と解釈します。

クォートあり

grep 'hello world' file

hello world という一続きのフレーズを、file から探せ」と正しく伝わります。

2. 展開や特殊文字の制御

シェルには展開(Expansion)という機能があり、いくつかの特殊文字はシェルにとって特別な意味を持ちます。例えば*(ワイルドカード)は「現在のディレクトリにあるファイル名に展開せよ」という命令です。

クォートなし

grep -E a* file

もしカレントディレクトリに apple というファイルがあったら、シェルが*を展開しgrep apple file と書き換えてから実行してしまいます。

クォートあり

grep -E 'a*' file

シェルは中身を無視し、そのまま a* という文字列を grep に渡します。これで本来の「aが0回以上続く」という正規表現として機能します。

なお、クォートの種類によっても、シェルの挙動が変わります。

シングルクォート(' ')を使うと、シェルは中の文字を一切解釈せず、文字リテラルとしてそのままgrepに渡します。

# $USERという文字列そのものをfileから検索
grep -E '$USER' file

一方、ダブルクォート (" ")を使うと、シェルは$(変数)や `(コマンド実行)などの一部の特殊文字のみ展開し、それ以外は文字リテラルとして扱います。

# 現在のログインユーザー名を検索。
grep -E "$USER" file

そのため、変数を意図的に使いたい時のみダブルクォートを使い、それ以外の時はシングルクォートを使うことが推奨されます。

まとめ

grepでの正規表現では、以下の点がポイントです。

  • -E オプション(または egrep)をデフォルトで使う。
  • 量化子 (+, ?, {}) を活用して柔軟な回数指定を行う。
  • シェルによる誤動作を防ぐため、パターンはシングルクォート ' ' で囲む。

拡張正規表現を使いこなせるようになると、grepを一回実行するだけで望み通りの検索結果を得られるようになります。全てのメタ文字を一度に覚える必要はないので、まずは .* などの基本から日常のオペレーションに取り入れてみてください。

なお、grepに限らず様々なプログラミング言語やツールで正規表現を使いこなせるようになりたい方には、オーライリーの「詳説 正規表現 第3版」がおすすめです。私も持っていますが、原文の題名はMastering Regular Expressionsで、まさに正規表現を極めるための本です。