tr_ikym_blog

某Webサービスに従事する者のブログです。

exprコマンドは正規表現の(...)は使えない?-> 拡張正規表現ではないみたい。

仕事でexprコマンドと正規表現を使おうとして、やや詰まったので備忘録。

文字列長を返す場合

exprでよくやる使い方として、正規表現にマッチする文字列数を返す方法があると思います。

例えば、

$ expr "123" : '^[1-3].*$'
3

正規表現^[1-3].*$に文字列123がヒットするので、123の文字列数3が返ります。

文字列長が返らないパターン?

同じように、正規表現にマッチする文字列の長さが返ることを期待して、

$ expr "A123" : '^(A|B)[1-3].*$'

と書いてみました。 (A|B)という部分は、AかBでマッチすることを期待しています。

実行してみると、

$ expr "A123" : '^(A|B)[1-3].*$'
0

あれ?どこにもマッチしていない?

ちょっと悩んで、試しにエスケープを入れてみましょう。

$ expr "A123" : '^¥(A¥|B¥)[1-3].*$'
A

今度は数字ではなく、Aという文字列が返ってきました。

一体どうなっているのでしょうか?

exprでは(...)がそのまま使えない。

どうやらexprコマンドは、拡張正規表現ではなく基本正規表現に則っているようです。

正規表現 - Wikipedia

ただ(...)と書くだけでは、そのままの意味として扱われます。

$ expr "(A|B)123" : '^(A|B)[1-3].*$'
8

文字列(A|B)123の文字列長は8で、正規表現^(A|B)[1-3].*$にマッチしています。 (A|B)には「AまたはB」という意味はないことになっています。

ただ今回は、(...)をそのままの文字として使いたいのではありません。

基本正規表現ではエスケープするのが正しいようです。 (|にもエスケープしてます。)

# Aが返る
$ expr "A123" : '^\(A\|B\)[1-3].*$'
A

# Bが返る
$ expr "B123" : '^\(A\|B\)[1-3].*$'
B

# nullが返る?
$ expr "C123" : '^\(A\|B\)[1-3].*$'

確かに、「AまたはB」となっているようです。返ってくる文字が1文字ですが(笑)。

普段から拡張正規表現に慣れていると、そのまま(...)を使ってしまいますね。

では次に、どうして正規表現にマッチする文字列長ではなく、特定の文字列が返るのでしょうか?

\(...\)内にマッチした文字列が返る。

これに関しては、

man expr

と打てば答えが見つかりました。以下、引用です。

Pattern matches return the string matched between \( and \) or null; if \( and \) are not used, they return the number of characters matched or 0

なんと。。。整理すると、

確かに先の例はこれに従っていますね。

# 正規表現にマッチした文字列長が返る。
$ expr "A123" : '^A[1-3].*$'  
4

# 正規表現にマッチしているので、\(...\)の中身にマッチするものが返る
$ expr "A123" : '^\(A\)[1-3].*$' 
A

ちなみに、正規表現でのA or Bを実現しつつもマッチした文字列全体を返す方法はないのでしょうか?

\(...\)で囲えば返す文字列を操作できる。

出来ました。

入力文字だけ変えて、正規表現の部分は同じです。

\(A\|B\)[1-3].*\(...\)で囲んでいます。

$ expr "A123" : '^\(\(A\|B\)[1-3].*\)$'
A123

$ expr "B123" : '^\(\(A\|B\)[1-3].*\)$'
B123

可読性が悪いですね。。。笑