TL;DR
自分はPerl One Linerを多用するが、使っているところを見られた時にだいたい「意味わからん」とかよく言われるので、なんとなく入門的なものを書こうかと。
- どうしてPerl One Linerを使っているか
- 基本的な使い方
あたりを、ざっくり簡単な書いていきます。
なぜPerl One Linerか
- Linux環境なら、Perlはおよそインストールされているので、どこでも使える
- 環境によって、正規表現の方言に悩まされない
- 過去に実行環境における
sed
の拡張正規表現の方言に悩まされたこととかある
- 過去に実行環境における
- *Nixコマンド的に使えるので、パイプと組み合わせていろいろできて便利
- その気になれば、Perlでできることはほぼ書ける
- OSコマンド実行とか
- ハッシュ使って集計とか
個人的な使い方
-
sed
、awk
の代替として - 少し凝った条件で絞り込みをしたい時には、
grep
の代替として - で
-
find
の結果を整形する -
xargs
をお供に
-
といった感じで。
即興でのログの集計や、find
の結果をPerl One Linerで整形してコマンドを作って実行、などもよくやります。
基本形
次が、基本形です。
$ perl -w[なにか] -e '[One Line Program]' [ファイル名] [ファイル名]...
## ファイルを省略すると、標準入力から読み取る
$ ... | perl -w[なにか] -e '[One Line Program]'
-
-w
… 警告を有効にする(より良いスクリプトを書く時には、基本お供で。use warnings;
) -
-e
… このオプションの後に、実行するスクリプトを指定する
「なにか」の部分には-p
や-n
を指定します。
これは、ざっくり以下のイメージで実行されます。
while (<>) { ## 指定されたファイルの中身や、パイプで渡された標準入力の内容でループ
# ここで、「-e」で指定したスクリプトが実行される
}
-p
と-n
の差は、print
が入るかどうかです。
これを、覚えておきましょう。
ちなみに、-e
で指定するスクリプトは"
(ダブルクォート)ではなく、'
(シングルクォート)で囲うようにしましょう。$
がPerlの前にシェルに評価されるのを抑止します。
※これを理解したうえで、意図的に使うこともたまにありますが…
ユースケースとともに、簡単な使い方を書いていってみましょう。
サンプルに、こんなテキストファイルを用意。
sample.txt
foo,bar
hoge,fuga
shell,shell
こちらを使って、少し例を書いていきます。
sed
の代わりに
個人的には、1番よく使います。
$ perl -wp -e 's/[pattern]/[replace]/' [ファイル名]
$ ... | perl -wp -e 's/[pattern]/[replace]/'
# 「-i」を付けると、対象のファイル自体を書き換える
$ perl -wpi -e 's/[pattern]/[replace]/' [ファイル名]
# 「-i」に引数を追加すると、指定の拡張子でバックアップを作ってくれる
$ perl -wp -i.bk -e 's/[pattern]/[replace]/' [ファイル名]
-p
を付けると、実行時にprint
を含めるようになります。見た目、ほとんどsed
ですね。
例。
# 「foo」を「var」に置換
$ perl -wp -e 's/foo/var/' sample.txt
var,bar
hoge,fuga
shell,shell
# gを使った例
# 「shell」という文字を、すべて「perl」に置換
$ perl -wp -e 's/shell/perl/g' sample.txt
foo,bar
hoge,fuga
perl,perl
# gなしの場合
$ perl -wp -e 's/shell/perl/' sample.txt
foo,bar
hoge,fuga
perl,shell
# ファイルそのものを書き換える場合(バックアップ付き)
$ perl -wp -i.bk -e 's/shell/perl/g' sample.txt
$ diff sample.txt sample.txt.bk
3c3
< perl,perl
---
> shell,shell
awk
の代わりに
awk
やcut
の代わりとして。
$ perl -wnal -e 'print $F[...]' [ファイル名]
$ ... | perl -wnal -e 'print $F[...]'
# splitのパターンを指定するには、「-F」(正規表現可)を使う
$ perl -wnal -F'...' -e 'print $F[...]' [ファイル名]
$ ... | perl -wnal -F'...' -e 'print $F[...]'
-n
はprint
が入らないので、自分で出力する必要があります。splitした結果は@F
配列に入るので、$F[index]
で指定します。
-l
は、print
に改行を追加するオプションです。
例。
$ perl -wnal -F',' -e 'print $F[0]' sample.txt
foo
hoge
shell
grep
の代わりに
print
とif
の組み合わせで、grep
のようにも使えます。
$ perl -wnl -e 'print if ...' [ファイル名]
$ ... | perl -wnl -e 'print if ...'
print
の後ろには、$_
(現在処理している行)が隠れています。
例。
$ perl -wn -e 'print if /foo|shell/' sample.txt
foo,bar
shell,shell
$ perl -wn -e 'print if /foo/ and /bar/' sample.txt
foo,bar
if
文が使えるので、それなりに柔軟な表現が可能です。
grep
よりもキータイプ数が増えてしまうのですが、grep
だとANDを表現するには
$ grep ... | grep ... | grep ...
とパイプを介することになって、やややりづらいのと、数が増えてくるとプロセス数のオーバーヘッドも大きくなるのでPerl One Linerで書いた方が高速になります。
もちろん、「そんな複雑な条件をOne Linerで書くな」というのはもっともですが…。
ひとこと
このあたりのオプション(-p
、-n
、-a
、-l
)とprint
の組み合わせで書いていくのが、Perl One Linerの基本だと思います。
ファイルを直接読むだけではなくて、標準入力からも読み込めるので、あるコマンドの結果を整形するなどもよくやります。
Perl自体を知っていると、;
で区切って複数の行を書いたり、変数を宣言できたりしていろいろ広がりますが、ほどほどに。
知っておくと便利なPerlの変数
他にもいろいろありますが、まずはこのくらい。
-
$_
… 現在処理している行の内容 -
$.
… 現在処理している行番号
少し応用
たとえば、以下のようなアクセスログがあるとしましょう。
108.81.70.158 - - [16/Jun/2015:13:59:34 +0000] "GET /item/electronics/3717 HTTP/1.1" 200 86 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
176.33.96.225 - - [16/Jun/2015:13:59:35 +0000] "GET /category/cameras HTTP/1.1" 200 88 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
32.129.29.109 - - [16/Jun/2015:13:59:35 +0000] "GET /item/toys/2278 HTTP/1.1" 200 129 "/search/?c=Toys" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
192.75.60.93 - - [16/Jun/2015:13:59:35 +0000] "POST /search/?c=Giftcards+Electronics HTTP/1.1" 200 127 "/category/giftcards" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
72.42.173.76 - - [16/Jun/2015:14:01:35 +0000] "GET /category/software HTTP/1.1" 200 66 "-" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"
168.180.150.153 - - [16/Jun/2015:14:01:35 +0000] "GET /category/books HTTP/1.1" 200 107 "/item/software/3528" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
172.171.186.182 - - [16/Jun/2015:14:15:35 +0000] "POST /search/?c=Software+Networking HTTP/1.1" 200 80 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
212.147.150.123 - - [16/Jun/2015:14:15:35 +0000] "GET /category/electronics HTTP/1.1" 200 49 "/category/books?from=20" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
140.210.67.42 - - [16/Jun/2015:15:00:35 +0000] "GET /item/electronics/1466 HTTP/1.1" 200 80 "/category/software" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"
104.33.70.151 - - [16/Jun/2015:15:00:35 +0000] "GET /category/garden?from=10 HTTP/1.1" 200 89 "/item/garden/2967" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
152.90.29.208 - - [16/Jun/2015:15:30:35 +0000] "GET /category/electronics HTTP/1.1" 200 41 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"
これは、こちらに記載されていたものを、少しいじったものです。
ここから、分あたりのアクセス数を出力してみます。
まず、アクセスログの時間の部分から、分までを抜き出します。
$ perl -wp -e 's/.+ \[([^:]+:\d\d:\d\d):\d\d \+\d+\] .+/$1/g' accesslog
16/Jun/2015:13:59
16/Jun/2015:13:59
16/Jun/2015:13:59
16/Jun/2015:13:59
16/Jun/2015:14:01
16/Jun/2015:14:01
16/Jun/2015:14:15
16/Jun/2015:14:15
16/Jun/2015:15:00
16/Jun/2015:15:00
16/Jun/2015:15:30
あとは、sort
とuniq
コマンドを組み合わせれば、あっさり出せたりします。
$ perl -wp -e 's/.+ \[([^:]+:\d\d:\d\d):\d\d \+\d+\] .+/$1/g' accesslog | sort | uniq -c
4 16/Jun/2015:13:59
2 16/Jun/2015:14:01
2 16/Jun/2015:14:15
2 16/Jun/2015:15:00
1 16/Jun/2015:15:30
print
する範囲を変えれば集計範囲が変わるので、例えば時間とパスを出力して、時間別のURLパス単位に集計することも可能です。
自分はたいてい、他のコマンドと組み合わせて使うことが多いのですが参考までに。
※とはいえ、この例だとsed
でも十分ですね。