はじめに:連番は脳にやさしくない!?
Rubyをはじめ、多くの正規表現エンジンでは ( )
を使ったキャプチャ(マッチした文字列の捕捉)をサポートしています。
キャプチャを使うとキャプチャした部分を連番で参照することができます。
以下は "yyyy-mm-dd" 形式の文字列を "yyyy年mm月dd日" 形式に置換するRubyのコード例です。
s = '2016-05-08'
# \1と\2と\3がキャプチャした文字列を連番で参照している部分
puts s.gsub(/(\d+)-(\d+)-(\d+)/, '\1年\2月\3日')
# => 2016年05月08日
ただ、連番を使うと数字が増えれば増えるほど、その番号と格納されている内容を脳内でマッチングするのが大変になります。
また、数字が少なくても正規表現が複雑だと「この ( )
は一体何をキャプチャしてるんだろう?」と正規表現を作った人の意図がつかみにくくなります。
そこで正規表現では連番でなく、 変数のように名前を付けて参照する 方法があります。
これを正規表現の 名前付きキャプチャ と言います。
この記事ではその名前付きキャプチャの使用例を紹介します。
正規表現のキャプチャがわからない方はこちらへ
「そもそもキャプチャ自体がよくわからない」という場合は、以下の記事を読んでからこの記事に戻ってきてください。
初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」 - Qiita
動作環境(と名前付きキャプチャが使える環境の例)
この記事のサンプルコードは以下の環境で動作確認しています。
- Ruby 2.3
ただし、(未検証ですが)正規表現の名前付きキャプチャは以下の環境でも使用できるようです。
- Python
- PHP
- Perl (5.10以降)
- .NET (C#, VB.NET等)
この記事で紹介するような処理はこれらの環境でもできるはずなので、使い方については各環境のAPIドキュメント等を参照してください。
なお、JavaやJavaScript(Atomエディタを含む)では標準で名前付きキャプチャをサポートしていないそうです。(外部ライブラリを導入すると使用できるようです)
それでは以下が本編です。
名前付きキャプチャを使って置換する
連番を使う場合のキャプチャは ( )
ですが、名前付きキャプチャを使う場合は次のような構文を使います。
(?<name>pattern)
?< >
の中に入る文字列がキャプチャの名前で、どんな名前を付けるかは自由です。
たとえば、冒頭で登場した (\d+)-(\d+)-(\d+)
という正規表現で名前付きキャプチャを使う場合は次のようになります。
(?<year>\d+)-(?<month>\d+)-(?<day>\d+)
Rubularを使って検索結果を確認してみましょう。
Match groups欄に注目してください。
?< >
で指定した名前でテキストの内容がキャプチャされているのがわかりますね。
一方、置換用の文字列では \1
のような番号ではなく \k<name>
という文字列を指定します。
なので、\1年\2月\3日
は次のように書き換わります。
\k<year>年\k<month>月\k<day>日
これらの内容を組み合わせると、名前付きキャプチャを使って置換するコード例は次のようになります。
s = '2016-05-08'
puts s.gsub(
/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
'\k<year>年\k<month>月\k<day>日'
)
# => 2016年05月08日
名前付きキャプチャを変数に入れる
上の例では \k<year>
のように置換用の文字列や正規表現の中で直接名前を指定しましたが、キャプチャした部分を変数に入れて使うこともできます。
以下はそのコード例です。
s = '2016-05-08'
if m = s.match(/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/)
year = m[:year]
month = m[:month]
day = m[:day]
puts "year: #{year}, month: #{month}, day: #{day}"
# => year: 2016, month: 05, day: 08
end
また、Rubyの場合、=~
演算子の左辺に正規表現リテラル、右辺に文字列を置くと、名前付きキャプチャを直接ローカル変数にアサインできます。
s = '2016-05-08'
if /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/ =~ s
# 名前付きキャプチャがそのままローカル変数になる
puts "year: #{year}, month: #{month}, day: #{day}"
# => year: 2016, month: 05, day: 08
end
なお、名前付きでない、ただの ( )
を使う場合は次のようなコードになります。
s = '2016-05-08'
if m = s.match(/(\d+)-(\d+)-(\d+)/)
year = m[1]
month = m[2]
day = m[3]
puts "year: #{year}, month: #{month}, day: #{day}"
# => year: 2016, month: 05, day: 08
end
Rubyでは他にも $~
のようなグローバル変数を使ってキャプチャを参照する方法がありますが、ここでは割愛します。
後方参照で名前付きキャプチャを使う
後方参照で名前付きキャプチャを使うこともできます。
以下は名前付きキャプチャなしで後方参照を使用する例です。
# HTMLからhrefのURLと表示テキストの内容が全く一緒のリンク(aタグ)を抜き出す
html = '<p>Please visit <a href="http://google.com">http://google.com</a>.</p>'
# 正規表現で該当するリンクを抜き出す。\1が連番で後方参照されている部分
puts html[/<a href="(.+?)">\1<\/a>/]
# => <a href="http://google.com">http://google.com</a>
一方、以下は名前付きキャプチャありで後方参照を使用する例です。
後方参照の場合も置換の時と同じように、?<name>
や \k<name>
で名前付きキャプチャを使うことができます。
html = '<p>Please visit <a href="http://google.com">http://google.com</a>.</p>'
# 名前付きキャプチャと後方参照を組み合わせる
puts html[/<a href="(?<url>.+?)">\k<url><\/a>/]
# => <a href="http://google.com">http://google.com</a>
なお、後方参照がよくわからない、という方は以下の記事を参照してください。
初心者歓迎!手と目で覚える正規表現入門・その4(最終回)「中級者テクニックをマスターしよう」 - Qiita
まとめ
というわけで、この記事では正規表現で名前付きキャプチャを使う方法を説明しました。
例がシンプルすぎるとメリットが伝わりにくいかもしれませんが、正規表現が複雑になってくると連番では何をやっているのかひと目で分かりにくくなってきます。
そんな場合は名前付きキャプチャを使って、コードの可読性を上げることを検討してみてください。
あわせて読みたい
「正規表現は苦手なのでゼロから学びたい」という方は以前に執筆した以下の連載記事をどうぞ。
初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」 - Qiita
初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」 - Qiita