基本正規表現 (BRE) と拡張正規表現 (ERE)
POSIX正規表現には、grepコマンドやsedコマンドで使用できる基本正規表現(BRE)と、egrepコマンドやawkコマンドで使用できる拡張正規表現(ERE)があります。一般的にはどういうわけかより多くの特殊文字を持つ正規表現が好まれているようで、BREよりもERE、EREよりもPerlの正規表現の方が人気があるように思います。しかし、BREとEREの違いをきっちりと理解されている人はそれほど多くないのではないかと思います。
BREとEREの違い
下記はBREとEREで使用できる正規表現の一覧です。
名前 | BRE | ERE |
---|---|---|
Collation-related bracket symbols | [==] [::] [..] | [==] [::] [..] |
Escaped characters | \ |
\ |
Bracket expression | [] | [] |
Grouping | \(\) \n | () \n |
Single-character duplication | * \{m,n\} | * + ? {m,n} |
Concatenation | ||
Anchoring | ^ $ | ^ $ |
Alternation | (not supported) |
見ていただければわかるように、EREで使えてBREで使えないのは、「+」、「?」、「|」の3つです。「+」は「{1,}」、「?」は「{0,1}」に置き換えることができるので、実際にEREで使えてBREで使えないのは、「|」のみです。
しかも、GNU grepやGNU sedでは、BREであっても「\|」を用いることができるので、ほとんど差はありません。
次に通常の文字として認識させたい場合にエスケープが必要となる特殊文字を示します。
BRE | . | [ | \ | * | ^ | $ | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
ERE | . | [ | \ | ( | ) | * | + | ? | { | | | ^ | $ |
BREよりもEREの方がかなり少ないですね。これがBREがEREよりも表現力に乏しいと思われている理由ではないでしょうか。
BREとEREの使い分け
ここまで読んでいただいて、BREもEREも機能面で大差はないことを理解していただけたかと思います。それでは、BREとEREをどのように使い分けるのがよいでしょうか。
コマンドラインで使用する場合
コマンドラインで使用する場合は、どちらかと言うとEREをおすすめします。以下にその理由を説明します。
例として、「abcまたはdef」をBREとEREで書くと、それぞれ下記のようになります。
BRE: abc\|def ERE: abc|def
続いての例として、abcの3個から5個をBREとEREで書くと、それぞれ下記のようになります。
BRE: abc\{3,5\} ERE: abc{3,5}
どちらの例においても、BREよりもEREの方がよりも短くなっています。そして、読みやすいと思います。それは、間違えにくいことでもあります。なので、コマンドラインで使用する場合は、どちらかと言うとBREよりもEREをおすすめします。
スクリプト内で使用する場合
スクリプト内で使用する場合は、BREをおすすめします。パターン内にシェル変数を使う場合、一般的には特殊文字をエスケープする必要がありますが、BREの場合はエスケープが必要なのは「.[\*^$」の6文字ですが、EREの場合は「.[\()*+?{|^$」の12文字もあります。エスケープ用のシェル関数を定義すればよいだけのことですが、文字数が少ない方がシンプルかつ高速です。
下記の例は、指定されたファイル「$FILE」 のパスが「$DIR/」 で始まる場合は、「$DIR/」 を取り除いて「$FILE」に書き出しています。sed_pattern_string()を通さなかったら、DIR 特殊文字や「/」 が入ってきた場合にうまく動作しません。
sed_pattern_string() { echo "${1-}" | sed -e " \ "'s/\\/\\\\/g; s/\./\\\./g; s/\*/\\\*/g; s/\^/\\\^/g;'" \ "'s/\$/\\\$/g; s/\[/\\\[/g; s/\]/\\\]/g; s/\//\\\//g' } DIR=$1 shift DIR_SED=`sed_pattern_string "$DIR/"` for FILE do test -f "$SRCFILE" || continue echo "$FILE" | sed -e "s/^$DIR_SED//" >>"$FILE" done