初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」

  • 375
    Like
  • 2
    Comment
More than 1 year has passed since last update.

はじめに

みなさんこんにちは!
この記事は「手と目で覚える正規表現入門」の第2回です。

この連載記事は「知識ゼロからでも理解できる」「実践的なサンプルを提供する」「自分の手と目で動きを確認できる」をモットーにした、正規表現の入門記事です。

今回は正規表現を使って、テキストの微妙な違いを許容しながら検索する方法と、そこからさらに文字列置換を実行する方法を説明します。

対象となる読者

本記事は正規表現の予備知識が全くない「正規表現初心者」を対象としています。
ただし連載記事なので、読者のみなさんは過去の記事で紹介した知識をすべて理解できている、という前提で進めます。

まだ第1回の記事を読んでない人は、先にそちらを読んでからこの記事に戻ってきてください。

初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」 - Qiita

第1回目と同様、今回もRubularを使って実際に正規表現を動かしながら学習できるようにしています。
また、置換処理ではテキストエディタのAtomを使用しています。

しっかりと内容が頭に定着するよう、実際に自分の手を動かしながら読み進めることを強くオススメします!

この記事で学ぶこと

本記事では表記の揺れを許容した検索と、HTMLタグからCSVへの置換処理を通じて、正規表現に関する以下のような知識を学びます。

  • ?.+*\w[^AB] の意味
  • ( ) を利用したキャプチャと置換
  • (?: ) を利用したキャプチャ無しのグループ化
  • 特別な文字のエスケープ
  • +* を使用する際の注意点

今回は前回よりもボリュームが多めです。
また、内容も少し難しくなっています。

ただし、どれも使用頻度の高いテクニックなので、これをマスターすればかなり実用的な正規表現が作れるようになるはずです。

もし読み進めるのがしんどくなったら、ときどき休憩を挟みつつ進めるようにしてくださいね。

動作環境

本記事の正規表現は以下の環境で動作確認しています。

  • Ruby
  • JavaScript
  • Atom

正規表現が使えるプログラミング言語やテキストエディタであれば、本記事のサンプルコードはほぼ同じように動くはずですが、正規表現の仕様や使えるメタ文字が微妙に異なることもあります。
何か動きがおかしいなと思ったら、自分が使っている環境の正規表現の仕様を確認するようにしてください。

それでは第2回の本編を始めましょう!

カタカナ語の表記の揺れを許容する

突然ですが、僕の妻はCoupé Baguette(クープ バゲット)という小さなパン屋さんを経営しています。

いちおう正式なカタカナ表記は「 クープ バゲット (半角スペース区切り)」なのですが、ネットを見ていると「クープバゲット(区切り無し)」だったり、「クープ・バゲット(中黒区切り)」だったり、「クープ バゲット(全角スペース区切り)」だったり、人によって様々です。
また、ときどき「バゲット」ではなく「バケット」と濁点なしの「ケ」で書いてる人もいます。

「みんなちゃんと "クープ バゲット" って書いてよ!!」・・・と叫んだところで誰も聞いちゃいないので、こちら側で表記の揺れを許容するしかありません。

さて、前振りが長くなりましたが、今回はこんなテキスト(架空のレビューテキスト)を扱います。

クープバゲットのパンは美味しかった。
今日はクープ バゲットさんに行きました。
クープ バゲットのパンは最高。
ジャムおじさんのパン、ジャムが入ってた。
また行きたいです。クープ・バゲット。
クープ・バケットのパン、売り切れだった(><)

「クープバゲット」やら「クープ・バケット」といった表記の揺れを許容しつつ、「クープ バゲット」について言及していると思われるレビューだけを抽出してみましょう。

ではまず前回と同様に、 http://rubular.com/ にアクセスして上記のテキストをYour test string欄に貼り付けてください。

Kobito.8AzINR.png
※スマートフォンで見ると画像が小さいかもしれないので、PCで見ることをオススメします。

続いて、Your regular expression欄に クープ バゲット (半角スペース区切り)と入力してください。
そうすると2行目にある「クープ バゲット」がマッチするはずです。

Kobito.nFRVJu.png

とはいえ、これはただの文字列検索です。メタ文字は一切使っていません。
ここから正規表現を使ってマッチする文字列を増やしていきます。

さまざまな区切り文字を許容する

