ほぼ同じ内容をn回もつログファイルから、最初の1回目だけを出力するPerlのワンライナー

 タイトルの通り、ほぼ同じ内容をn回持つログファイルから最初の1回目を取得して出力するPerlワンライナーを書きました。対象は200個近くあるログファイルになります。

ログファイルのイメージ

 ログファイルはこんな感じです。Oracleのautotraceの結果です*1。このログ自体は、実行計画+Elapsed timeを複数回取得するのが目的です。ほぼ同じ内容としたのは、Elapsed timeは若干ですがズレるためです。

6 rows selected.

Elapsed: 00:00:00.29

Execution Plan
----------------------------------------------------------
Plan hash value: 2988506077
....
------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |    6 |   360 |     6  (17)| 00:00:01 |
|*  1 |  HASH JOIN         |          |    6 |   360 |     6  (17)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| EMPLOYEES|    6 |   204 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| JOBS     |   19 |   494 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
  1 - access("E"."JOB_ID"="J"."JOB_ID")
  2 - filter("E"."SALARY">12000)

Note
-----
  - dynamic sampling used for this statement

Statistics
----------------------------------------------------------
         0  recursive calls
         0  db block gets
        10  consistent gets
         0  physical reads
         0  redo size
       706  bytes sent via SQL*Net to client
       496  bytes received via SQL*Net from client
         2  SQL*Net roundtrips to/from client
         0  sorts (memory)
         0  sorts (disk)
         6  rows processed

 これがn回続きます。取得したいのは、最初の「6 rows selected.」から次の「6 rows selected.」までの間です。

6 rows selected.

(省略)

6 rows selected.

(省略)

6 rows selected.

(省略)

6 rows selected.

(省略)

6 rows selected.

(省略)

使用したPerlワンライナー

 こんなのgrep正規表現で一発や!と色々(10分程度)格闘した結果、うまくいかなかったのでPerlワンライナーで解決しました*2

$perl -ne '$l{$_}++; last if(/^(\d+|no) rows selected\.?$/ && $l{$_}==2); print $_;' hogehoge.log

 あとは標準出力をファイルにリダイレクトしてあげて。

$perl -ne '$l{$_}++; last if(/^(\d+|no) rows selected\.?$/ && $l{$_}==2); print $_;' hogehoge.log > unique_hogehoge.log

ファイルには以下のように1回めだけが出力されます。

6 rows selected.

(省略)

簡単な解説

perl -ne

 -eオプションでPerlワンライナーを利用可能にします。シングルコーテーション内がスクリプトになります。-nオプションで繰り返し=while相当です。つまり、$perl -neで「引数のファイルの内容で繰り返す」を表現しています。

$perl -ne 'print $_' hogehoge.log
# hogehoge.logの内容をすべて出力
$l{$_}++;

 ハッシュ$lにキー$_を設定して、インクリメントしています。$_は組み込み変数で宣言不要で使える変数です。今回の場合はwhileの引数にしているhogehoge.logの1行ずつが格納されています。hogehoge.logの内容が以下のように設定されているとして。

あああああ
いいいいい
ううううう
あああああ
あああああ
いいいいい

 出力回数のカウント+ファイルの内容を出力するワンライナーが簡単に作れたりします。Perlでユニークを作りたい時によく使うテクニックですね。。

perl -ne '$hash{$_}++; print "$hash{$_}: $_"' hogehoge.log

 出力結果は以下のとおり。

1: あああああ
1: いいいいい
1: ううううう
2: あああああ
3: あああああ
2: いいいいい
last if(/^(\d+|no) rows selected\.?$/ && $l{$_}==2);

 if内の処理が1行の場合は処理を先に書いて、処理の後にif文を書く構文が許容されます。本例の場合、ifに該当したらlast(whileを中断する)になります。if文の条件句では、「正規表現で狙った行にマッチする」かつ「ハッシュで2回目の登場」の場合としています。正規表現ブロックでは行が選択できた場合(\d+)と行が選択できない場合(no)を表現しています*3。これにより、最初の「6 rows selected.」から次の「6 rows selected.」まで間はwhile文を繰り返すを実現しています。

print $_

 解説不要、単なるprintですね。

Perlワンライナーについては

 ワンライナーが好きになったのは数年前にこのページを見てからです。UNIXLinuxユーザ必読。

 Perl1行野郎

*1:元ネタはTuning SQL*Plus

*2:grep解決版があれば教えて頂きたい

*3:no rows selectedの場合、末尾に「.」が付かないのはOracleのバグだと思います