正規表現とは

電話帳や郵便番号簿から目的のデータを探すときはどうするだろう。 おそらく先頭から1、2文字(イニシャル)を利用して、検索ボタンなり索引を使っ て探すだろう。誰が登録されているか覚えていればよいが、未知のものから探す のはたいへんである。また、データの先頭の文字を忘れていたり、 「サトウ」なのか「サイトウ」なのかとか、 「イカラシ」なのか「イガラシ」なのかなど、途中があやふやだったりすると 検索機能では探せない。

なにかを検索するときに、そのデータに含まれている文字列のパターンを 汎用的に指定する方法がある。そのうち、もっとも一般的であるものが 正規表現である。

正規表現の例

たとえば、「サトウ」と「サイトウ」どちらかを探したい場合、 正規表現では次のように検索パターンを書く。

サイ?トウ

こうすると、"サトウ" または "サイトウ" 両方にマッチするようになる。 特別な文字 ? を指定すると、「その直前の文字があってもなくて もよい」という特別な意味になる。また、別の特別な文字 [ ] を 使っても、特別な意味を持った指定ができる。たとえば、

イ[カガ]ラシ

とすると、"イカラシ" でも "イガラシ" でもマッチするようになる。 特別な文字 [ ] の中に何文字か文字を列挙すると、 「その中のどれかの文字が来ればよい」という指定になる。

上記の正規表現の例は特殊な文字の働きを分かりやすくた めにもっとも単純に書いたものであり、実際にRubyプログラムに正規表現を書く ときはそのままでは使えないので注意すること。

egrepコマンドによる正規表現の実験

実際に名簿のようなデータを作って、正規表現で検索する練習をしてみよう。 最初に検索のデータとなるファイルを作っておこう。以下のような名簿データファ イル、meibo.csv を作成しよう。

ナマエ,Name,氏名,居住地
サトウ ムネユキ,SATOH Muneyuki,さとう胸幸,青葉台
ナカマチ タロウ,NAKAMACHI Taro,中町太郎,酒田
イイモリ ハナコ,IIMORI Hanako,飯森花子,飯森山
サイトウ テツヤ,SAITOH Tetsuya,斎藤徹夜,白夜の国
ゲゲノ キタロウ,GEGENO Kitarou,下々野喜太郎,魔界村
ナニワノ タロー,NANIWANO Taro,浪花之太朗,探偵騎士王国
ヒミ ヨンタロウ,HIMI Yontarou,悲見四太郎,甲子園
ヤマタイ ヒミコ,YAMATAI Himiko,邪馬台氷見子,大和
イモリ テツコ,IMORI Tetsuko,井森徹子,井戸

リンクを右クリックし "Save link as" で、~/Ruby に保存する。

これを利用して検索してみる。 まずはローマ字でパターンを指定してみよう。 Unixには正規表現を使ってファイルから特定の行を検索してくれる egrep というコマンドがある(Rubyとは別物)。 egrep コマンドは

% egrep "正規表現パターン" [ファイル群…]

のように起動し、0個以上の複数のファイルから「正規表現パターン」にマッ チする行だけを選んで出力してくれる。ファイルを0個指定すると、 標準入力から読み込んだデータから検索する。

標準入力/標準出力に関してはとても大事な概念なので Unixひとめぐりページを よく読んでおくこと。

SAITOH または SATOH の検索

SAITOH または SATOH のいずれかを含む行を全部選んでみよう。 この場合、大文字の I があってもなくてもよいので、 正規表現パターンは "SAI?TOH" となる。

egrep "SAI?TOH" meibo.csv

正規表現のパターンを "" で括っているのは、 途中にある? 記号をファイル名マッチだと思われないようにするため である。このようにコマンドラインで正規表現パターンを指定するときは、パター ンを"" または '' で必ず括ること。

実行結果は次のようになるだろう。

egrep "SAI?TOH" meibo.csv
サトウ ムネユキ,SATOH Muneyuki,さとう胸幸,青葉台
サイトウ テツヤ,SAITOH Tetsuya,斎藤徹夜,白夜の国

大文字小文字の同一視

もう少し欲張って、楽になるように検索パターンを考えよう。 パターン指定にいちいち大文字を打つのは面倒だし、もしかしたらデータファイ ルの方で Satoh と書いてしまう場合もあるので、大文字でも小文字でもどちら でもマッチするようにしてみよう。検索のときに大文字小文字を区別しないため には egrep コマンドに -i オプションを指定すれば よい。

