正規表現って何? | この謎の文字列、便利なの?

この記事の対象読者
  • 正規表現という言葉は聞いたことがあるけど何が便利なのか分からない
  • 正規表現が具体的にどのような場面で使われているのか分からない

正規表現の何が嬉しいのか

最初から結論のようなことを言いますが、正規表現が活躍する場面は文字列の判定置換抽出です。ある文字列に〇〇が含まれているかを検索する、もしくは〇〇を××に置換するといった状況で力を発揮します。見た目の威圧感から嫌煙されがちな正規表現ですが、使いこなす事ができれば、あらゆる場面で効果が期待できる非常に心強いヤツなんです。本記事では正規表現が実際にどのような使われ方をしているのか、具体的な使用例からその有用性について説明していきます。

正規表現が役立つ場面
  • 文字列が定義されたルールに従っているのかを判定する
  • 文字列中の任意の部分を柔軟に置換する
  • 文字列中から任意の部分文字列を抽出する

ルールに従っているのかを判定する

正規表現とは小難しく聞こえるかもしれませんが、文字列の集合を一つの文字列で示したものと言えます。後ほど詳しく触れますので、今は心のどこかに先の言葉を留めておいてください。具体例で考えた方が分かりやすいので、メールアドレスを例にとって考えてみることにします。メールアドレスもあるルールに従った文字列の集合であると考えることができます。実際には文字数が3文字以上30文字以内であること、先頭の文字が数字かアルファベットであることなど様々なルールが存在し、具体例を示すと「example@example.com」、「foo@var.ne.jp」などが挙げられます。例えばあなたがWebサービスを開発しているとして、ユーザー登録画面に入力されたメールアドレスがこのルールに従っているかどうかを確認するためにはどうすれば良いでしょうか。説明の簡略化のため、メールアドレスに課せられているルールが、次のようなものだけだったとして考えてみることにします。

仮定する条件
  • @が含まれている
  • 最初の文字は小文字アルファベット

注:実際のルールではありません。説明の簡略化のために、メールアドレスのルールを上の二つだけという前提で以下の説明に入ります。

ここでもう一度文字列の集合を一つの文字列で示したものという言葉の意味について考えています。上記条件である「@が含まれ、かつ先頭の文字がアルファベットである」という条件を提示したときに、その条件に従っている文字列とそうではない文字列が存在します。上図では「example@example.com」、「sample@123.ne.jp」などは条件に適合しますが、一方で「123@exapmle.com」(先頭の文字がアルファベットではない)や「exapmle.com」(@がない)は条件に適合していません。正規表現とは、この条件に適合している文字列の集合、つまり枠で囲まれた部分の文字列の集合を一つの文字列^[a-z].*@.*で表したものを言います。少し言い方を変えると、正規表現を使った一つの文字列により条件を定義したとも捉えることができます。例示した正規表現がどのような意味を持つのかはブロックに分けて考えると分かりやすいので以下に示します。

  • ^[a-z] : 最初の文字がaからzの間の1文字である
  • .* : ドットが任意の文字、アスタリスクが0文字以上の繰り返しを表します。従ってドットとアスタリスクで任意の文字の0文字以上の繰り返しを示します。
  • @ : "@"という文字そのもの
  • .* : 任意の文字の0文字以上の繰り返し

つまり、この正規表現は最初がaからzで始まり、その後0文字以上の任意の文字が続き、その後"@"があって、再度0文字以上の任意の文字が続くという意味を持ちます。では正規表現を使った時と使わない時で実際プログラム中ではどのように変わるのかを見ていきます。まずは正規表現を使わずに確認しようとすればどのようになるのか、C#を例にそのコードを書いてみます。

bool isValidMailAddress(string mailAddress) {
    if (mailAddress.Contains("@") // @を含むかどうか
    && (mailAddress[0] >= 0x61 && mailAddress[0] <= 0x7a)) { // 先頭がa-zであるか
        return true;
    } else {
        return false;
    }
}

