正規表現でハマりやすい場所のメモ
気が付かないとバグりやすいです。
注意点を知らないと確認用のテストもなかったりするです。
筆者はJavaScript、JScript、サクラエディタを特に使っています。
正規表現は方言が多いので、この記述とは違う実装もあります。
1.シングルラインモードとマルチラインモード
/pattern/m
マルチラインモード
/pattern/s
シングルラインモード
デフォルトはマルチラインモードです。
1.任意マッチの.*
に\n
が含まれるかどうかが違います。
マルチライン:\n
が含まれません
シングルライン:\n
が含まれます
これを理解していないと「複数行文字列の行内マッチ」の感覚で
/|.+《.+》/s
こんなふうに書いてしまう事故にあったりします。
/|[^\r\n]+《[^\r\n]+》/s
シングルラインモードのまま行を超えないようにするには改行を排除する必要があります。
とくに普段「サクラエディタ」使っている人は行を超えたマッチが起きないので、ハマりやすいです。
※ここではUnicode改行は無視しています。
2.^
と$
の動作が違う
マルチライン :行の先頭、末尾
シングルライン:データ全体の先頭、末尾
全然マッチするものが違うので、明らかなバグの元です。
2.[^なにがし]
では改行に要注意
上ですでに書いていますが、マルチラインモードの時、.
には最初から\n
が排除されています。
ところがその感覚で[^なにがし]
では改行を排除するとき自分で書く必要があります。
これを知らないと、
/《[^《》]+》/
このように書いてしまい、《》の間のマッチで改行を超えてしまいます。
いろはにほへと《ちりぬるを[改行]
わかよたれそ》つねならむ[改行]
うゐのおくやま《けふこえて》[改行]
あさきゆめみし ゑひもせす[改行]
こういうデータで行内のみならず、改行を食べてしまいます。
(もちろん意図的に改行を超えてマッチさせたいときは別です)
3.パイプ演算子「|」は左優先
/(山田|旭|旭丘|旭丘駅|野木)/
こういう|
を使った複数マッチをしたいとき多くの実装で「左優先」です。
バックトラックが発生せず、一番左から順番に試していって、最初にマッチしたものを採用するようになっています。
この例では「旭」にマッチしても「旭丘」「旭丘駅」にはマッチしません。
/(山田|旭丘駅|旭丘|旭|野木)/
このように、左側に長い文字列を置くことで解決することができます。
/(ギルド|デスク|トイレ|バスルーム|バス停|ホテル|仕事場)に/
/(中学|会社|大学|学校|屋敷|教室|玄関|王宮|病院|職場|部室|部屋|階段|高校)/に
/([塾家屋席店門館駅])/に
長さごとに分離して実装する方法もあります。
また1文字の場合には[なんちゃら]
のブラケット表記が使えます。中身が大量にある場合、もしかしたら最適化の関係で[]
のほうが高速に実行される可能性があります。
一文字ずつ|
入れるのも面倒ですしね。
4.バックトラック
/.*■.*■.*/
なんかこういう*
や+
とかが複数ある正規表現は場合によって、速度に注意が必要です。
バックトラックといって、一部分だけマッチしたあと、それが最終的に全体がマッチしなかったときに、再度長さを変えてマッチさせようとします。
これ、指数関数的に時間を使う表記が存在していまして、めちゃくちゃパフォーマンスが悪い組み合わせがあります。
5.CRLFのデータのハンドリング
多くの正規表現エンジンの改行コードは\n
のことを指します。
しかし入力データには\r\n
、\r
やUnicode改行などがあります。
str = str.replace(/\r\n/g, '\n').replace(/\r/g, /\n/);
こういう前処理を必要ならしましょう。
6.最長一致の原則
入力1:あいうえお《かきくけこ《さしすせそ》たちつてと》なにぬねの
こういうデータがあるとして、/《.+》/
これが何にマッチするかというと、
出力1:《かきくけこ《さしすせそ》たちつてと》
こうですね。ところが
入力2:あいうえお《かきくけこ》さしすせそ《たちつてと》なにぬねの
こういうときでも、
出力2:《かきくけこ》さしすせそ《たちつてと》
こうなってしまいます。これが最長一致の原則です。
一番マッチ文字列を短くしたいなら、
/《.+?》/
のように*?
、+?
、{1,5}?
のように「長さ指定子」の後ろに?
を付け足す必要があります。
出力1-2:《かきくけこ《さしすせそ》
出力2-2:《かきくけこ》
とまあ、こうなります。
ちなみに、二重になっている括弧を数えてどこが終わりか判断するのは、ちょっと簡単ではありません。
「入力1」で一番内側の「《さしすせそ》」を抜き出すにはこうします。
/《[^《》\r\n]+》/
「入力1」で最初に見つけた《から最初の》まで「《かきくけこ《さしすせそ》」を抜き出すにはこうします。
/《[^》\r\n]+》/
7.Unicode拡張モード
全種類の文字チェックとかしない数種類だけのテストだと、場合によっては気が付かないことがあります。
phpなどで日本語コードを使う場合はUnicodeモードで使用しないと場合によっては、コード範囲がおかしなります。
簡易カタカナチェック
/[ア-ヴー]+/u
JavaScriptでもサロゲートペアの範囲までフォローする場合には、同様に拡張モードを使用します。
ES2018で書式が拡張されました。
新常用漢字の異体字
/[曾瘦麵艷龜餠彌篭堽崕葢𥡴咒溯璢填剥頬叱]/u
新常用漢字
/[曽痩麺艶亀餅弥籠岡崖蓋稽呪遡瑠塡剝頰𠮟]/u
こんな感じですね。
8.忘れやすいgオプション
JavaScriptで「すべて置換」する場合には「gオプションフラグ」が必要です。
テストコードを書くときには、置換対象が2個以上あるテストケースも書きましょう。
let bad = str.replace(/</, '<').replace(/>/, 'gt;');
let good = str.replace(/</g, '<').replace(/>/g, 'gt;');
はい、上でやらかしましたので、備忘録として追記しておきます。
例はあまりよくないですね。他にも&
自身を先に&
にエスケープしておく必要があります。
TODO
・「通常使うカタカナ」一覧の正規表現の研究。
・「通常使うひらがな」一覧の正規表現の研究。
・「最新の漢字」の一覧の正規表現の研究。
(終わり)