Perlワンライナーでテキスト処理

# 想定読者
* Perlをさわっている
* Perlをさわっていなくてもコマンドラインの操作ができる

# ゴール
* Perlワンライナーの基礎的な仕組みを理解して、テキストデータにフィルタをかけれるようになる
* Perlワンライナーを書くことと、Perlスクリプトを書くことの使い分けを考えられるようになる

# 背景
[過去に投稿した記事](http://qiita.com/AKIF/items/67fabf49c3d64274898f)のテーマに、Rubyで簡単なテキスト処理をするCliツールを作成するというものがあったが、
業務ではほぼ同じものをPerlで軽くテストを加えて使用している。
今回はそのような簡単なテキスト処理は、本来であればPerlワンライナーで即席で作れてしまうよ、
ということを同じ処理を題材にすることによって紹介させていただくこととした。

# やりたいことのおさらい
以前に取り上げた処理の内容は、過去の記事も参考にしていただければよいが、
今回の記事のために簡単におさらいする。

下記のようなログファイルへ、以下の3つのフィルタをかけたい。
1. 必要なレコード(μsecオーダ時間のフィールドを先頭にもつ)の、欲しいフィールドのみを出力する(`Length`以降を除く)
2. 特定のフィールド(3番目のID,`1F3`等)の値をもとに出力するレコードを絞り込む
3. 先頭の時間のフィールドの値から、差分時間を算出し、先頭のフィールドとして追加する

```
date Thu Apr 11 04:41:25 pm 2013
base hex  timestamps absolute
internal events logged
// version 8.0.0
Begin Triggerblock Thu Apr 11 04:41:25 pm 2013
   0.000000 Start of measurement
   0.001316 CAN 1 Status:chip status error active
   0.001399 1  1F3             Rx   d 3 00 10 00  Length = 146000 BitCount = 77 ID = 499
   0.002763 1  1E5             Rx   d 8 4C 00 21 10 00 00 00 B9  Length = 228000 BitCount = 118 ID = 485
   0.003009 1  710             Rx   d 8 00 5F 00 00 00 00 13 BE  Length = 238000 BitCount = 123 ID = 1808
   0.003175 1  C7              Rx   d 4 00 38 26 9B  Length = 158000 BitCount = 83 ID = 199
   0.003349 1  1CC             Rx   d 4 00 00 00 00  Length = 165883 BitCount = 87 ID = 460
   0.003586 1  F9              Rx   d 8 00 DA 40 33 D0 63 FF 1C  Length = 228000 BitCount = 118 ID = 249
   0.003738 1  1CF             Rx   d 3 00 00 05  Length = 144000 BitCount = 76 ID = 463
   0.003976 1  711             Rx   d 8 00 23 00 7E FF EB FC 6F  Length = 230000 BitCount = 119 ID = 1809
   0.004148 1  1D0             Rx   d 4 00 00 00 00  Length = 164000 BitCount = 86 ID = 464
   0.004382 1  C1              Rx   d 8 30 14 F6 08 32 B4 F7 70  Length = 226000 BitCount = 117 ID = 193
   0.004615 1  C5              Rx   d 8 31 27 F8 44 32 B0 F8 5C  Length = 224121 BitCount = 116 ID = 197
   0.004825 1  BE              Rx   d 6 00 00 4D 00 00 00  Length = 202242 BitCount = 105 ID = 190
   0.005051 1  D1              Rx   d 7 80 00 BF FE 00 FE 00  Length = 218121 BitCount = 113 ID = 209
   0.005292 1  C9              Rx   d 8 80 2C 5A 60 00 00 18 00  Length = 232242 BitCount = 120 ID = 201
   0.005538 1  1C8             Rx   d 8 80 00 00 00 FF FE 3F FE  Length = 238121 BitCount = 123 ID = 456
   0.005774 1  18E             Rx   d 8 00 00 00 84 78 46 08 45  Length = 228242 BitCount = 118 ID = 398
```

# ということで3つのPerlワンライナーのご紹介+簡単に解説

* まずは使用するPerlワンライナースイッチの解説
    * `-e`
        * スイッチの後に続くstring terminator(' '," ")内に書かれた文字列をPerlスクリプトとして実行する
    * `-a`
        * 渡された入力を1行ごとに特殊変数`@F`へセパレータ(デフォルトは`space`)で分割して代入し、後述の`-n`スイッチを併用しループ処理をする
    * `-n`
        * -aスイッチと併用し`while(<>){'-e スイッチのscript'}`のようなループ処理をする(行演算子`<>`へ入力全体が渡され、@Fへ一行づつ代入されるということ)
    * `-l`
        * 行ごとに`chomp`し、行末の改行コードを削除した上で、`print`をコール時に暗黙的にで改行コードを出力する

1.必要なレコードの、欲しいフィールドのみを出力する

```
cat log.txt | perl -alne 'print join " ", @F[0..$F[5]+5] if $F[0] =~ /^\d+\.\d+/'

0.000000 Start of measurement
0.001316 CAN 1 Status:chip status error
0.001399 1 1F3 Rx d 3 00 10 00
0.002763 1 1E5 Rx d 8 4C 00 21 10 00 00 00 B9
0.003009 1 710 Rx d 8 00 5F 00 00 00 00 13 BE
0.003175 1 C7 Rx d 4 00 38 26 9B
0.003349 1 1CC Rx d 4 00 00 00 00
0.003586 1 F9 Rx d 8 00 DA 40 33 D0 63 FF 1C
0.003738 1 1CF Rx d 3 00 00 05
0.003976 1 711 Rx d 8 00 23 00 7E FF EB FC 6F
0.004148 1 1D0 Rx d 4 00 00 00 00
0.004382 1 C1 Rx d 8 30 14 F6 08 32 B4 F7 70
0.004615 1 C5 Rx d 8 31 27 F8 44 32 B0 F8 5C
0.004825 1 BE Rx d 6 00 00 4D 00 00 00
0.005051 1 D1 Rx d 7 80 00 BF FE 00 FE 00
0.005292 1 C9 Rx d 8 80 2C 5A 60 00 00 18 00
0.005538 1 1C8 Rx d 8 80 00 00 00 FF FE 3F FE
0.005774 1 18E Rx d 8 00 00 00 84 78 46 08 45
```

* 解説
    * `if $F[0] =~ /^\d+\.\d+/`
        * 先頭のフィールド(\$F[0])が一文字以上の数字.一文字以上の数字'にマッチした場合のみ`print`ステートを実行する
    * `print join " ", @F[0..$F[5]+5]`
        * 先頭のフィールド(\$F[0])からデータのフィールド(最大\$F[6]~\$F[13])の長さを、lengthを示すフィールド(\$F[5])の値を元に算出し、標準出力へ出力する

2.特定のフィールドの値をもとに出力するレコードを絞り込む

```
cat log.txt | perl -alne 'print join " ", @F[0..$F[5]+5] if $F[2] =~ /1F3|710|C1|1C8/'

0.001399 1 1F3 Rx d 3 00 10 00
0.003009 1 710 Rx d 8 00 5F 00 00 00 00 13 BE
0.004382 1 C1 Rx d 8 30 14 F6 08 32 B4 F7 70
0.005538 1 1C8 Rx d 8 80 00 00 00 FF FE 3F FE
```

* 解説(上までに解説済のものは省略)
    * `if $F[2] =~ /1F3|710|C1|1C8/`
        * 3番目のレコードのIDを示すフィールド($F[3])が特定の文字列の場合のみ`print`ステートを実行する

3.先頭の時間のフィールドから、差分時間を算出し、先頭のフィールドとして追加する

```
cat log.txt | perl -alne 'unshift @F, sprintf "%.6f", $F[0]-$l; print join " ", @F[0..$F[6]+5] if $F[1] =~ /\d+.\d+/;$l = $F[1]'

0.000000 0.000000 Start of measurement
0.001316 0.001316 CAN 1 Status:chip status
0.000083 0.001399 1 1F3 Rx d 3 00 10
0.001364 0.002763 1 1E5 Rx d 8 4C 00 21 10 00 00 00
0.000246 0.003009 1 710 Rx d 8 00 5F 00 00 00 00 13
0.000166 0.003175 1 C7 Rx d 4 00 38 26
0.000174 0.003349 1 1CC Rx d 4 00 00 00
0.000237 0.003586 1 F9 Rx d 8 00 DA 40 33 D0 63 FF
0.000152 0.003738 1 1CF Rx d 3 00 00
0.000238 0.003976 1 711 Rx d 8 00 23 00 7E FF EB FC
0.000172 0.004148 1 1D0 Rx d 4 00 00 00
0.000234 0.004382 1 C1 Rx d 8 30 14 F6 08 32 B4 F7
0.000233 0.004615 1 C5 Rx d 8 31 27 F8 44 32 B0 F8
0.000210 0.004825 1 BE Rx d 6 00 00 4D 00 00
0.000226 0.005051 1 D1 Rx d 7 80 00 BF FE 00 FE
0.000241 0.005292 1 C9 Rx d 8 80 2C 5A 60 00 00 18
0.000246 0.005538 1 1C8 Rx d 8 80 00 00 00 FF FE 3F
0.000236 0.005774 1 18E Rx d 8 00 00 00 84 78 46 08
```

* 解説(上までに解説済のものは省略)
    * `unshift @F, sprintf "%.6f", $F[0]-$l`
        * 先頭フィールドの現在時間の値から新たに宣言した変数`$l`(初期値は0)を引いた値を、`sprintf`でμsecオーダの桁数へフォーマットし`@F`の先頭へ`unshift`で追加
  * あとはフィールドの添え字のずれを考慮して、上で解説した通りのロジックで標準出力を行う

# まとめ
* スクリプトを書くことに対する、ワンライナーを書くことについて
    * 利点
        * 処理が簡易であればスクリプトを書くよりも作業時間の短縮になる
        * 他の様々なコマンドラインベースの入出力とパイプを利用して連動させられる(ex.vimのウィンドウバッファから入力して書き戻す)
        * なんとなく誰かに自慢したくなれる(簡単に実演できることもあり)
    * 欠点
        * 処理が複雑になってきたり、ステートが増えると処理の取り回しがわるくなり、スクリプトを書いたほうが早い場合となる
        * 可読性はどうしてもスクリプトを書くよりも劣るので、他人に配って変更して使ってもらいにくい
        * 単機能が基本となるので、再利用できて多機能、というものをつくりにくい
    * よって使い分けの境界はこのような判断ステップが良さそうであると考えた
        * ワンライナーで解決できそうな問題は、まず最小単位の試作としてワンライナーを書く
        * 処理が複雑になって、ワンライナーで効率化可能な見込みが少なくなってきた段階で、使い捨てレベルのスクリプトを書く
        * 使い捨てスクリプトの試作と運用の中で、自身や周囲で再利用できる見込みがあれば、リッチなコマンドラインツールへ発展させる