そろそろ常識?マンガでわかる正規表現
実質的な正規表現の勉強メモ
正規表現が読めるようになるらしいぞ
正規表現とは
正規表現
あやふやな文字列からルールを見つけ出してパターンで表現したもの。
Regular Expression、規則性に基づく表現という意味
使い道
パターンに当てはまる文字列のまとめての検索
vscodeはもちろんテキストエディタなら大体使えるぞ
フォームへの入力チェック
テキストの編集(置換など)
試してみよう
郵便番号の検索をしてみる
\d{3}-\d{4} の結果、郵便番号の書式になっているものだけが抽出できた すごい
https://scrapbox.io/files/6242edecc5e46c00230c5b84.png
第2章
正規表現のきほん
検索したい文字列を見て、どこが決まっていて、どこが変化するのかを考える
アリス
アイス
アマリリス
決まっているところ : アとス
変化するところ:リ、イ、マリリの部分
→「ア{正規表現}ス」として記述するというようなイメージ
上の候補の中から、「アリス」を検索してみる
.(ピリオド)
特定の1文字とマッチする
ア.スとすることで、アリスとアイスがマッチする
ア...スならアマリリスがマッチする
マンガでわかるシリーズを検索したいなら
どこが決まっていて、どこが変化するのか = マンガでわかる , 〇〇
なので〇〇の部分だけ正規表現を使うので、「マンガでわかる..」とすれば良い
[]
どれか1文字にマッチさせる
[白初]雪
→大雪、小雪、白雪、吹雪、初雪、豪雪なら、白雪と初雪がマッチする
[^]で否定にできる
[^白初]雪ならそれ以外がマッチ
特定の文字とだけはマッチして欲しくないときに使う。
[-]で特定の範囲指定
[1-5], [A-E]みたいな感じ。数字、アルファベット、平仮名カタカナで使える
[0-9]とかで書いた場合
要するに全ての数字であることを示している
これは [\d]と表現することもできる
特殊な1文字にマッチするもの
\d
数字1文字とマッチ。digit(0-9までの数値を意味する単語)のd
\d話とかけば、[0-9]話と同じ意味
\w
英数字1文字とマッチ。
[a-zA-Z0-9]と同じ意味
https://scrapbox.io/files/6242f2cbc5405c001dabe2cf.png
平仮名カタカナをマッチさせたいなら、[ぁ-ん], [ァ-ヴ]で指定する。
ぁとァは小文字
こんな記述になる理由は、文字コードの並びで範囲を決めているから
https://scrapbox.io/files/6242f36ae9c244001d9bb0ec.png
メタ文字をヒットさせる
.*+?\|^$[](){}みたいな文字のこと。特殊な記号
コンピュータにとっての正規表現の目印
これを検索するときは、\.とか、\+として記述することでマッチさせることができる
いわゆる、メタ文字のエスケープ
エスケープせずに書くと、以下のようにtest1Ajpgがマッチしてしまう
https://scrapbox.io/files/6242f499024840001da34137.png
これは正規表現としての.の役割「何か1文字にマッチ」に従って、Aがマッチしてしまっている
test\d\.jpgと変更することで.そのものをマッチ対象とさせることができる
\.と、\wの違い
ピリオドそのもの = \.
wという文字そのもの = \w というわけではない
\メタ文字か、\メタ文字じゃないかで意味が変わるようになる。
\s 空白
\d 数字
\t タブ
\w 英数字
\n 改行
\メタ文字 メタ文字そのものを意味する
\ は必ず別の文字列とセットで使われる。特殊なものだと解釈しよう
ちなみにバックスラッシュ2つを検索したいときは、\\\\とするとできる
https://scrapbox.io/files/6242f64c591eed001f95b5b2.png
|
どれかの単語にマッチさせる(グループ化)
a|b|c|dというようにかける
https://scrapbox.io/files/6242f6f12ffbb90021f735b6.png
()でグループでまとめよう
例えば、〇〇ティーで検索をかけたい
ミルク|レモンティーとかけたら、ミルクかレモンティーかにヒットしてしまう
(ミルク|レモン)ティーとすればミルクティー、レモンティーがヒットする
第3章
位置、数を指定してマッチさせる
先頭と末尾のメタ文字
^ 先頭を示す
[^a-c]とかで使ったときは否定の意味を持っていたけど、ここでは「先頭」を意味しているよ
$ 末尾を示す
先頭にあるもの、末尾にあるものをマッチさせるときに使う
赤巻紙、青巻紙、黄巻紙 で^.巻紙で検索をかけると、最初の赤巻紙だけがマッチ
じゃあ末尾は$.巻紙かと思いきや、そうではなく.巻紙$と、後ろに$をつける
繰り返し文字とのマッチ
* 0回以上の繰り返し
例えば、やったー*!は、ーを繰り返す文字列とマッチする
やったー! やったーーーーーーーーーーー! やったーーー!とか
0回以上なので、やった!にもマッチするよ
.*という正規表現にすると?
「特定の文字列がn回以上繰り返されている」という意味なので、殆どにマッチする
でも全部にマッチしたら意味ないので、工夫して使う必要がある
形式が決まっているものであれば、これを使えば大体ヒットさせられる
https://scrapbox.io/files/6242fcdb525651001d2a1bbc.png
+ 1回以上繰り返す文字とマッチする
.+とかくと、上の例だとやった!がマッチしなくなる
特定の単語をループさせる
グループ化させることで、単語を+や*で回すこともできる
https://scrapbox.io/files/62446299f7ccc1001d6753f2.png
上で \ でエスケープさせてるけど、?も正規表現
? : あってもなくてもマッチする
boy, girl, boys, girlsという中から、girls?とすればgirlとgirlsがヒットする
girl?ではないので気をつけよう
n回繰り返す文字とマッチさせる
{n}とする
https://scrapbox.io/files/624464056756c0001d67a546.png
すも{8}のうちとすれば、すもももももももものうちがヒットするぞ
n回以上繰り返すものとマッチ
{n,}とする
すも{8,}のうちとすれば、もが多くてもヒットする
これはパスワードとかによく使う \w{8,}とすれば8文字以上の英数字という条件にできる
https://scrapbox.io/files/62446510f7ccc1001d676595.png
n~m回繰り返したい場合は、{n,m}と記述すれば良い
最初の桁が、1-3桁までのものとマッチ
https://scrapbox.io/files/624465fac0bf5d002049b287.png
第4章
?* 最短マッチ
例えば以下の例は、「フ ... キ」までの部分がマッチしてしまっている
https://scrapbox.io/files/6244669cf7ccc1001d6773bb.png
.*?とすると、「」の部分が3つずつマッチするようになる。
. : 何か適当な1文字
* : 0回以上繰り返されている
? : 最も短い範囲で というのがそれぞれの意味。短い範囲で繰り返されている部分で切り取ってくれる
HTMLタグとかで検索かけるときにめっちゃ使える <.*?>でタグだけ抽出とか。
https://scrapbox.io/files/62446827907cf7001d153f90.png
第5章
キャプチャ
マッチした箇所を、もう一度マッチさせて調べること
正規表現は文書を左から右にチェックしていく
文書の左でマッチした条件を合致するものが右側でもう一度使われているかどうかを確認できる
https://scrapbox.io/files/624469bd3900ce001d69ebe1.png
わたしでマッチしたなら、\1 = わたしになるというイメージ
(.*)のものは、(.*)のものだ。とすると、全部ヒットしてしまう
\1は変数みたいなイメージ。マッチしたものが入っている変数が増えていくイメージ
https://scrapbox.io/files/62446a5200d2d10022af43ae.png
いろんなパラメータのあるURLを抜き出す記述を書く記事を作ってみてもいいかもしれない...
第6話
先読み、後読み
(?=)先読み
後ろに特定の条件がある時だけ、マッチさせる
「後ろ」というのは、文章で言うと右の方から読むということ
100円、令和3年、西暦2021年、第005回というものがあったとする
数字だけ抽出したい。\d+(1回以上の数字繰返し)で検索をかけると全部にヒットする。
年だけ抜き出したくて\d+年とすると、3年と2021年がヒットする。数字だけ欲しいのに...
先読みを使って、\d+(?=年)とすれば、後ろに念がついている時だけ
\d数字
+ 左の条件を1回以上繰り返す
(?=年) 後ろが年のとき
(?<=) 後読み
手前に特定の条件がついている時だけ、マッチさせる
(?<=令和)\d+とすれば、上だと令和3年の3とだけマッチ
(?<=令和) : 前が令和の時
(?!) 否定的先読み
test.jpg test.png test.gif test.txtがあったなら
test(?!\.txt)とすることで、.txt以外をマッチさせることができる
(?<!) 否定的後読み
東京都港区 京都府京都市 東京都品川区 京都府宇治市で、東京都以外をマッチさせたい
東が前についていないものを絞り込めばいいので、(?<!東)京都.*
第4章
正規表現でマッチした文字を置換する
置換するというシステムが正規表現側でついている
正規表現の使い道は、大きく3つ。
まとめて検索できる→説明済
入力のチェック→説明済
テキスト編集
一般的なテキストエディタには置換機能がついている。これは正規表現で作られている
windowsウインドウズウィンドウズっていう文字列を、全部Windowsに変えたい時
全てに合致するような正規表現を作って、それを全て置換してあげればいい
そんなに難しく考えなくて良さそう
https://scrapbox.io/files/62459b19b57354001f2763cf.png
マッチした文字の前後に文字を追加する
正規表現で表示させたい部分を()でグループ化する
置換対象で$1と記述することで、置換対象の文字を表示させることができる
対象がキャプチャされていて、それを呼び出しているイメージ。
正規表現ではキャプチャの呼び出しは\1だったけど、置換でのキャプチャは$1を使う
https://scrapbox.io/files/62459c230e5d1a001d60c996.png
カギかっこで包んだけど、HTMLなら<b>$1</b>で包むと全部boldにできる。便利そう!
第5章
正規表現の具体的な使い方
である調をですます調に置き換えてみる
明日は晴れだ。暑くなりそうである。ところ、今日のおやつはアイスだ。
だ。|である。で出来る
ダブルクォート"をカギカッコに変える
明日は"晴れ"だ。 を分解してみる
""
晴れ
"(.*?)"としてあげると、最初の一括りだけ指定してくれる
.*だと複数同じ文字があった時対応できない(下の画像)
(.*)という変数に、晴れを入れているというようなイメージで良さそう
https://scrapbox.io/files/62459ed4b52b5a001d2c1bb7.png
カンマ区切りの数字にマッチさせる
まずは、マッチする数字がどんな条件で組まれているの考えよう
3桁ならカンマはつかない
4桁以上なら下から3桁ごとにカンマが繰り返し付属する
123,456,789,000,000,000円があったとする これは、
123
1桁目は10円とかだと2桁になったり、1桁になったりする。なので{1,3}
,\d{3}
カンマの後ろに桁がある時は、絶対数じは3つ
↑が0回以上繰り返されるはずなので、()で括って*とする
まとめると、\d{1,3}(,\d{3})*
https://scrapbox.io/files/6245a219b57354001f279fa4.png
123456789にヒットしているように見えるのは、123と456と789にヒットしているから。
¥と-で見分けをつけさせよう
さっきの正規表現をこれで囲んで、グループにする
¥(\d{1,3}(,\d{3})*)-でOK
¥マークから始まって、-で終わる部分までを()で括ってチェック
上の桁は1~3桁の数字 \d{1,3}
後ろの桁がある場合は、3桁なのは確定なので,\d{3}
*で()で括る。1なら0回だし、 1,000,000なら2回繰り返しだから。
マッチした数字に「円」をつけてみる
¥(\d{1,3}(,\d{3})*)-とすると、
¥()-
カッコの中身は\d{1,3}(,\d{3})* ->$1となる
さらに中の(,\d{3})は$2
時刻の正規表現
簡単に考えると 00:00:00 12:30:59 23:59:59がマッチするのは\d{2}:\d{2}:\d{2}
でもこれだと39:99:72のようにありえない時間もマッチすることになるので、工夫する
分と秒 : 00-59 は、[0-5]\dで表現できる
[0-5]が10の位で、\dが1の位(0~9)
[0-5]\d で一つの単語ではないので気をつける
時間 : 00~23まであるので、分けて考えてみる
00-19 : [0-1]\d
20-23 : 2[0-3]
この二つをグループ化して、([0-1]\d|2[0-3])とする
合わせて、([0-1]\d|2[0-3]):[0-5]\d:[0-5]\dで完成。
ちなみに自分で考えた[0-2]\d:[0-5]\d:[0-5]\dは、27時とかがマッチしちゃうぞ
複雑な場合を考えてみる
AM / PM付きの時刻、0時から12時までの表記の場合もあるのでそこを考慮する
あらゆる環境に対応できる正規表現の書き方が一番正しい
その文書にはどう言った書き方で形式が統一されているのかを考えて正規表現を作る
上を考慮するときは、([0-2]\d:[0-5]\d:[0-5]\d|)と書いて、|の横にもう一つ条件をつければ良さそうだ
日付の正規表現
\d{4}([\/\-\.])\d{2}\1\d{2}と書いた
\d{4} : \d4桁分なのでこう表記できる
[\/\-\.] : 区切り文字 / , - , .を考慮。うち2つはメタ文字なのでエスケープする。
上で基本OKだが、日付が1996-99-99とかでも通っちゃうのでそこも考慮してみる
年 : このままでOK
月 : 01-12まで。分けて考える
01-09 : 0[1-9]
10-12 : 1[0-2]
合わせて、(0[1-9]|1[0-2])
日 : 01-31まであるので、分けて考える
01-09 : 0[1-9]
10-29 : [1-2][0-9] or [12]\d
30-31 : 3[0-1] or 3[01]
合わせて、(0[1-9]|[12]\d|3[01])
\d{4}([\/\-\.])(0[1-9]|1[0-2])\1(0[1-9]|[12]\d|3[01])が正しい正規表現になる
https://scrapbox.io/files/624ac8a250d62a001d6b52fd.png
さらにうるう年や30日しかない日付のことを考慮していくと、正規表現はさらに長くなっていく
フォームの入力チェックに使ってみる
アルファベットか数字4~8桁のパスワード
[a-zA-Z0-9]{4,8}
\wは_も含まれてしまうので、こういうときは手書きで指定する
文字列の最初から最後までが4~8桁なのかも判断したい。
先頭と末尾を表す文字を使おう
^ 先頭を示す
$ 末尾を示す
先頭にあるもの、末尾にあるものをマッチさせるときに使う
指定の文字列に、「前が先頭」「後ろが末尾」という情報をつけてやる
赤巻紙、青巻紙、黄巻紙 の時は、^.巻紙で検索をかけると最初の赤巻紙だけマッチする
末尾は.巻紙$でマッチさせることができる
ので、^[a-zA-Z0-9]{4,8}$とする
https://scrapbox.io/files/624acb5169b321001fdcbef4.pnghttps://scrapbox.io/files/624acb9a22b15f001d49790a.png
郵便番号にマッチ
^\d{3}-\d{4}$でオッケー
メールアドレスにマッチ
skonishi1125@gmail.com
sample-mail@test-01.example.comなどのアドレスを小分けにしてみよう
1文字以上の英数字か-か.
@
1文字以上の英数字か-か. (ドメイン。gmailとかtest-01.exampleとか)
.
1文字以上の英語(comとか)
条件別に細かく分けてみよう
[\w\-\.]
@
[\w\-\.]
.
[a-zA-Z]とか?
→1文字以上とするのを忘れてた。+がいる
^[\w\-\.]+@[\w\-\.]+\.[a-zA-Z]+$で完成
\1は使えないのか試してみてもいいかも。無理そう
https://scrapbox.io/files/624acf24cebd5c001def16be.png
HTMLの編集に使ってみる
<>と</>に囲まれていること
中は1文字以上の英数字
締めタグは同じ単語
https://scrapbox.io/files/624c2268c2581a00226d0cb7.png
空っぽのタグを見つけたい時は、<(\w+)></\1>でOKそう。
URLにマッチさせる
URLで使える文字は、以下の通り
アルファベット
数字
_-.~!#$%&'()*+,/:;=?@[]
これらを組み合わせて作ってやる
順に作っていく
http://かhttps://で始まるので,あってもなくてもマッチする?を使う。https?://
特定の文字と、アルファベット数字が1文字以上[\w\-\.~!#\$%&'\(\)\*\+,/;=?@\[\]]+
https://scrapbox.io/files/624c245b7037af001d853778.png
自分の場合は、先頭^と後ろ$も作ってあげた
URLのホスト/ドメイン名とマッチさせる
ホスト/ドメイン名はアルファベット数字_-.が使える
手前はhttp://かhttps://
後ろはある場合もない場合もある
plusmash.blog/test plusmash.blogとか。
手前と後ろを調べるので、(?=)先読みと(?<=) 後読みを使おう
先読みを使って、\d+(?=年)とすれば、後ろに年がついている時だけマッチする
後読みは、手前に特定の条件がついている時だけ
(?<=令和)\d+とすれば、令和3年の3とだけマッチすることを思い出そう
code:php
(?<=https?://)
(?=/.*) これは後ろが"/何かの文字"だった場合 .* = 特定の文字列をn回繰り返す
合わせたら完成
https://scrapbox.io/files/624c26f316b807002069be90.png
どういう状況で使うのかをはっきりさせてから正規表現を組み立てるのが大事らしい
jsで使うなら、こんな感じで使える
pattern = /正規表現/gの形にする
https://scrapbox.io/files/624c29d11ce5810021d21c07.png
置換の方法なども書いてるので、使う時に調べてみよう
データの入った文字列から、該当するデータだけ抽出ということもできる
array = "123-4567, 12-345, 1234-567, 999-8888"とか。
PHPで使うならこんな感じ
$pattern=/正規表現/と、/で括って定義する。
https://scrapbox.io/files/624c2ba80a8302001d1cceaa.png
この本の付録に正規表現一覧などのチートシートがあるので、機会があれば使ってみよう