手を動かしながら覚える正規表現<置換処理編>

スポンサーリンク

はじめに

文字列の検索や文字列の書式チェックに使うだけでも正規表現は便利なのですけど、置換の際にも使えるようになると、正規表現を何倍も便利に感じられます。

多機能なテキストエディタであればたいていは正規表現を用いた置換をサポートしています。プログラミングする方だけでなくテキストエディタで文章を書いている方にとっても便利な道具となりますので、正規表現による置換処理をぜひ身につけていって下さい。

なお、このページは基礎入門編を読み終えた方を想定しております。正規表現について分からないことがあればそちらをご覧下さい。

登場した正規表現はリファレンスにまとめてあります。まとめて振り返る時などはこちらをご覧下さい。

単純な置換

まずは基本から。単純な文字列置換を紹介します。置換では「マッチパターン」と「置換文字列」の二つを指定することになります。例えば「ください」を「下さい」に置換する場合、マッチパターンと置換文字列はそれぞれ「ください」「下さい」になります。以下、「ください」を「下さい」に置換する例です。

ここで履物を脱いでください。
一つ下さい。
やめてください。
ここで履物を脱いで下さい。
一つ下さい。
やめて下さい

それでは早速練習してみましょう。「入力」の文字列に対して適用するマッチパターンの正規表現と置換文字列を考えて下さい。「マッチパターン」と「置換文字列」を入力して「確認」ボタンをクリックすると、置換した結果の文字列を下の欄に表示します。これが「出力」の文字列を一致すると正解です。

文字列中の「全て」を「すべて」に置換して下さい。

入力
出力
マッチパターン 置換文字列  
全て すべて

文字列の削除

特定の文字列を削除する方法です。例えば「株式会社」を削除する場合には、マッチパターンと置換文字列にはそれぞれ「株式会社」「」を指定します。置換文字列に何も指定しないというところがポイントです。

それでは練習しましょう。

文字列中の「曜日」を削除して下さい。

入力
出力
マッチパターン 置換文字列  
曜日

行頭の文字列置換

行頭を表す正規表現は「^」でしたね。マッチパターンに「^」を使います。例を見てみましょう。行頭の「・」を「○」に変えたいという例です。

・春
  ・春眠暁を覚えず
・夏
  ・夏休みの宿題は8月31にやる
・秋
  ・天高く馬肥ゆる秋
・冬
  ・冬将軍

この文字列に対してマッチパターンと置換文字列をそれぞれ「・」「○」とすると次のようになります。

春眠暁を覚えず
夏休みの宿題は8月31にやる
天高く馬肥ゆる秋
冬将軍

これは失敗。マッチパターンと置換文字列をそれぞれ「^・」「○」とします。

春
  ・春眠暁を覚えず
夏
  ・夏休みの宿題は8月31にやる
秋
  ・天高く馬肥ゆる秋
冬
  ・冬将軍

もう一つ例を見てみましょう。メールの返信の時に、相手の文章の先頭に>を付けて内容を引用することがあります。これを正規表現を使った置換ではどのように書くのでしょうか。

日 時:2008年10月10日 10:00~12:00
場 所:A-3会議室
参加者:プロジェクトメンバー、担当営業
>日 時:2008年10月10日 10:00~12:00
>場 所:A-3会議室
>参加者:プロジェクトメンバー、担当営業

マッチパターンと置換文字列にはそれぞれ「^」「>」を指定します。置換というとAをBに置き換えるという印象がありますが、「^」のような位置を表すパターンだけを指定すると挿入のようなことができます。

それでは練習しましょう。

行頭の空白文字を削除して下さい。

入力
出力
マッチパターン 置換文字列  
^\s+

行頭に#を挿入してコメントアウトして下さい。

入力
出力
マッチパターン 置換文字列  
^ #

行末の文字列置換

行頭の文字列置換が分かっていれば簡単です。行末を表す正規表現は「$」でしたね。マッチパターンに「$」を使います。例を見てみましょう。行頭の「.txt」を「.bak」に変えたいという例です。

atxt
contents.txt.gz
data.dat
index.html
readme.txt

マッチパターンと置換文字列をそれぞれ「\.txt$」「.bak」とします。

atxt
contents.txt.gz
data.dat
index.html
readme.bak

