[技術継承の現場] 範囲演算子について丁寧に説明してみる
弊社では社内の若手技術者向けにLinuxやPerlなどの講習会を行っています。講習会というとモノモノしいものを想像する方もいらっしゃるかもしれませんが、どちらかといえば和気あいあいと先輩が後輩に対して教える、いわば勉強会に近いスタイルです。
その講習の内容をきっかけに、社内ブログ等で話題が発展することもあります。このカテゴリではそんな話題をピックアップしてお届けしようと思います。ベテランプログラマには既知の初歩的な内容かもしれませんが、同様に疑問に思っていたひともいるかもしれませんし、また社内に対しては講習の補足にもなりますしなによりネタの再利用もできますし大変リーズナブルな企画ですね。
さて、まず最初はPerlの範囲演算子です。「..」というやつです。通称フリップフロップ演算子。これ、らくだ本ではやや込み入った表現になっているせいもあるのか、いま一つピンと来ていないひともいるようです。
日本のPerl界隈では2年前くらいにnaoyaさんや小飼弾さん、結城浩さんたちが話題にしていましたね。
http://naoya.g.hatena.ne.jp/naoya/20061222/1166754815
http://blog.livedoor.jp/dankogai/archives/50720796.html
http://d.hatena.ne.jp/hyuki/20061222#flip
けどこんなんめったに使わないな。。 http://b.hatena.ne.jp/entry/3540130
…えー、しょっぱなから出鼻をくじかれましたが、あ、上記の方々はPerl関係では知っていて当然の方々なので、知らない初学者の方は上記リンク先をチェックされますように。miyagawaさんはにぽたん研究所のこの記事によると(ほぼ)世界一CPANモジュールを登録していたりする方です。miyagawa@cpan.orgなどを見たほうが早いかな。すでに、あるいはこれからお世話になるモジュールがいくつもあると思います。
ああ、ついでながら申し上げておくと「日本の Perl ユーザのためのハブサイト」http://perl-users.jp/というのも最近はありますので各自片っぱしからRSSリーダーに突っ込んでおくように。
えー、業務連絡が長くなりましたが、そうです範囲演算子を改めて紹介するのでした。
まずリストコンテキストのときは、連番にしたリストができる。これはよく使いますね。
for (1 .. 1000) {print;}
みたいなやつです。perl -e ‘$\=”\n”;print for (”aa”..”zz”);’ なんてこともできる。
問題はスカラーコンテキストのときなんですが、これは(やや曲った学習方法ではあるけれども)結果から動作を覚えたほうが早い。
たとえば、あるファイルの20行目~30行目まで抜き出すときは、以下のように書けます。
$ perl -ne ‘print if (20 .. 30);’ file
省略せずに書くと ‘print if ($. == 20 .. $. == 30);’ですが数字だけを条件にすると行番号と比較する、という特別ルールがあります。
あ、perl -ne を知らない方もいらっしゃいますかね。引数のファイルを開けて1行ずつ指定された文を実行するオプションです。つまり省略せずに上の例を書くと、
$ cat a.pl while (<argv>) { if ($. == 20 .. $. == 30) { print; } }のようなスクリプトを書いておいて
$ perl a.pl fileとやっているのとだいたい同じです。
範囲演算子に戻りましょう。正規表現を使った例。
$ cat a.html <html> <head> <title> こんにちはこんにちは! </title> <body> .... </html>
というファイルからタイトルを抜き出そうとしたときは、
$ perl -ne 'print if (/<title>/.. /<\/title>/);' a.html <title> こんにちはこんにちは! </title>
と書けるような演算子なのです。まさに範囲ですね。ループの中に置いてやると「開始条件 .. 終了条件」の範囲に合致したものを判定してくれる。
開始条件がfalse | -> false | |
開始条件がtrue | 終了条件false | -> true |
終了条件false | -> true | |
終了条件false | -> true | |
………. | ||
終了条件true | -> true | |
開始条件がfalse | -> false |
という流れになります。演算子の中で直前はどうだったか覚えているんですね。それで次に評価されたときの挙動を変えている。あれです、フラグなんかを使って
my $in_tag = 0; while (<>) { if (!$in_tag && hogehoge) { # なにかの処理 $in_tag = 1; } ............... }
みたいに書くような状況があったとして、フリップフロップ演算子はこのフラグを内部に抱えてくれているようなものってわけですね。
ここで、開始条件と終了条件が同時にtrueになった場合どうなるのか。たとえば上の例でファイルが
<title>こんにちはこんにちは!</title> ....
だった場合。これは意図どおりタイトルの行だけがtrueとなります。
開始条件がtrue 終了条件true -> true
となり、次の行からはfalseとなるわけですね。
ところが、開始条件と終了条件が同時にtrueになったときにはそこで終了されたら困るようなときというのがある。そんな場合には、「…」を使います。
たとえば、開始マークと終了マークに挟まっているのではなくて、ただ切れ目だけがわかるようなファイル。
<hr> こういうの好きだなシンプルで ソースの味って男のコだよな。 <hr> <hr> モノを食べる時はね、誰にも邪魔されず自由でなんというか救われてなきゃあダメなんだ 独りで静かで豊かで・・・ <hr> <hr> この煮込み雑炊をひとつください。 <hr>
かなり無理やり作った例という気が我ながらしますが、まあ強いて言えば掲示板のログなどにありそうな感じですね。
ここから<hr>に囲まれたパートを取り出すような場合
$ perl -ne 'print if (/<hr> .. /<hr>/);' bbs_log
ではうまくいきません。<hr>の1行で開始条件も終了条件も満たしてしまうので、その行だけを返して終わってしまう。
ここで、
$ perl -ne 'print if (/<hr>/ ... /<hr>/);' bbs_log
と書けば、1行目の時点で終了条件はチェックせず次の行へ、となります。
といったわけでつらつらと説明してきましたが、確かにmiyagawaさんのおっしゃる通り本格的なコードではあまり使わないですけど、結構便利ですよ。最後に実際に私がときどき使うワンライナーを。今朝9時から12時までのアクセスログを抜き出そうというとき。
$ perl -ne 'print if (/09:00:00/../12:00:00/);' access_log
「フリップフロップ演算子…そういうのもあるのか!」と皆様の日々の暮らしに小さな驚きをもたらしたなら幸いです。