@が含まれていることと先頭の文字をそれぞれ一つずつ確認しています。特に「先頭の文字が小文字のアルファベットであるかどうか」を調べるためには、二つの条件式によって判断しています。もちろん実際のメールアドレスにはもっとたくさんのルールが存在しているため、そのルールの数だけ確認していくことになります。では次に正規表現を使って書き直してみます。

bool isValidMailAddress(string mailAddress) {
    if (Regex.IsMatch(mailAddress, "^[a-z].*@.*")) {
        return true;
    } else {
        return false;
    }
}

C#ではRegex.IsMatchという関数で正規表現に一致しているかどうかを確認する事ができます。第1引数に対象の文字列、第2引数に正規表現をとります。正規表現に一致しれいればtrueを返します。先のコードと比較してみると入力された文字列とたった一つの正規表現とを見比べることにより、条件に適合していることを確認しています。今回の例は条件が二つしかないために、コードの記述量に大差はありませんが、複雑になればなるほどその差は顕著になります。

正規表現はC#を始め、Java、Python、JavaScript、Perl、Ruby、PHPなど多くのプログラミング言語で使うことができます。言語によって使い方に若干の差はありますが、概念としては同じです。

柔軟に置換する

では次に置換です。最近の定番であるVisual Studio Codeをはじめ、私の愛用しているVimやその他テキストエディターには文字を置換する機能が備わっています。単純な置換であれば問題ありませんが、正規表現を使うことにより、より柔軟に文字を置換することができます。この柔軟さこそ、正規表現を使う最大のメリットです。単純な置換、複雑な置換とは具体的にどのような状況なのか以下の通り整理しておきます。

正規表現が不要な置換の例
  • HTML中のすべてのh2タグをにh3タグに置き換える
  • 文章中のすべての「制作」を「製作」に置き換える
正規表現が必要な置換の例
  • 特定の文字が含まれる行全体を別の文字列に置き換える
  • test@bdigitalworks.comやabc@example.comの"@"より前の部分を別の文字列に置き換える

正規表現の便利さを体感していただくために、一つの状況を仮定してみます。以下コードのように、imgタグ中のsrc属性を相対パスで記述していましたが、絶対パスに直したいとします。つまり/media/img.png/picture/image.pngの手前にhttps://www.bdigiralworks.comという文字列を挿入するということです。普通にsrc="src="https://www.bdigiralworks.comに置換するとscriptタグまで一緒に書き変わってしまいます。それでは実際に置換してみますが、本記事ではVisual Studio Code(VSCode)の置換機能と正規表現を使って実演していきます。

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <img src="/media/img.png">
        <img class="test" src="/picture/image.png">
        <script src="/script.js"></script>
    </body>
</html>
</code></pre></div>

Visual Studio Code(VSCode)にはデフォルトで正規表現で置換する機能が備わっています。上部メニューからEdit→Replaceで以下のような小さいWindowが表示されます。この状態ではまだ正規表現は使えません。以下画像の赤枠内のボタンをクリックすると正規表現が有効になります。

2つのテキストボックスにそれぞれ入力し、Replace Allボタンをクリックすると以下のように置換することができます。

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <img src="https://www.bdigiralworks.com/media/img.png">
        <img class="test" src="https://www.bdigiralworks.com/picture/image.png">
        <script src="/script.js"></script>
    </body>
</html>

もう一度上の画像を見て、それぞれテキストボックスに入力した文字列の意味を、パーツに分解してその中身を紐解いていきます。理解を簡単にするために一旦カッコを除いて考えます。

Findテキストボックス:(img.*src=")(.*")

  • img : imgという文字そのもの
  • .* : 任意の文字の0文字以上の繰り返し
  • src : srcという文字そのもの
  • " : ダブルクォーテーションそのもの
  • .* : 任意の文字の0文字以上の繰り返し
  • " : ダブルクォーテーションそのもの

Replaceテキストボックス:$1https://www.bdigiralworks.com$2

  • $1 : 1つ目にキャプチャされた文字列(*1)
  • https://www.bdigiralworks.com : 文字そのもの
  • $2 : 2つ目にキャプチャされた文字列(*1)