\.txt」のように行末を表す$を忘れると2行目の「contents.txt.gz」がマッチして「contents.bak.gz」になってしいます。

また、ついうっかりマッチパターンのピリオドをエスケープし忘れて「.txt$」としてしまうと1行目の「atxt」がマッチして「.bak」になってしまうので注意して下さい。

それでは練習しましょう。

行末に<br>を挿入して下さい。

入力
出力
マッチパターン 置換文字列  
$ <br>

パターンで置換

例えばクレジットカード番号をそのまま画面に表示してしまうと、他の人に画面を覗かれて番号が流出して問題になることがあります。そんな時は別の文字に置き換えてマスクしてしまうのが良いですね。これを正規表現でやってみましょう。今回の例ではクレジットカード番号は数字16桁で4桁毎にハイフンで区切られているものとします。

あなたのクレジットカード番号は0123-4567-8901-2345です。

これを次のようにします。

あなたのクレジットカード番号は****-****-****-****です。

この場合のマッチパターンと置換文字列はそれぞれ「\d{4}-\d{4}-\d{4}-\d{4}」「****-****-****-****」となります。正規表現が読めれば簡単ですね。もし「\d{4}-\d{4}-\d{4}-\d{4}」の意味が分からないようであれば基礎入門編で確認して下さい。

マッチパターンのところに正規表現を書くだけです。それでは練習しましょう。

「Google」や「Gooogle」や「Goooogle」のようにGで始まって、oが2個以上続いて、gleで終わる文字列を「Google」に置換して下さい。

入力
出力
マッチパターン 置換文字列  
Go{2,}gle Google

マッチオプション

基礎入門編の「大文字小文字の同一視」では、アルファベットの大文字と小文字を区別しないようにするための例として以下のものを紹介しました。

