ЯoomeR

プログラミング~実装とエラー解決と、時々、AI~

半角英数混合の正規表現[完全解剖]

/\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i

パスワードのバリデーションを記述するため、「半角英数混合 正規表現」などで検索するとヒットするこの記述。

他サイトの説明がやたら難しく書かれているので、実例を交えて簡単に解説する。

(?=.*?[a-z])

ざっくり言うと

「a〜zが出現したときに、その前を確認する」という記述。

実験その1

irb(main):001:0> password = "あいう123あいうabcあいう456あいうefg"
=> "あいう123あいうabcあいう456あいうefg"
irb(main):002:0> password.match(/(?=.*?[a-z])/)
=> #<MatchData "">

"あいう123あいうabcあいう456あいうefg"という文字列で試してみる。

初めて「a〜z」の文字が出現するのは「a」である。

「"あいう123あいうabcあいう456あいうefg"」

「a」の直前とは、「"う"と"a"の間の空文字」である。

よってマッチするのは「""」という空文字になる。

実験その2((?=.*?[a-z])[a-z\d]の場合)

irb(main):001:0> password = "あいう123あいうabcあいう456あいうefg"
=> "あいう123あいうabcあいう456あいうefg"
irb(main):002:0> password.match(/(?=.*?[a-z])[a-z\d]/)
=> #<MatchData "1">

「英数字での入力」という規制を表す[a-z\d]を付与してみる。

今回は以下の2つの条件の組み合わせである。

  • (?=.*?[a-z])・・・a〜zが出現したときに、それより前の記述を確認する
  • [a-z\d]・・・半角英字または半角数字でなければならない

よって、先程と同様「a」が出現したタイミングで、「半角英字または半角数字」の探索が行われる。

「"あいう123あいうabcあいう456あいうefg"」 

「.*?」この記述によって、最も左の文字が確認される。

よって、「1」がマッチする。

(?=.*?[\d])

ざっくり言うと

「半角数字が出現したときに、その前を確認する」という記述。

実験

irb(main):001:0> password = "あいう123あいうabcあいう456あいうefg"
=> "あいう123あいうabcあいう456あいうefg"
irb(main):002:0> password.match(/(?=.*?[\d])/)
=> #<MatchData "">

"あいう123あいうabcあいう456あいうefg"という文字列で試してみる。

今回始めて「a〜z」の文字が出現するのは「1」である。

「"あいう123あいうabcあいう456あいうefg"」

「1」の直前とは、「"う"と"1"の間の空文字」である。

よってマッチするのは「""」という空文字になる。

(?=.*?[a-z])(?=.*?[\d])[a-z\d](組み合わせ)

上記2つの記述を組み合わせる。

ざっくり言うと

「半角英字が出現する」かつ「半角数字が出現する」場合、その前を確認する。

実験

irb(main):001:0> password = "あいう123あいうabcあいう456あいうefg"
=> "あいう123あいうabcあいう456あいうefg"
irb(main):002:0> password.match(/(?=.*[a-z])(?=.*?[\d])[a-z\d]/)
=> #<MatchData "1">

「半角英字」と「半角数字」が出揃うのは以下のタイミングである。

「"あいう123あいうa(←ここで条件が満たされる)bcあいう456あいうefg"」

条件が満たされたので、[a-z\d]のマッチを確認する。

まとめると、「半角英字と半角数字がどちらも出現した場合、条件が満たされたタイミングで[a-z\d]を満たす最左の文字がマッチする」となる。

よって、マッチするのは「1」である。

\Aの必要性

ざっくり言うと

「\A」は「ファイルの先頭を確認する」という役割を持つ。

つまり、1文字目が適切なものかどうかを確認するための記述である。

実験1(ない場合)

irb(main):001:0> password = "あ123abc"
=> "あ123abc"
irb(main):002:0> password.match(/(?=.*?[a-z])(?=.*?[\d])[a-z\d]/)
=> #<MatchData "1">

これまでと同様の理由で「1」がマッチする。

ということは、「あ123abc」をパスワードとして許可してしまう

(ひらがなが入っていても保存されてしまう。)

実験2(ある場合)

irb(main):001:0> password = "あ123abc"
=> "あ123abc"
irb(main):002:0> password.match(/\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]/)
=> nil

1文字目の確認を行った結果、「あ」が不適切であるためnilとなっている。

先頭にひらがなが含まれていた場合に許可せず突き返すことができるようになった。

+\zの必要性

ざっくり言うと

文字列の最後までバリデーションを確認するための記述

\zの役割・+の役割

\z

これは「末尾」を確認するための記述。

+

直前の文字が 1回以上 繰り返す場合にマッチする最長一致。

\zと組み合わせることで、「文字列の最後まで」を確認することができる。

「\A」、「+\z」どちらも使用することで「文字列の最初から最後まで」を確認している。

実験

irb(main):001:0> password = "abc123あ"
=> "abc123あ"
irb(main):002:0> password.match(/\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]/)
=> #<MatchData "a">
irb(main):003:0> password.match(/\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+/)
=> #<MatchData "abc123">
irb(main):004:0> password.match(/\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/)
=> nil

+も\zも無い時

aだけがマッチしている

+がある時

一致する限り最長の範囲を取得するため、abc123がマッチしている

(マッチしない「あ」は無視している)

+\zがある時

文字列の最後まで確認し、「あ」が条件にマッチしないためnilになっている

まとめ

以下の上限の組み合わせになっている

  • 半角英数以外の文字が使用されていないこと
  • 半角数字が使用されている かつ 半角英字が使用されていること
  • ファイルの先頭の文字が条件を満たすこと
  • ファイルの末尾まで条件を満たすこと