前回は [AB] で「AまたはBのいずれか1文字」というメタ文字を紹介しました。
この知識を活用すると、次のように区切り文字を複数指定することができます。

クープ[  ・]バゲット

画面上では非常にわかりにくいですが、[ ] の中身は半角スペースと全角スペースと中黒の三文字が含まれています。
これをRubularに入力してください。
そうすると3行目と5行目のテキストにもマッチするはずです。

Kobito.c05s7D.png

濁点の有無を許容する

同じく正規表現を使って「バゲット」と「バケット」の両方にもマッチさせましょう。
もうおわかりかと思いますが、ここでも [ ] を使います。

クープ[  ・]バ[ゲケ]ット

Kobito.lJY3US.png

これで6行目のテキストにもマッチさせることができました。

しかし、1行目にある「クープバゲット(区切り無し)」にはマッチしていません。
これはどうやってマッチさせればよいでしょうか?

区切り文字の有無を許容する

まずは検索したい文字列のパターンを考えてみましょう。
今回は次のようなパターンになります。

「クープ、区切り文字(半角スペースまたは全角スペースまたは中黒)が1文字、もしくは区切り無し、バゲ(またはケ)ット」

新しく登場したのは「区切り文字が1文字、 もしくは区切り無し 」というパターンです。

今回のように 「~が1文字、または無し」を表現するためには ? というメタ文字を使います。(文字量を指定するので 量指定子 のひとつです)

? を使うと正規表現は次のようになります。

クープ[  ・]?バ[ゲケ]ット

これをRubularに入力すると・・・1行目の文字にもマッチしました!

Kobito.cMQZ5t.png

区切り文字を簡易的に表現する

さて、この正規表現はこれで完成!・・・としてもいいのですが、もうひとつ新しい知識を学習しておきましょう。
先ほどの正規表現の [  ・] の部分ですが、「半角スペースまたは全角スペースまたは中黒」と厳密に文字種を指定しなくても、現実的には「任意の1文字」という考えで十分だったりします。

正規表現にはちょうど「任意の1文字」を表す . というメタ文字(文字クラス)があります。
これを使うと先ほどの正規表現を次のように書き直せます。

クープ.?バ[ゲケ]ット

Rubular上でこの正規表現を入力しても、実行結果は変わりません。

Kobito.SjtVHu.png

もちろん、. は「任意の1文字」なので、「クープ★バゲット」や「クープ@バゲット」にもマッチしてしまいますが、今回のような要件であれば、その方がかえって良いかもしれませんね。

Rubyで動かしてみる

Rubyを使って対象の行だけを抽出する場合はこんな感じになります。

text = <<-TEXT
クープバゲットのパンは美味しかった。
今日はクープ バゲットさんに行きました。
クープ バゲットのパンは最高。
ジャムおじさんのパン、ジャムが入ってた。
また行きたいです。クープ・バゲット。
クープ・バケットのパン、売り切れだった(><)
TEXT

text.split(/\n/).grep(/クープ.?バ[ゲケ]ット/)
# => ["クープバゲットのパンは美味しかった。", "今日はクープ バゲットさんに行きました。", "クープ バゲットのパンは最高。", "また行きたいです。クープ・バゲット。", "クープ・バケットのパン、売り切れだった(><)"]

JavaScriptで動かしてみる

同様に以下はJavaScript(JS)で対象行を抽出するサンプルコードです。

var text = "クープバゲットのパンは美味しかった。\n今日はクープ バゲットさんに行きました。\nクープ バゲットのパンは最高。\nジャムおじさんのパン、ジャムが入ってた。\nまた行きたいです。クープ・バゲット。\nクープ・バケットのパン、売り切れだった(><)";

var lines = text.split(/\n/);

var targets = [];
for (var i = 0; i < lines.length; i++) {
  if (lines[i].match(/クープ.?バ[ゲケ]ット/)) {
    targets.push(lines[i]);
  }
}
targets;
// => ["クープバゲットのパンは美味しかった。", "今日はクープ バゲットさんに行きました。", "クープ バゲットのパンは最高。", "また行きたいです。クープ・バゲット。", "クープ・バケットのパン、売り切れだった(><)"]

Atomで検索する

Atomのようなテキストエディタでも同じように、表記の揺れを許容しながら検索できます。

Screen Shot 2016-02-07 at 7.14.30.png