キャプチャとは「捕まえた」などと訳されますが、その名の通り検索に合致し、補足した文字列のことを言います。ここでFindテキストボックスに入力したカッコを思い出してください。ドルマークと数字の組み合わせにはカッコで捕まえた文字列が入っています。いわば変数のようなものです。具体的には$1には1つ目のカッコに書かれているimg.*src="でキャプチャした文字列であるimg src="img class="test" src="が入ります。また$2には.*"でキャプチャした/media/img.png"/picture/image.png"が入ります。表にすると以下のようになります。

$1 $2
正規表現 (img.*src=") (.*")
コード6行目 img src=" /media/img.png"
コード7行目 img class="test" src=" /picture/image.png"

さらにキャプチャされた文字列が実際に置換される時にどのように扱われるのかを6行目を例に図にしてみると以下のようになります。

文字列中から任意の部分文字列を抽出する

3つ目に正規表現がよく使われる場面は、クローリング(Webサイトから情報を収集する事)によって得たサイトからスクレイピング(収集したWebサイトの情報から任意の部分を抽出すること)する際です。Webサイトは作り手によって様々な構造を取り得るため、任意の情報を抽出することは大変困難です。しかし、正規表現を使うことで柔軟に任意の部分文字列を抽出することができます。早速具体例を見てみましょう。為替レートを掲載しているWebサイトのhtmlを取得し、その中から実際のレートを取得したいという状況を想定しています。

<table>
    <tr>
        <td class="pair">米ドル/円</td>
        <td class="rate">109.88</td>
    </tr>
    <tr>
        <td class="pair">ユーロ/円</td>
        <td class="rate">129.55</td>
    </tr>
    <tr>
        <td class="pair">ポンド/円</td>
        <td class="rate">151.94</td>
    </tr>
</table>
void Extract(string html) {
    Matches matches = Regex.Matches(html, @"class="rate">(.*)<");
    foreach (Match match in matches) {
        Console.WriteLine(match.Groups[1]);
    }
}
109.88
129.55
151.94

上のコードを解説します。まずは2行目に書かれている一番大事な正規表現の部分ですが、class="rate">(.*)<のように記述しています。.*の部分は任意の文字を0文字以上繰り返すという意味です。それ以外の部分は文字そのものを意味します。さらに.*の部分をカッコで囲むことで、抽出したい文字列をキャプチャしています。

Regex.Matches()という関数ですが、一つ目の引数には探索対象の文字列を、二つ目の引数には正規表現をとります。そして戻り値はMatchesと呼ばれるオブジェクトで、これはMatchオブジェクトのコレクションです。Matchオブジェクトは正規表現に合致した場所の様々な情報が入っています。その情報には合致した文字列全体はもちろんキャプチャした文字列も参照する事ができます。今回のサンプルコードは4行目でmatch.Group[1]のようにキャプチャした文字列を参照しています。ちなみにGroup[0]には合致した文字列全体であるclass="rate">109.88<が入っています。今回はカッコをひと組しか使用していませんのでGroup[2]には何も入っていません。

このように正規表現を使うことで簡単なコードで複雑な抽出もできるようになります。よく使う正規表現での抽出を以下の表に正規表現をまとめておきます。以下表で抽出した文字列はすべてmatch.Group[1]でアクセス可能です。

正規表現
リンクタグ(<a>)のリンクを抽出 <.*?a.*?href="(.*?)"
class名がsampleであるタグを抽出 class¥s*=¥s*"sample".*?>(.*?)<
idがsampleであるタグを抽出 id¥s*=¥s*"sample".*?>(.*?)<
nameがsampleであるタグを抽出 name¥s*=¥s*"sample".*?>(.*?)<

まとめ

本記事では正規表現の有用性について3つの具体的な使用例を紹介しました。簡単な概念でないことは事実ですが、使いこなすことにより柔軟で効率的な作業が可能になります。B Digital Worksでは正規表現によってファイル名を置換する事ができるフリーソフトStockFilerを提供しています。是非このソフトをお使いいただき、みなさんの作業効率化に貢献できれば幸いです。正規表現の文法一覧は以下のサイトが大変参考になります。

参考になるサイト

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です