古来の正規表現 = 標準正規表現、basic regular expression、BRE
モダンな正規表現 = 拡張正規表現、extended regular expression、ERE
独自にさらに拡張された拡張正規表現 = 長いので、超拡張正規表現とします (ちなみにもう正規言語の域を超えた)
の3種類が存在するのだけれど、みんなあまり気にしていない気がする。
BRE と ERE の違い
便宜上、左を拡張正規表現であるEREにしており、違う部分だけを抜粋(したつもり
ERE | BRE | 用途 | 代替表現 |
---|---|---|---|
| | unsupported | OR 表現 | I don’t know |
+ | unsupported | 1文字以上の繰り返し | \{1,\} |
? | unsupported | 0 or 1文字 | \{,1\} |
() | \(\) | グループ化 | – |
{n,m} | \{n,m\} | n文字以上、m文字以下の繰り返し | – |
であれば、詳しくは以下のコマンドを叩いて、読んでみると面白いです。(Macでしか確認していません)
man re_format
超拡張表現との比較が非常に多くなり、色々と面倒なので割愛するけれど、例として POSIX クラスを挙げておく。
e.g. 超拡張表現では POSIXクラスを [:space:]
として表せるが、BRE、ERE共に [[:space:]]
としなければならない。
それぞれの違いを例示してみる
他に詳しく違いを並べる時間もないので、例を出して「色々知っておかないとどはまりするかもよ」ということを以下で、備忘録という意図も含めて書いておきたい。
超拡張正規表現は大体のプログラミング言語で扱われていて便利な分、尚更違いを知っておくべきである。
2つの例を用意する。
1つ、Mac で private ipを引くために、sed
を使って ifconfig
からぶっこ抜くことを考える。
2つ、axyzaopqalmn
という文字列から aから始まり、aで終わる文字列
にマッチさせることを考える。
Mac で private ip を引く時の表現の違い
まずは1つ目。
一応 ifconfig
の usage を超簡潔に示す。
ifconfig [interface name] inet
これで対応する interface の inet が出て来る。以下が出力例。
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 inet xxx.xx.x.xxx netmask 0xfffffxxx broadcast xxx.xx.x.255
ここからぶっこ抜くために、全体マッチさせてipの部分で置換することを考える。 (ifconfig en0 inet|tail -1
を流し込むとする。)
たくさん思いつきますね?
# BSD sed sed 's/[[:blank:]][[:blank:]]*inet \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g' # BSD sed with modern regexp sed -E 's/[[:blank:]]+inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/g' # BSD sed sed 's/[[:blank:]][[:blank:]]*inet \([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\).*/\1/g' # BSD sed with modern regexp sed -E 's/[[:blank:]]+inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/g' # BSD sed sed 's/[[:blank:]]\{1,\}inet \(\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}\).*/\1/g' # BSD sed with modern regexp sed -E 's/[[:blank:]]+inet (([0-9]{1,3}\.){3}[0-9]{1,3}).*/\1/g'
( :digit: は長すぎるので使っていません )
BRE vs ERE で、グループ記法 ()
、繰り返し記法 {n,m}
、1以上繰り返し +
の有無辺りに違いが見て取れる。
超拡張正規表現を使ってみる with ジャバ
String parttern = "\s+inet ((\d{1,3}\.){3}\d{1,3}).*"; // replace the matched string with 1st group
メタ文字が使えてスッキリ。
axyzaopqalmn
という文字列から aから始まり、aで終わる文字列
にマッチさせる時の表現の違い
(面倒くさいのでEREを使います)
特に条件を指定していないので、2つ出来るはず。(最長マッチ、最短マッチ)
echo 'axyzaopqalmn' | sed -E 's/a.+a/ここだよ/' # 最長 echo 'axyzaopqalmn' | sed -E 's/a[^a]+a/ここだよ/' # 最短
超拡張正規表現ならば最長マッチは変わらないのだけれど、最短マッチは
a.+?a
で表せる。
結論
超
拡張正規表現しか知らないと sed とかで組むときにその人の発想が柔軟かどうかみたいな話になってしまうので 拡張正規表現の記法がどんなもののエイリアス(正確な表現ではないのだけれど)になっているか
を知っておくべきだと思う。
勿論表現限界のせいで実現できないものも存在するので、知っておいた方がいい。シェル芸人になりたいなら尚更である。
例えば否定後読みなどは JavaScript (Chromium上) ですら1年前に実装されたばかりである。
https://codereview.chromium.org/1418963009
Tips
- シェルスクリプト内ではBREの方が安全な場合もあるので、BREのみで表現可能であればBREでいいのかもしれない。 e.g.) 変数展開の文字をエスケープし忘れるなどが減る
- 大体のコマンドは
-E
オプションでEREを使えるので、EREで表現可能であれば移植性も考えてEREでよい。 - どうしてもEREでもだめだというなら perl や ruby にコマンドレベルで委譲するべき。問題ないのであればスクリプト全体をその言語にする必要はない
- ある言語で定義した正規表現が別の言語だとそのまま使えない場合、バリデーションの統一に支障が出ることを念頭に置く。e.g.) サーバーサイドで定義したバリデーション用正規表現をクライアントで用いるケース
ちなみに正規表現エンジンの勉強がしたいなら以下の本がとてもオススメ。読み物としてもなかなかおもしろいです。