HTMLタグをCSVへ変換する

ところで、正規表現は検索だけでなく、置換する場合にも非常に便利です。
というわけで、ここからは正規表現を使った置換処理を取り上げてみます。

たとえば以下のようなHTMLソースがあったとします。

<select name="game_console">
<option value="wii_u">Wii U</option>
<option value="ps4">プレステ4</option>
<option value="gb">ゲームボーイ</option>
</select>

理由はよくわかりませんが、あなたは上司から「selectタグの中身(option)をCSVに変換して渡してほしい」と言われました。
つまり、こういう結果が欲しい、ということです。

wii_u,Wii U
ps4,プレステ4
gb,ゲームボーイ

さて、あなただったらどうしますか?
まあ、3つぐらいなら手作業でも何とかなるかもしれませんが、実際は100個以上optionが並んでいる、と想定してください。

HTML/XMLパーサを使って変換プログラムを書くという手もありますが、ある程度パターンが決まっているケースであれば、正規表現を使って置換してしまう方が速いかもしれません。
ちょっとやってみましょう。

valueを抜き出す正規表現を考える

というわけでまず、optionのvalueと表示テキストを正規表現を使ってきれいに抜き出しましょう。
valueは次のようなパターンになっています。

「value=、ダブルクオート、英数字またはアンダースコアが1文字以上、ダブルクオート」

これを正規表現で表すとどうなるでしょうか?
「直前の文字が 1文字以上 」を表す場合は + というメタ文字(量指定子)を使います。
「英数字またはアンダースコア」は [a-z0-9_] のように [ ]- を使って書けます(第1回の記事を参照)。

よって value="[a-z0-9_]+" のように書けばvalueの部分にマッチさせることができます([a-z0-9_]+ 以外はメタ文字でない、ただの文字です)。
Rubularを使って確認してみましょう。

まず以下のテキストを貼り付けてください。

<select name="game_console">
<option value="wii_u">Wii U</option>
<option value="ps4">プレステ4</option>
<option value="gb">ゲームボーイ</option>
</select>

続いて value="[a-z0-9_]+" という正規表現を入力します。
これでvalueの部分がマッチするはずです。

Kobito.EY2BJ3.png

表示テキストを抜き出す正規表現を考える

次に表示テキスト("Wii U" や "プレスト4" など)を抜き出す正規表現を考えてみましょう。
難しそうに思えるかもしれませんが、ここでは単純にこう考えることにします。

> で始まり、何かしらの文字が続き、< で終わる」

厳密に言うと >< はタグの一部なので表示テキストではありませんが、今はとりあえず良しとしてください。

「何かしらの文字が続く」というのは、本記事で紹介した .+ を使えば実現できます。
つまり、表示テキストを抜き出す正規表現は次のようになります(>< はメタ文字ではない、ただの文字です)。

>.+<

上の正規表現をRubularに貼り付けてみましょう。

Kobito.bWxZOQ.png

OK、表示テキスト部分がマッチしましたね。

さて、と・・・。

valueと表示テキストを抜き出す正規表現はわかりましたが、問題はこれからどうやって CSV形式に置換するかです。

今回のようなケースであれば、次のように処理するのが良いと思います。

  1. 行全体にマッチする正規表現を作る
  2. valueと表示テキストの部分をそれぞれ ( ) で囲んでキャプチャする
  3. キャプチャを利用して新しい文字列を組み立てる

順を追って見ていきましょう。

1. 行全体にマッチする正規表現を作る

まず行全体にマッチする正規表現を作ります。

<option value="[a-z0-9_]+">.+<\/option>

[a-z0-9_]+.+ の部分が正規表現(メタ文字)です。

あと、<\/option> に入っているバックスラッシュ(\)はスラッシュ(/)をエスケープするためのエスケープ文字です。
RubyやJavaScriptでは /abc/ のようにスラッシュを使って正規表現オブジェクトを作るので、正規表現中に出てくるスラッシュはエスケープする必要があります。

それ以外はメタ文字ではない、ただの文字列です。

この正規表現をRubularに貼り付けると、optionの各行全体がマッチするはずです。

Kobito.ao4c9c.png

2. valueと表示テキストの部分をそれぞれ ( ) で囲んでキャプチャする

次にvalueと表示テキストをそれぞれ ( ) で囲みます。
正規表現は次のようになります。

