作者: Kazuhiro Yoshida
日時: 2002/4/29(22:49)
もりきゅうです。

パターンマッチ(Pattern Matching)を使うと、ファイルから興味のある部
分だけ抜き出すことができます。パターンマッチの話題の中心は正規表現
ですが、正規表現だけではパターンマッチ処理は書けません。繰り返し・
条件分岐・特殊変数など、さまざまな文法要素が合わさってひとつのプロ
グラムになります。これらはどれも欠かすことのできない重要な要素なの
です。

■ パターンマッチ

TSruby を含む行だけ出力するために、条件を書きます。

while line = gets
  print line if /TSruby/ =~ line
end

ここで if は条件分岐です。

  何か行う if 条件

ここでの条件は

  /TSruby/ =~ line

これがパターンマッチです。// に挟まれた TSruby がパターンで、line
がマッチ対象となる文字列です。
パターンは正規表現(Regular Expression)で記述します。
TSruby は単なる文字列ですが、これも正規表現のひとつの形です。T の
次に S がきて r がきて u がきて b がきて y がくるパターンを表現し
ています。

■ 正規表現

正規表現は奥深い世界です。詳しくはドキュメント[*1]を参照していた
だくことにして、よく使う例をいくつか挙げます。

/\bTSruby\b/
  \b は単語境界
  HITSruby や TSrubyist にはマッチしない

/^TSruby/
  ^ は行頭
  TSruby で始まる行にマッチする

/TSruby$/
  $ は行末
  TSruby で終わる行にマッチする

/^$/
  空行にマッチする

/./
  . は一文字にマッチする[*2]

/(TS)?ruby/
  ? は 0回または 1回
  TSruby と ruby にマッチする

/TS(abc|free)/
  | は「または(or)」
  TSabc と TSfree にマッチする

/[a-zA-Z]+/
  [] は文字クラス(文字をまとめる)
  文字クラス中の a-z は a から z までの文字
  アルファベットにマッチする

/\b[A-Z]\w*/
  \w は英数字 [a-zA-Z0-9_] と同じ[*2]
  * は 0回以上
  Rubyの定数名にマッチする

このように、正規表現はそれ自体が言語です。Rubyの中に正規表現を埋め
込めるわけですね。

*1
  http://www.ruby-lang.org/ja/doc.html

*2
  . や \w は文字コードを指定すると漢字にもマッチします。文字コード
については、後日「日本語の扱い」としてまとめて説明したいと思います。

■ ほかの書き方

文字列とパターンを逆に書くこともできます。

  print if line =~ /TSruby/

隠れた変数 $_ を利用すれば

while gets
  print if ~ /TSruby/  # あるいは print if /TSruby/
end

と楽に書くことができます。でも $_ は見えないので説明しにくいです。

■ 条件の書き方

if ... end を使ってもいいですし[*1]

  if /TSruby/ =~ line
    print line
  end

論理演算子 and を使ってもいいです。

  /TSruby/ =~ line and print line

*1
  Ruby の if ... end は文ではなく式です。だから値を返せます。詳し
くは今後の講座で。

■ 正規表現のオプション

TSruby だけでなく tsRuby にもマッチしたい(大文字小文字を区別しな
い)ときには i オプション(IGNORECASE)を付けます。

  /TSruby/i =~ line

m オプション(MULTILINE)を付けると . は改行にもマッチします。

  /./m =~ line

さて、プログラム外部からパターンを指定できるようにしてみましょう。

--^ grep0.rb
keyword = ARGV.shift
while line = gets
  print line if /#{keyword}/o =~ line
end
--$

$ ruby grep0.rb TSruby rec1.txt rec2.txt

正規表現に変数(だけでなく式全般ですが)を埋め込むとき、内容が変わ
らないのなら、o オプションを使うと一度だけ式展開されるので速度を稼
げます。

しかし、よりRuby的な解決方法があります。

■ 正規表現オブジェクト

--^ grep1.rb
keyword = ARGV.shift
keyword_re = Regexp.compile(keyword)
while line = gets
  print line if keyword_re =~ line
end
--$

文字列から正規表現を作り出す Regexp.compile を使ってみました。
i オプションはどうやって指定するのでしょう? ドキュメントで調べて
みましょう。

keyword_re = Regexp.compile(keyword, Regexp::IGNORECASE)

■ ARGV と ARGF

ARGV はプログラムに渡された引数を保持する配列の定数名です。
shift は配列の先頭の要素を取って返します。つまり最初の引数を返しま
す。ここで

  keyword = ARGV.shift

のかわりに

  keyword = ARGV[0]

と書くとうまくいきません。

grep0.rb:2:in `gets': No such file or directory - "TSruby" (Errno:
:ENOENT)

ARGV[0] は 0番目の引数、つまり最初の引数です。Rubyの配列は 0番から
数えます。返すものは shift の場合と同じだから keyword は問題ないん
です。ではどこがまずいのか? それは gets です。いままで秘密にしてい
ましたが gets の正体は ARGF.gets 。ARGV の要素をファイル名とみなし
て、それらを連結したひとつの仮想ファイルを ARGF は表します。

ARGV[0] は ARGV.shift と違って配列自体は変わりません。だから
TSruby というファイルを探しにいってしまったのです。

----
YOSHIDA Kazuhiro  moriq@...  http://www.moriq.com/