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
コマンドは、拡張正規表現ではなく基本正規表現に則っているようです。
ただ(...)
と書くだけでは、そのままの意味として扱われます。
$ 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
なんと。。。整理すると、
\(...\)
が正規表現内にある。 -> 正規表現のうち、\(...\)
内にマッチした文字列 or nullが返る。\(...\)
が正規表現内にない。 -> 正規表現にマッチした文字列長 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
可読性が悪いですね。。。笑