<option value="([a-z0-9_]+)">(.+)<\/option>

これをRubularに入力すると、ちょっとした変化が起きます。

Kobito.R9drLJ.png

画面の右下にMatch groupsという欄が新たに登場しました。
Match groupsには以下のように表示されています。

Match 1
1.  wii_u
2.  Wii U
Match 2
1.  ps4
2.  プレステ4
Match 3
1.  gb
2.  ゲームボーイ

1がvalue、2が表示テキストになっていますね。
正規表現に ( ) を使うと、その部分がキャプチャ(捕捉)され、連番が付けられるのです。

3. キャプチャを利用して新しい文字列を組み立てる

さて、残念ながらRubularには文字列を置換する機能がありません。
ここまで来たら次は Atom にバトンを渡します。
(Atom以外のテキストエディタを使ってもOKです。ただし、置換で使用する記号等の扱いが微妙に異なる場合があります)

Atomに先ほどのテキストと正規表現を貼ります。
Rubularと同様、各行の全体がマッチしていることを確認してください。

Screen Shot 2016-02-09 at 4.15.30.png

ちなみに、AtomはRubyやJSのようにスラッシュを使った正規表現リテラルを持っているわけではないので、スラッシュをエスケープしなくても大丈夫です。
ただ、エスケープしていても今回は問題ないので、Rubularで作った正規表現をそのまま使うことにします。

では続いて、Replace in current buffer欄に次のような文字を入力します。

$1,$2

Screen Shot 2016-02-09 at 4.18.47.png

入力したら Replace ボタンをクリックしてみましょう。

Screen Shot 2016-02-09 at 4.19.54.png

最初のoptionが wii_u,Wii U に置換されました!

なんとなく予想が付いていると思いますが、$1$2 はそれぞれキャプチャされた1番目の文字列と2番目の文字列を表しています。
つまり、$1,$2 はマッチした文字列を value,表示テキスト の文字列に置換していることになるわけです。

同じように Replace ボタンを2回押すか、Replace Allボタンを1回押すと全ての文字列を置換することができます。

Screen Shot 2016-02-09 at 4.21.17.png

はい、ちゃんと全部置換されましたね!

表示テキストがないoptionも置換できるようにする

ではもう少しバリエーションを増やしてみましょう。
ここでは次のように表示テキストがないoptionを考えてみます。
value="none"になっているoptionが新しく追加した行です。

<select name="game_console">
<option value="none"></option>
<option value="wii_u">Wii U</option>
<option value="ps4">プレステ4</option>
<option value="gb">ゲームボーイ</option>
</select>

これをRubularに貼り付けてみると・・・。
残念、表示テキストがないoptionはマッチしませんでした。

Kobito.bAmU88.png

ですが、これは先ほどの正規表現をちょっと修正すればうまくいきます。
表示テキストを抽出する正規表現は .+ でした。
これは「任意の文字が1文字以上」の意味ですが、これを「任意の文字が 0文字以上 」に変えれば良いのです。

「直前の文字が0文字以上」を表す場合は * というメタ文字(量指定子)を使います。
よって、.+.* に変えればOKです。

<option value="([a-z0-9_]+)">(.*)<\/option>

Rubularで確認してみましょう。

Kobito.s3UEBT.png

はい、各optionがちゃんと選択状態になりました。
Match groups欄を見ると「value="none"」の表示テキストも正しく「2.(何にもない)」になっています。

この正規表現を使えば、Atomでも問題なく置換できますね!

4OZBkLyxmv.gif

selectedになっているoptionも置換できるようにする

最後にもう一つだけ、バリエーションを増やしてみましょう。
それはoptionタグのどれかひとつが選択状態になっている(selected属性が付いている)場合です。

<select name="game_console">
<option value="none"></option>
<option value="wii_u" selected>Wii U</option>
<option value="ps4">プレステ4</option>
<option value="gb">ゲームボーイ</option>
</select>

とりあえず、上のHTMLをRubularに貼り付けてみてください。

Screen Shot 2016-02-09 at 4.42.58.png

うーん、やはりselectedが付いているoptionは選択されませんでした・・・。

でも大丈夫です。
これも先ほどの正規表現を修正すればマッチさせることができます。

ここでのポイントは「"selected"があり、または無し」というOR条件を追加することです。

