Displaying posts tagged with

“正規表現”

「末尾がxyzで終わってはいけない」正規表現

とあるソフトウェア製品での出来事。

あるバグフィックス・アップデートを適用して以来、それまでは問題なかった入力値でValidation Errorが出るようになった。丸一日調べ回った結果、以前は

^[a-z0-9_\-][a-z0-9_\.\-]*$

だった入力チェックの正規表現が、

^[a-z0-9_\-][a-z0-9_\.\-]*[^x][^y][^z]$

に変わったため、と判明した(xyzは架空)。どうやら「末尾がxyzで終わってはいけない」という条件を追加したかったようだが、この正規表現は完全に誤りだといえる。これだと最低でも4文字以上ないとマッチしないし、’boxes’のような正当なはずの文字列にもマッチしないし、逆に’a+:^’のような不正であるべき文字列にマッチしてしまう。

では「末尾がxyzで終わってはいけない」を表す正しい正規表現とはどんなものだろうか。戻り読みが使える環境なら、否定戻り読みを使うのが簡単だと思う。

irb> my_answer = /^[-a-z0-9_][-a-z0-9_.]*(?<!xyz)$/
=> /^[-a-z0-9_][-a-z0-9_.]*(?<!xyz)$/
irb> my_answer =~ "a_xyz"
=> nil
irb> my_answer =~ "xyz"
=> nil
irb> my_answer =~ "a"
=> 0
irb> my_answer =~ "abcdef"
=> 0
irb> my_answer =~ "abc_xyz_def"
=> 0

戻り読みが使えないとなると……ちょっと思いつかない。条件式を2つに分けるしかないだろうか。

ひっくり返して「xyzで始まってはいけない」にすると、否定先読みを使うことになる。例えば xyz で始まらない単語文字列なら

^(?!xyz)\w+$

さらに厳しくして「xyzではない」にするとどうだろう。…これは先読み/戻り読みだけでは無理かな?長さの条件も加えて

^\w{4,}$|^(?!xyz)\w+$

といったところか。まあいずれにせよ、実際に使う機会は稀だろうが。

ちなみに件の製品は戻り読みが使えない glibc の regex を使っていたので、諦めてアップデート前の形に戻した。