もりきゅうです。
人の書いたRubyプログラムを読めるようになったら一人前です。その実装
に至った過程を想像してみましょう。
■ Rubyカッター
応用として、本講座に出てくるRubyコードを切り取るプログラムを書いて
みましょう。
まず完成品を見てもらいます(もちろん、ほかの実装も考えられます)。
--^ cut0.rb
file = STDOUT
while line = gets
case line
when /^--\^ +([\w.+-]+)/
path = $1
file = open(path, 'w')
next
when /--\$$/
file.print $`
file = STDOUT
next
end
file.print line
end
--$
$ ruby cut0.rb rec1.txt rec2.txt
うわ。なんじゃこりゃ。と思われた方は次のように分けてみましょう。
file = STDOUT
while line = gets
file.print line
end
case line
when /^--\^ +([\w.+-]+)/
path = $1
file = open(path, 'w')
next
when /--\$$/
file.print $`
file = STDOUT
next
end
上の断片はフィルタの基本形ですね。STDOUTは標準出力(Standard Output)
です。正確に言うと標準出力を示すFileオブジェクトを格納している定数
です。
下の断片はパターンマッチでマークのある行を調べて、fileを切り替えて
います。case when は見慣れないですね。まだ説明していませんでした。
まず上の断片に注目します。
file という変数がずっと STDOUT を指したまま変わらないとすれば
while line = gets
STDOUT.print line
end
ということになります。これは標準出力に line を出力します。[*1]
下の断片で
file = open(path, 'w')
file = STDOUT
のように file への代入が行われているので、file はいつも STDOUT と
いうわけではないようです。出力先を切り替えるために file という変数
を用意したんですね。
次に下の断片に注目します。
when の中を省略すると次のようになります。
case line
when /^--\^ +([\w.+-]+)/
#(A)
when /--\$$/
#(B)
end
case when ... end は、if ... end で書くと
if /^--\^ +([\w.+-]+)/ === line
#(A)
elsif /--\$$/ === line
#(B)
end
という意味です。正規表現(Regexp) の場合 === は =~ と同じです。[*2]
/^--\^/
\^ は ^
--^ で始まる行にマッチする
/--\$$/
\$ は $
--$ で終わる行にマッチする
/ +([\w.+-]+)/
1個以上の空白 それに続く 1個以上の [\w.+-] にマッチする
つまり、line が行頭の --^ ファイル名 にマッチしたら (A)、行末の --$
にマッチしたら (B) を処理することになります。
(A)
path = $1
file = open(path, 'w')
next
一行ずつ見ていきましょう。
path = $1
$1 は最後に成功したマッチの1番目の部分文字列です。[*3]
部分文字列とは、正規表現の中で () で囲んだパターンにマッチした部分
の文字列のことです。この例ではファイル名(path)にマッチすることが期
待されます。
file = open(path, 'w')
pathをファイル名として書き込みモード('w') でファイルを開きます。こ
うすると新しいファイルが作られます。
next
while に帰って次の処理に移ります。ちなみに、while を抜けるには
break を使います。ほかの言語を使っている方は continue とか last な
どと書かないようにご注意を。^^;
(B)
file.print $`
file = STDOUT
next
ここも一行ずつ。
file.print $`
$` は最後に成功したマッチの前方の文字列です。[*4] つまりここでは
--$ が(行末にはあるんだけど)行頭にない場合に残りの文字列を file
に出力しています。
file = STDOUT
標準出力に戻します。
next
while に帰って次へ。どうして next が必要なのか? 仮に next がなかっ
たとすると
file.print line
を評価してしまいます。マーク行を出力してしまうんですね。
今日はここまで! 難しかったかな?
*1
STDOUT.print と print は構文上はかなり違うものですが、結果は同じ
です。今は気にしないでおきましょう。
*2
=== は case 用の演算子(メソッド)。各クラスで定義されています。
*3
$1 に対応するオブジェクト指向的な表現は Regexp.last_match[1]
*4
$` は Regexp.last_match.pre_match と同じ
----
YOSHIDA Kazuhiro moriq@... http://www.moriq.com/