A? のような正規表現は「Aがあり、または無し」の意味でしたが、実はメタ文字の ? は2文字以上の文字列に対しても使えます。
ただしその場合は対象となる文字列を ( ) で囲みます。( グループ化 といいます)
具体的には (ABC)? のように書くと、「文字列 ABC があり、または無し」の意味になります。

説明がちょっと長くなりましたが、今回は次のような正規表現を使えばOKです。
(selectedの前に半角スペースが入っている点に注意してください)

<option value="([a-z0-9_]+)"( selected)?>(.*)<\/option>

Kobito.veCftz.png

はい、これで全部の行がマッチしましたね!

・・・って、あれ?なんかちょっと変です。
Match groupsを見てください。

Match 1
1.  none
2.   
3.   
Match 2
1.  wii_u
2.  selected
3.  Wii U
Match 3
1.  ps4
2.   
3.  プレステ4
Match 4
1.  gb
2.   
3.  ゲームボーイ

今まで1と2しかなかったのに、急に1、2、3になっちゃいました!

はい、実は何も考えずに ( ) を使うと、すべての ( ) がキャプチャ対象になってしまいます。
なので、( selected)?の部分もキャプチャされて、1、2、3になってしまったわけです。

キャプチャする必要がない ( )(?: ) のように、?: をつけてやります。
というわけで正規表現を次のように変更しましょう。

<option value="([a-z0-9_]+)"(?: selected)?>(.*)<\/option>

こうすればキャプチャされる文字列が2個に戻ります。

Kobito.Kf3Lpl.png

もちろん、Atomで置換することも可能です。
正規表現を使った文字列置換は柔軟性があって便利ですね!

rI2OEfqebt.gif

第2回で紹介するテクニックは基本的にこれで終わりなのですが、便利なTipsや注意事項がいくつかあるので、ここからはそれを紹介していきます。

[0-9] を \d に置き換える

前回の記事では \d というメタ文字を紹介しました。
これは半角数字を表すメタ文字(文字クラス)です。
つまり、\d[0-9] は同じ意味になります。

また、\d[ ] の中でも使えます。
なので、[a-z0-9_]+[a-z\d_]+ に置き換えることができます。

以下は \d を使った場合のRubularの実行結果です。
[0-9] を使ったときと同じ結果になっていますね。

<option value="([a-z\d_]+)"(?: selected)?>(.*)<\/option>

Kobito.uFzCoA.png

[a-z\d_]+ を \w に置き換える

ここからさらにもう一段階、正規表現をシンプルにしてみましょう。

正規表現には \w というメタ文字があります。
これは「英単語を構成する文字」の意味です。
RubyやJavaScriptでは「\w = [a-zA-Z0-9_](半角英数字とアンダースコア1文字) 」という仕様になっています。

なので、[a-z\d_]+\w+ と書き換えてもほぼ同じ意味になります(厳密には\wを使うと大文字のアルファベットも含まれる点が異なります)。

正規表現が短くなりますし、\w の意味を知っていれば何をやっているのかも理解しやすくなります。

念のため、検索結果に変わりがないことをRubularを使って確かめてください。

<option value="(\w+)"(?: selected)?>(.*)<\/option>

Kobito.89F2JK.png

Rubyで置換してみる

Atomではなく、Rubyのコード内で文字列置換を実行してみましょう。
Rubyのサンプルコードはこのようになります。
キャプチャされた文字列を $1 ではなく \1 で参照しているのがAtomと異なる点です。
$1 のような特殊変数が使えるケースもあるのですが、長くなるので割愛します)

html = <<-HTML
<select name="game_console">
<option value="none"></option>
<option value="wii_u" selected>Wii U</option>
<option value="ps4">プレステ4</option>
<option value="gb">ゲームボーイ</option>
</select>
HTML

replaced = html.gsub(/<option value="(\w+)"(?: selected)?>(.*)<\/option>/, '\1,\2')

puts replaced
# <select name="game_console">
# none,
# wii_u,Wii U
# ps4,プレステ4
# gb,ゲームボーイ
# </select>

JavaScriptで置換してみる

こちらはJavaScriptで置換するコード例です。
こちらはAtomと同じように $1 を使います。

var html = "<select name=\"game_console\">\n<option value=\"none\"></option>\n<option value=\"wii_u\" selected>Wii U</option>\n<option value=\"ps4\">プレステ4</option>\n<option value=\"gb\">ゲームボーイ</option>\n</select>\n";

