理系学生日記

おまえはいつまで学生気分なのか

Perl における flip-flop 演算子の使い道について

flip-flop 演算子とは、スカラーコンテキストで使用される範囲演算子(Range Operator) のことです。一般にはドット (.) 2 個で構成される演算子として認識されています。
この演算子は 2 つのオペランドを取ります。下記の例では、3 と 5 をオペランドに取っています。

perl -nle 'print if 3 .. 5' bigfile.txt

上記は、bigfile.txt を 1 行ずつ読みこんでいくのですが、bigfile.txt が 1 ~ 2 行目のうちは if 文は false を返します。3 行目になると if 文は true になり、if 文が true として評価されるこの状態は 5 行目まで続きます。そして 6 行目に達して以後は、ずっと false として評価されます。
ここではわざと「状態」という言葉を使いましたが、この演算子は「状態」を持ちます。つまり、左オペランドが true になった後は、右オペランドが true になるまでその状態(全体として true として評価される状態)を保ち続けます。flip-flop 演算子という名前の由来は、IC 回路を多少でもかじった人なら理解できるのではないでしょうか。


この flip-flop 演算子、何に使うんだよという話もときどき聞こえてきますが、ぼくは主にログファイルの解析に使ってたりします。

API の I/F を叩く or 叩かれるシステムのログファイルに多いですが、そのリクエストやレスポンスは 1 行ではなく、複数行に出力されることがあります。Java の toString でオブジェクトをダンプするときや JSON でオブジェクトをシリアライズするときのことを考えると想像しやすいかもしれません。

簡単な例

以下は非常に汚い例ですが、begin から end の間に挟まれているアルファベット列がリクエスト内容だと仮定しましょう。

aaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbb
begin
cccccccccccccccccccccccccccc # <= ここからが 1 つのリクエスト内容
dddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffff # <= ここまで
end
gggggggggggggggggggggggggggg
hhhhhhhhhhhhhhhhhhhhhhhhhhhh
iiiiiiiiiiiiiiiiiiiiiiiiiiii

このような「多数の行で 1 つの意味を持つログ」というのは簡易的な解析においてわりと厄介です。grep に代表される、行単位の動作を基本とするツールを用いた場合、その "1 つの意味を持つログ" の抽出は一筋縄ではいきません。その行数がターミナルに入り切らないほどの数になると、リクエストやレスポンスの全容を知ろうとするだけでも割と骨が折れます*1

しかし、flip-flop 演算子を使えば、このようなケースにおいても、one-liner で抽出が可能になります。

$ perl -nle 'print $_ if /begin/ .. /end/ ' request.log
begin
cccccccccccccccccccccccccccc
dddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffff
end

ちょっと込み行った例

では次の例はどうでしょう。

aaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbb
request: 1
cccccccccccccccccccccccccccc  # <= ここからが request no.1 の内容
dddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffff  # <= ここまで
request: 2
gggggggggggggggggggggggggggg
hhhhhhhhhhhhhhhhhhhhhhhhhhhh
iiiiiiiiiiiiiiiiiiiiiiiiiiii

これは先程の方法では解決できません。

$ perl -nle 'print $_ if /request/ .. /request/ ' resquest.log
request: 1
request: 2 # リクエストの内容が抽出されていない

これはなぜかというと、「request: 1」の行を評価しているとき、flip-flop 演算子の左右両方のオペランドが true を返却してしまうからです。これを避けるためには、もう一つの flip-flop 演算子である "..." (ドット 3 つ)を使用します。"..." を使用すると、左オペランドで評価された行は、右オペランドの評価には使用されません。
従って、これを用いると以下のように、正しく抽出することが可能になります。

$ perl -nle 'print $_ if /request/ ... /request/ ' request.og
request: 1
cccccccccccccccccccccccccccc
dddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffff
request: 2

使い道がなさそうに見えても、わりかし用途というのはあるものですね。

*1:awk 使うと…というのはありますけど