[Perl]
if ($filename =~ m/\.(gif|png)$/i) {
[JavaScript]
if (filename.match(/\.(gif|png)$/i)) {

上記で強調している「i」が「大文字小文字の同一視」のオプションになります。

もう一つ紹介しましょう。

数字を*に置換する場合を考えてみます。

1500

数字は「\d」ですから、マッチパターンと置換文字列をそれぞれ「\d」「*」とすれば良さそうですが、これで置換するとこうなります。

*本500円

上手く行きません。最初だけ置き換わって残りはそのままになってしまうのです。通常は最初にマッチした場所だけが対象になります。そうではなく、マッチする場所すべてを対象にする場合には「g」オプションを使います。「g」オプションを使えばグローバルマッチを行い、マッチする場所すべてが対象になりますので以下のようになります。

****

マッチオプションはツールや言語によって他のものもありますのでチェックしてみて下さい。ここでは「大文字小文字の同一視」の「i」と、「グローバルマッチ」の「g」を覚えましょう。それでは練習です。

拡張子「.txt」(大文字の場合も含む)を「.txt」(小文字)に置換して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
\.txt$ .txt i

アルファベットを「_」に置換して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
[a-zA-Z] _ g

perl」(大文字の場合も含む)を「(____)」に置換して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
perl (____) ig

キャプチャ

この章は重要です。キャプチャを使えるようになると正規表現生活の質が大幅に向上するので、ぜひ覚えてから帰って下さい。

大文字2文字以上を強調するという例を考えてみましょう。ここでは強調は<em></em>で囲むこととします。

国内総生産はGDP、国民総生産はGNPです。
国内総生産は<em>GDP</em>、国民総生産は<em>GNP</em>です。

マッチパターンは「[A-Z]{2,}」ですけど、置換文字列が困りました。上記例での「GDP」「GNP」のように置換後の文字列がマッチした文字列によって変わっています。こんな時に活躍するのがキャプチャです。

キャプチャというのはマッチした文字列を捕獲して、あとで使えるようにしてくれる仕組みです。

まずはキャプチャしたいところを括弧で囲みます。「([A-Z]{2,})」ですね。キャプチャした文字列は「$1」で参照できます。今回の場合では置換文字列を「<em>$1</em>」とします。

こうするとマッチパターンにマッチした文字列がGDPだった場合に、$1にはGDPが入るので、<em>GDP</em>で置換します。

マッチパターン マッチした文字列 置換文字列 置換する文字列
([A-Z]{2,}) GDP <em>$1</em> <em>GDP</em>
([A-Z]{2,}) GNP <em>$1</em> <em>GNP</em>

括弧が複数ある場合は括弧の開始の順に「$1$2、……」のようにいくつもキャプチャできます。

ここではキャプチャした文字列の参照に$1を使いましたが、ツールや言語によって方法は異なります。詳しくはご利用の環境で調べて下さい。ちなみに、PerlとJavaScriptは$1を使います。

もう一つ例を見てみます。値とキーをカンマ区切りにしてあるデータを、「キー=値」という形式に変換します。

258,id
Japan,country
4,level
id=258
country=Japan
level=4

マッチパターンと置換文字列はそれぞれ「(.*),(.*)」「$2=$1」となります。カンマの前までの文字列が$1、カンマの次からの文字列が$2に入ります。これを「$2=$1」という形に置換するので目的の形式に変換できます。

マッチパターン マッチした文字列 $1 $2 置換文字列 置換する文字列
(.*),(.*) 258,id 258 id $2=$1 id=258
(.*),(.*) Japan,country Japan country $2=$1 country=Japan
(.*),(.*) 4,level 4 level $2=$1 level=4

そろそろキャプチャに慣れたでしょうか。練習して確実に身につけてしまいましょう。

YYYYMMDD」形式の日付データを「YYYY-MM-DD」形式に置換して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
(\d{4})(\d{2})(\d{2}) $1-$2-$3

各行の文字列を「<li>」と「</li>」で囲って下さい。

入力
出力
マッチパターン 置換文字列 オプション  
(.*) <li>$1</li>

perl」(大文字の場合も含む)を「<b>」と「</b>」で囲んで下さい。

入力
出力
マッチパターン 置換文字列 オプション  
(perl) <b>$1</b> ig

キャプチャなしの括弧

キー=値」ないしは「キー,値」あるいは「キー=>値」と書かれている文字列を「KEY[キー]:VAL[値]」という文字列に置換する正規表現を考えてみましょう。例えばこのようになります。

マッチパターン 置換文字列
(.*)(=>|=|,)(.*) KEY[$1]:VAL[$3]

これで特に問題ないのですけど、$2がなくて$3が出てくるところが少し気になりますね。この程度であれば間違えることもあまりないでしょうけど、後で正規表現を修正して括弧が増えた場合に$の後の数字をいちいち直さないといけないのは手間ですし、ミスにもつながります。

そんな時にはキャプチャなしの括弧を使うとすっきりします。先ほどの例はこのように書くことができます。

マッチパターン 置換文字列
(.*)(?:=>|=|,)(.*) KEY[$1]:VAL[$2]

普通に括弧を使うとグループ化とキャプチャの両方の動作を行いますが、「?:」を括弧に付けることで、グループ化だけを行うようにすることができます。どの部分をキャプチャするのかが明確になる反面余計な記述が増えるので、分かりやすくなるかはケースバイケースです。適宜使い分けていただければよいかと思います。

キャプチャなしの括弧を使う利点がもう一つあって、キャプチャを行わないことによる処理速度の向上が期待できます。大量のデータを扱う場合には性能向上の一つの方法として使える場合もありますので、記憶の片隅にでもとどめておいてください。

非欲張り量指定子

ダブルクォートで囲まれた部分をemを使って強調する例を考えてみます。

"Will you please open this door?" said the man. "Sure."
<em>"Open this door."</em> said the man. <em>"Sure."</em>

前章で出てきたキャプチャを使います。マッチパターンと置換文字列をそれぞれ「(".*")」「<em>$1</em>」とするのは惜しい間違い。次のようになってしまいます。

<em>"Open this door." said the man. "Sure."</em>

.*」の部分が途中のダブルクォートも含めてマッチするのでこのような結果になります。こういう時は「非欲張り量指定子」を使います。「非欲張り量指定子」は覚えていますか? 指定子の後ろに「?」を付けるのでしたね。マッチパターンは「(".*?")」となります。マッチする箇所が複数になるので「g」オプションを忘れないように注意して下さい。

それでは練習しましょう。

各行のHTMLのタグを削除して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
<.*?> g

前後読み

Javaを使おうと思っていたら大人の事情でC#を使うことになってしまったなんていうことありますよね? そんな時に発生するのがドキュメントの修正。「Java」と書いてある部分を「C#」に直す作業が発生します。こういう時はマッチパターンと置換文字列をそれぞれ「Java」「C#」にして置換、といきたいところです。しかし次のような文章があるかもしれません。

サーバ側はJavaで書き、ブラウザ上の処理はJavaScriptで書く。

JavaScript」の中の「Java」の部分は置換したくないですね。このような場合に「前後読み」を使います。今回の例ではその中の「否定先読み」を使います。

まずは前後読みを紹介します。

名前 正規表現 説明
肯定戻り読み (?<=……) 左側にマッチする部分が存在する箇所
否定戻り読み (?<!……) 左側にマッチする部分が存在しない箇所
肯定先読み (?=……) 右側にマッチする部分が存在する箇所
否定先読み (?!……) 右側にマッチする部分が存在しない箇所

戻り読みは先読みと比べるとサポートされているツールや言語が少ないので、これらすべてが使えるとは限りません。

この前後読みですが、「^」や「$」と同じく位置にマッチします。例えば肯定先読みを使ってマッチパターンと置換文字列をそれぞれ「(?=Java)」「」とすると次のようになります。

サーバ側はJavaで書き、ブラウザ上の処理はJavaScriptで書く。
サーバ側はJavaで書き、ブラウザ上の処理はJavaScriptで書く。

マッチしているのは左側に「Java」がある箇所で、「Java」という文字列そのものにはマッチしていません。そのため置換後の文字列に「Java」が残っています。

否定先読みを使えば「Java」を「C#」置換する。ただし「JavaScript」の中の「Java」の部分は置換しない、というのを書くことができます。マッチパターンに「Java(?!Script)」を指定します。これで右側に「Script」が存在しない「Java」がマッチするので、期待通りの動きになります。

サーバ側はJavaで書き、ブラウザ上の処理はJavaScriptで書く。
サーバ側はC#で書き、ブラウザ上の処理はJavaScriptで書く。

今度は肯定先読みの例を見てみましょう。「下さい」の「下」を「くだ」に置換する例を考えます。

下に行って下さい。

単純に「下」を「くだ」に置換してしまうと失敗しますので、右側に「さい」が存在する「下」を置換するようにします。するとマッチパターンと置換文字列はそれぞれ「下(?=さい)」「くだ」になります。

ただ、この場合はマッチパターンと置換文字列をそれぞれ「下さい」「ください」としても同じ結果になります。好きな方を使えば良いと思います。

もう一つ例を見てみましょう。「京都」を「奈良」に変えたいとします。マッチパターンを「京都」にすると次のところがマッチします。

集合場所:東京都新宿区
行き先は京都

「東京都」の中の「京都」にマッチしてしまって上手く行きません。この場合は否定戻り読みを使って「(?<!)京都」とします。これで左側に「東」が存在しない「京都」がマッチするので、期待通りの箇所がマッチします。

集合場所:東京都新宿区
行き先は京都

前後読みの威力が分かったでしょうか。それでは練習しましょう。

各行の「携帯」を「ケータイ」に置換して下さい。ただし「携帯電話」はそのままにして下さい。

入力
出力
マッチパターン 置換文字列 オプション  
携帯(?!電話) ケータイ g

各行の「脆弱」の「脆」を「ぜい」に置換して下さい。

入力
出力
マッチパターン 置換文字列 オプション  
脆(?=弱) ぜい g

終わりに

正規表現を使った置換処理を紹介しました。特にキャプチャを使った置換処理が強力だということが分かっていただけたでしょうか。正規表現はプログラムの中だけではなくテキストエディタ等でも利用できますので、利用できる場面は多いと思います。

ここで紹介したものがすべてではありません。より詳しく知りたい方はネットで調べたり本を読んだりして下さい。本気で勉強するのであれば「詳説 正規表現」がお勧めです。

いろいろな問題にお手軽に挑戦したいという方向けにはlionfanさんが公開している正規表現クイズというサイトがあります。

このページをきっかけに正規表現が使えるようになっていただければ幸いです。最後までご覧いただきありがとうございました。

スポンサーリンク