var replaced = html.replace(/<option value="(\w+)"(?: selected)?>(.*)<\/option>/g, "$1,$2");

console.log(replaced);
// <select name="game_console">
// none,
// wii_u,Wii U
// ps4,プレステ4
// gb,ゲームボーイ
// </select>

重要: * と + は「貪欲」であることに注意!

ところで、今回紹介した *+ は扱い方を間違えると ドツボにハマります。
ちょっと難しいかもしれませんが、今から説明する内容は重要なポイントなのでしっかり理解してください。

先ほど使った例では1行ごとにoptionが変わっていました。
では、改行せずにoptionを並べるとどうなるでしょうか?
試しにこんなテキストを使ってみましょう。

<option value="ps4">プレステ4</option><option value="gb">ゲームボーイ</option>

これをRubularに貼り付け、先ほどと同じ正規表現 <option value="(\w+)"(?: selected)?>(.*)<\/option> を使って検索してみます。
結果を確認してみましょう。

Kobito.SmBoYr.png

あれ、なんかおかしくないですか?
Match group欄を見てください。

1.  ps4
2.  プレステ4</option><option value="gb">ゲームボーイ

むむむ、表示テキストがちゃんと取れてない・・・。

しかし、これは正規表現に言わせると「いいや、俺はちゃんと言われたとおりに仕事をした!」と反論されてしまうケースです。
なぜでしょうか?

問題は >(.*)< の部分にあります。
このパターンを日本語に直すと以下のようになります。

> で始まり、任意の文字が0個以上連続し(.*)、< で終わる」

これをふまえて、元のHTMLを見てください。

Screen Shot 2016-02-09 at 5.10.46.png

>(★)で始まり、任意の文字が0個以上連続し(.*)、<(☆)で終わる」

・・・どうでしょう?
パターンとしては間違ってないですよね?

.* は「任意の文字が0個以上」の意味なので、途中に出てくる < も「任意の文字」として扱われてしまうのです。

これは .+ に変えても同じ結果になります。
+* はしばしば「 貪欲なマッチ を試みる量指定子」と呼ばれます。
パターンの結果として矛盾がなければ、(貪欲に)最長のマッチを結果として返すのです。

しかし、今回の場合、これは期待する結果ではありません。
正規表現を工夫して、正しくvalueと表示テキストを抽出できるようにしましょう。

解決策1:「任意の1文字」よりも厳しい条件を指定する

1つの考え方は「任意の1文字」というゆるい条件を、少し厳しい条件に変えることです。
具体的には「 < 以外の 任意の文字」に変えてあげるとうまくいきます。

A以外の 任意の文字」を表す場合は [^A] と書きます。
[ ] の最初に ^ が入ると否定の意味に変わるのです。
[^AB] であれば「AでもなくBでもない任意の1文字」の意味になります。

よって、「< 以外の任意の文字」を表す場合は [^<] と書きます。
では正規表現を次のように書き換えてみましょう。

<option value="(\w+)"(?: selected)?>([^<]*)<\/option>

Rubularに貼り付けてみると・・・無事にvalueと表示テキストを抽出できました!

Kobito.lxYTKK.png

< 以外の任意の文字が0文字以上」というパターンに変わったので、正規表現は途中の < を突き破ってマッチすることはできなくなったわけです。

解決策2:最短のマッチを返すように指定する

もう一つの方法は *+ の「貪欲さ」をなくすことです。

>(.*)< の意味をもう少し厳密に書くと以下のようになります。

> で始まり、任意の文字が0個以上連続し(.*)、 最後に見つかった < で終わる」

今回のケースはこれを次のように変えればうまくいくはずです。

> で始まり、任意の文字が0個以上連続し(.*)、 最初に見つかった < で終わる」

こうするためには >(.*?)< のように ? を付けます。
*?+? にすると最長ではなく、最短のマッチを結果として返すようになるのです(これを 最小量指定子 と呼びます)。

というわけで次のような正規表現を作ってRubularに入力してみましょう。

<option value="(\w+)"(?: selected)?>(.*?)<\/option>

Kobito.47j41v.png

うん、大丈夫ですね!
* が貪欲さを失ったので、最初に見つかった < でマッチが止まるようになっています。