egrep -i "sai?toh" meibo.csv
サトウ ムネユキ,SATOH Muneyuki,さとう胸幸,青葉台
サイトウ テツヤ,SAITOH Tetsuya,斎藤徹夜,白夜の国

日本語(カタカナ)での検索

カタカナで検索してみよう。「サトウ」でも「サイトウ」でもよいように、 「サイ?トウ」というパターンで検索してみよう。

Terminalでの日本語入力は Shift-SPC でON/OFFする。 カタカナへの変換は C-k で行なう。

egrep -i "サイ?トウ" meibo.csv
サトウ ムネユキ,SATOH Muneyuki,さとう胸幸,青葉台
サイトウ テツヤ,SAITOH Tetsuya,斎藤徹夜,白夜の国

その他の検索の練習

続いて、以下の条件にマッチする人を検索してみよう(パターンを "" で括るのを忘れずに)。

上記の検索実験は期待した結果と少し違うものが出てくることを 実感するのが目的である。

Rubyで使う正規表現

egrepと違い、Rubyでは正規表現を / / で括って指定する。 たとえば、egrep の例で指定した正規表現 "sai?toh" はRubyでは、

/sai?toh/

と表記する。これに注意して、 「指定したファイルから sai?toh というパターンを 検索する」 Rubyプログラム saito.rb を作成すると次のようになる(後述の説明のため途中から行番号を付した)。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'csv'

1: meibo = CSV.read(ARGV[0], headers:true)
2: meibo.each do |row|
3:   if /sai?toh/i =~ row["Name"]
4:     print row.to_s
5:   end
6: end

まずは、実際に実行してみよう。egrep コマンドと同様 検索したいファイルを引数として与えよう(検索パターンはRubyプログラム内に 記述してあるのでコマンドラインからは指定しなくてよい)。

chmod +x saito.rb
./saito.rb meibo.csv
サトウ ムネユキ,SATOH Muneyuki,さとう胸幸,青葉台
サイトウ テツヤ,SAITOH Tetsuya,斎藤徹夜,白夜の国

プログラム(本体部分)の内容を一行ずつ見ていこう。

  1. meibo = CSV.read(ARGV[0], headers:true)

    コマンドラインで指定した meibo.csvARGV[0]に代入され、それがヘッダ付き CSV として読み込まれる(headers:true)。 データ全体がmeibo変数に代入される。

  2. meibo.each do |row|

    meibo 変数にあるレコードを1つ1つ取り出して繰り返す。 1レコード(1行にある全フィールドの集合)はブロック変数 row に代入されてレコード回数分だけ繰り返される。 つまり、一回目には row 変数に以下の値が入る(CSVrow形式)。

    => #<CSV::Row "ナマエ":"サトウ ムネユキ" "Name":"SATOH Muneyuki" "氏名":"さとう胸幸" "居住地":"青葉台">

  3. if /sai?toh/i =~ row["Name"]

    このifで、各レコード中のNameフィールドを取り出し、 それが正規表現にマッチしているかの判定を行なう。 「正規表現にマッチしているか?」を意味する条件式は

    正規表現 =~ 文字列

    のように書く。マッチしている場合は、実際にマッチした部分が 文字列 の何文字目かを表す整数を返す。マッチしない場合 はnilを返し、条件不成立となる。

    /sai?toh/i は、Rubyによる正規表現の 指定である。後の / の直後にある i は 「大文字小文字を区別しない」という意味を付加する働きを持つ。つまり、 egrep コマンドの -i オプションは、Rubyで は、正規表現の直後に i を付けることに相当する。

    正規表現の後ろの =~ は、「正規表現がマッチすれば…」 という比較演算子である。通常1行ずつ入力した文字列をこの右辺に書く。 ちなみに「マッチしなければ」を意味する演算子は !~ である。

    =~ の右辺に指定した「文字列」が正規表現の比較対象 となる文字列である。

  4. print row.to_s

    row 変数には複数のフィールドが入っている。 それらを元の(CSVファイルに書き込むような)文字列に to_s メソッドで変換し、printで出力する。 もちろん、一つ上の行にif があるので、正規表現にマッチした場合だけ該当行が出力される。

  5. end

    ifに対応するend

  6. end

    eachに対応するend

問題

上記 saito.rb を元に以下のように改良したものを作成せよ。

  1. 検索パターンを「サイ?トウ」に変えて、検索対象フィールドを ナマエ(第1項目)として検索した結果を示す saito-katakana.rb
  2. 検索対象を1レコード全体(row.to_s)として検索する saito-line.rb

解答例(解いてから見ること): saito-katakana.rb, saito-line.rb


本日の目次