少し前にバズっていた記事に次があります。
君たちは正規表現におけるインド数字の罠にハマったことはあるかい?(١٢٣٤٥٦٧٨٩) – Qiita
簡単にいうと Python のimport re
で呼べる正規表現のエンジンはデフォルトではインド数字も\d
の範囲に含めてしまうので[0-9]
やpattern = re.compile(r'\d{4}-\d{2}', re.ASCII)
にした方が安全、という話です。Pythonは実際その通りですが結構カジュアルに\d
が使われている PHP、JavaScript 界のあるあるな罠という話はあまり聞きません(よりセキュリティの高いプログラムを作るにはどうするかの話ではもちろん出てきますが)。というわけで各言語の\d
がどうふるまい、全角数字にマッチする\d
的な書き方はどうかを試してみました。
console.log('1'.match(/\d/));// null console.log('1'.match(/\p{Number}/u));// Array [ "1" ]
一つ目は JavaScript です。普通に正規表現を使った時は\d
は半角数字の0から9までにヒットします。これは JavaScript の仕様として次にも定められています。
文字クラス – JavaScript | MDN
ECMAScript® 2023 Language Specification#d
全角数字を数字グループとしてヒットさせるにはユニコードプロパティの数字カテゴリを使います。\d
このユニコードプロパティを簡単に呼び出せる言語が不穏な正規表現を生みやすい言語です。
Unicode property escapes – JavaScript | MDN
この様な挙動のため JavaScript では\d
を使っても問題がそうそう発生しないわけです。
<?php echo preg_match('/\d/', '1');// 0 echo preg_match('/\d/u', '1');// 1
二つ目は PHP です。こちらも普通に正規表現を使った場合は全角数字にヒットせず、半角数字のみです。全角数字にヒットさせるのは JavaScript より簡単です。
PHP: preg_match – Manual
PHP: 正規表現パターンに使用可能な修飾子 – Manual
u
フラグは UTF-8 として文字列を処理するフラグです。これは日本語などのマルチバイト文字を正規表現のパターンに組み込む際によく使います。オプション抜きで使用している際には問題にならないですが、簡単に立てられやすいこのフラグが立っている時は危険です。事故は比較的起きにくいですが普段使いから[0-9]
にしておく方が無難です。
-- 8.0.28-0ubuntu0.20.04.3 create table tests(id integer, title varchar(100)); insert into tests(id, title) values(1, "1"); insert into tests(id, title) values(2, "2"); insert into tests(id, title) values(3, "あ"); select * from tests where title regexp '\\d'; -- id title -- 1 1 -- 2 2 select * from tests where title regexp '[0-9]'; -- id title -- 2 2
三つ目は MySQL です。MySQL の正規表現エンジンがいまいちわからないのですが、少なくとも MySQL 8.0.28 では例の様にオプション等抜きで使った際に全角数字にヒットする状態にできます。8.0.4より前はまた違うやもしれません。とりあえず積極的に[0-9]
を使うべきです。
余談の上、印象論ですが SQL は正規表現を使うと速度が落ちる感じがします。式インデックスや実体付きの生成カラムを組み合わせればそんなこともないのかもしれませんが。そもそも SQL 上で正規表現を使うことを避けた方が無難な感じです。
-- 14.4 create table tests(id integer, title varchar(100)); insert into tests(id, title) values(1, '1'); insert into tests(id, title) values(2, '2'); insert into tests(id, title) values(3, 'あ'); select * from tests where title ~ '\d'; -- id title -- 2 2 select * from tests where title ~ '[0-9]'; -- id title -- 2 2
四つ目は PostgreSQL です。厄介なことに MySQL と PostgreSQL はデフォルトの\d
の挙動が違う状態を作れます。〇○SQL気分で\d
を使うと期待する結果と異なる結果になりやすいです。積極的に[0-9]
を使うべきです。
4つほど紹介しましたがこの他の言語でも挙動は様々です。とりあえず全言語共通で同じ挙動を期待できる[0-9]
がわかりやすく一意な書き方で最も安全です。