ただし、実行環境によっては *?+? を使えないケースがあります(JavaScriptやAtomでは使えます)。
その場合は解決策1で紹介した、[^<]* のような否定条件の正規表現を使ってください。

ちなみに、*+ が「貪欲なマッチ」と呼ばれるのに対して、*?+? は「 控えめなマッチ 」と呼ばれることがあります。

注意:ミスが許されない置換は1個ずつ確認しながら実行しましょう

正規表現を使った置換は大変便利ですが、想定外の文字列に遭遇して意図しない形に変換される恐れもあります。
前述の貪欲なマッチで説明したケースがまさにそうですね。

ソースコードのようにちょっとした間違いが命取りになるケースでは、何も確認せずに「すべて置換!」してしまうと非常に危険です。
なので、変換ミスが許されないテキストに対しては、ひとつずつ確認しながら「置換、次へ、置換、次へ」を繰り返すことをオススメします。

まとめ

さあ、いかがだったでしょうか?
今回はかなりのボリュームでしたね。

本記事では以下のようなことを学びました。

  • ? は「直前の文字が1個、または無し」を表す
  • . は「任意の1文字」を表す
  • + は「直前の文字が1個以上」を表す
  • * は「直前の文字が0個以上」を表す
  • ( ) はマッチする部分をキャプチャ(捕捉)する
  • キャプチャした部分は置換するときに $1\1 で参照できる
  • \w は「英単語を校正する文字(半角英数字とアンダースコア)」を表す
  • [^AB] は「AでもなくBでもない任意の1文字」を表す
  • 正規表現中の特別な文字は \ でエスケープする
  • ( ) はキャプチャだけでなく、グループ化にも使われる
  • (ABC)? は「文字列 ABC があり、または無し」を表す
  • (?: ) はキャプチャ無しでグループ化する場合に使う
  • *+ は「貪欲」で最長マッチを返すため、使い方を誤ると思いがけない結果が返る
  • *?+? にすると、最短マッチを返す
  • テキストエディタで重要なテキストを置換する際は、ひとつずつ確認しながら置換する

新しい内容がたくさん出てきたので、一度に全部吸収できなかった場合は何度か読み直しましょう。
今回の内容をすべて理解できれば、読み書きできる正規表現のバリエーションがかなり増えてくるはずです!

さて、第1回、第2回と正規表現についてあれこれ説明してきましたが、まだ「これで全て」というわけではありません。
正規表現の基礎を一通りマスターするためにはあと1~2回連載を続ける必要がありそうです。

次回以降の記事では次のようなメタ文字や知識を説明する予定です。

  • よく使いそうな各種のメタ文字(|, \s, \t, \n 等)
  • アンカーの使い方(^, $, \A, \z, \b 等)
  • その他、知っておくと便利な知識

正規表現をしっかり身につけたい!という読者の方は、次回以降の記事も引き続き読んでやってください。

なお、この記事をストックしてもらえると、新しい記事を公開したときに更新を通知しますので、よかったら ストック をお願いします。

それでは今回はこのへんで!

2016.02.23追記:第3回、第4回の記事を公開しました!
第3回、第4回の記事を公開しました。
最後まで読めば中級者レベルの正規表現もマスターできます。
がんばって学習していってください!

初心者歓迎!手と目で覚える正規表現入門・その3「空白文字を自由自在に操ろう」 - Qiita

説明する内容

  • ^$\s\t\n の意味
  • | を使ったOR検索
  • 環境によって異なる改行コードと正規表現の関係
  • 使われる場所によって異なる ^ の意味

初心者歓迎!手と目で覚える正規表現入門・その4(最終回)「中級者テクニックをマスターしよう」 - Qiita

説明する内容

  • \b の意味
  • 肯定の先読み、後読み
  • 否定の先読み、後読み
  • 後方参照
  • メタ文字の複雑な組み合わせ
  • 正規表現とパフォーマンス
  • メタ文字のエスケープ
  • [ ] 内のメタ文字の働き
  • {n,}{,n} の意味
  • \W\S\D\B の意味

正規表現を学習するのにオススメの本

正規表現をしっかり学ぶなら、オライリージャパンの「詳説 正規表現」がオススメです。
僕もこの本を読んで正規表現を学習しました。

詳説 正規表現 第3版
51cRxtwo7IL.jpg