Perl大好き阿部です。
YAPC::Fukuoka 2025でトークできることになりました。 内容の概要はこちらをご覧ください!
その内容がPerlにまったく関係ないので、Perl好きの断片を語るための記事です。
はじめに
タイトルからもわかる通りPerlのワンライナーを紹介します。
私はPerlのワンライナーを普段からよく使っています。
(Perlでだいたい達成できるため、いまだにsedとawkの文法を覚えていません ;-()
ということで、本記事ではPerlがあれば何でもできると題しまして、grep、sed、awkと同様のことをPerlのワンライナーで実現する方法を紹介します。
注意: ワンライナーはいろいろ省略した状態なので、それだけを見ても何が起きているのかわかりにくいです。 そこで「省略しないPerlのコードだとこういうイメージ」というのを合わせて紹介していきます。 わかりやすさを優先するので、正確ではないところもあると思いますがご容赦ください。
前提
Perlの文法をわかっていたほうが理解しやすいので、このあとの説明に必要なところだけを簡単に紹介します。 (細かい話はしません。)
標準入力の読み込みと$_
Perlでは次のように書くと、標準入力を1行ずつ処理できます。
foreach my $line (<STDIN>) {
print "$line";
}
$line に標準入力が1行ずつ入ります。
example.pl という名前で保存して実行したイメージは次の通りです。
$ ls / | perl ./example.pl
bin@
boot/
cdrom/
dev/
etc/
home/
lib@
lib32@
lib64@
libx32@
lost+found/
media/
mnt/
opt/
proc/
root/
run/
sbin@
snap/
srv/
swapfile
sys/
tmp/
usr/
var/
そしてここからが本番です。Perlといえばの$_です!
先ほどのコードは次のようにも書けます。
foreach (<STDIN>) {
print;
}
これも保存して実行すると先ほどと同じ結果が得られます。
いろいろ省略されていて何が起きているのかわからないですね!
Perlは変数を省略すると、自動的に$_に値が設定され、またそれを参照するという便利?機能があります。
$_ を明示的に書くと次の通りです。
foreach $_ (<STDIN>) {
print $_;
}
先ほどの$lineを省略すると$_に値が自動的に設定され、printの引数を省略すると自動で$_が補完されて実行される感じです。
このPerlの基本を踏まえまして、これから説明していきます。
grepをPerlで代替する
先に結論を書くと次のようになります。perl -ne 'if (/lib/) { print; }'の部分がPerlのワンライナーです。
これはls / | grep lib と同等のことをしています。
$ ls / | perl -ne 'if (/lib/) { print; }'
lib@
lib32@
lib64@
libx32@
(オプション(-ne)の解説はもう少し後でします。)
このワンライナーを何も省略しないで書くと、次のようなPerlのコードになります。
foreach my $line (<STDIN>) {
if ($line =~ /lib/) {
print $line;
}
}
$line =~ /lib/にて正規表現でチェックして、マッチしていたら$lineを表示しています。
これを先ほどの自動的に代入・参照される$_を利用して書くと次のようになります。
明示的に$_を書くと
foreach $_ (<STDIN>) {
if ($_ =~ /lib/) {
print $_;
}
}
となり、$_を省略すると
foreach (<STDIN>) {
if (/lib/) {
print;
}
}
となります。だいぶワンライナーに近づいてきました。
ここでオプションの説明です。
-neは-nと-eの2つのオプションを指定しているという意味です。
-eは-e <Perlのコード>のようにコードを指定するときに使います。
-nは-eで指定する<Perlのコード>をforeach (<STDIN>)の中で実行してくれるオプションです。
イメージとしては次のような感じです。
foreach (<STDIN>) { <- `-n` がやってくれる
`-e` で指定した<Perlのコード>が挿入される
} <- `-n` がやってくれる
$_を活用したり-nが勝手にやってくれる部分を省略すると、最初に紹介した次のワンライナーの出来上がりです。
$ ls / | perl -ne 'if (/lib/) { print; }'
sed(置換)をPerlで代替する
次のワンライナーで置換できます。
perl -pe 's/lib/HOGE/g'の部分です。
$ ls / | grep lib | perl -pe 's/lib/HOGE/g'
HOGE@
HOGE32@
HOGE64@
HOGEx32@
libがHOGEに置換されています。
何も省略しないでPerlで書くと次のようになります。
$line =~ s/lib/HOGE/g;で$line変数の中身の文字列の"lib"を"HOGE"に置換しています。
foreach my $line (<STDIN>) {
$line =~ s/lib/HOGE/g;
print $line;
}
$_を活用すると次の通りです。
foreach (<STDIN>) {
s/lib/HOGE/g;
print;
}
-eオプションはさっきの通りで、-pは-n + print;をしてくれるオプションです。
foreach (<STDIN>) { <- `-p` がやってくれる
`-e` で指定した<Perlのコード>が挿入される
print; <- `-p` がやってくれる
} <- `-p` がやってくれる
ということで、ワンライナーの中にprintがなくても結果が表示されたというわけです。
awk(文字で区切る)をPerlで代替する
lsの結果で例を示しにくくなったので、次の結果を加工する例を示します。
$ echo -e '1 hoge\n2 fuga\n3 piyo'
1 hoge
2 fuga
3 piyo
スペースで区切って2つ目の要素(hogeの列)のみを表示する例です。
perl -ane 'print "$F[1]\n"'で分割して2つ目の要素を表示しています。
$ echo -e '1 hoge\n2 fuga\n3 piyo' | perl -ane 'print "$F[1]\n"'
hoge
fuga
piyo
これを何も省略しないPerlのコードにすると次のようなイメージです。
foreach my $line (<STDIN>) {
my @F = split(/\s/, $line);
print "$F[1]\n";
}
Perlでは@を使って配列を表します。@Fは配列ということです。
ただし各要素にアクセスするときは$F[1]と$を使います。
この辺はスカラーがどうとか小難しい話があるのですが、長くなるので割愛します。
で、これを$_を活用して省略すると次のようになります。
foreach (<STDIN>) {
my @F = split(/\s/);
print "$F[1]\n";
}
splitの引数も省略できるんですね!
そしてオプションの解説です。-aを指定するとmy @F = split(/\s/);をやってくれます。
-aが@Fを作ってくれるので、突然登場する$F[1]が使えたというわけです。
foreach (<STDIN>) { <- `-n` がやってくれる
my @F = split(/\s/); <- `-a` がやってくれる
`-e` で指定した<Perlのコード>が挿入される
} <- `-n` がやってくれる
-aが挿入するsplitの/\s/からもわかる通りタブ(\t)区切りの場合も同じワンライナーで動作します。
$ echo -e '1\thoge\n2\tfuga\n3\tpiyo' | perl -ane 'print "$F[1]\n"'
hoge
fuga
piyo
カンマ区切りの場合も同じように処理できます。-Fオプションで区切り文字を指定できます。
echo -e '1,hoge\n2,fuga\n3,piyo' | perl -F, -ane 'print "$F[1]\n"'
hoge
fuga
piyo
-F,の効果で,で分割して同じように動きましたが、改行が増えました。
これは区切り文字による違いです。/\s/で分割すると改行でも区切られるので改行が表示されませんでした。
ということで、区切り文字によって改行を意識しなければいけません。これは面倒です。そんなときに便利なのが-lオプションです。
早速具体例で確認です。
$ echo -e '1,hoge\n2,fuga\n3,piyo' | perl -F, -lane 'print $F[1]'
hoge
fuga
piyo
-lを追加したら改行が消えました。便利!
-Fを指定せず\sで区切る場合も次のようになります。
$ echo -e '1 hoge\n2 fuga\n3 piyo' | perl -lane 'print $F[1]'
hoge
fuga
piyo
-Fの違いのみで、区切り文字によらず、同じワンライナーで文末の改行をいい感じに処理できました。
さらに注目してほしいのは-lがないときはprint "$F[1]\n"と最後に\nを付けていましたが、-lを付けるとprint $F[1]と\nが省略できることです。
これは-lを付けると次に示すコードイメージのように-lが頑張ってくれるからです。
(慣れてきたと思うので$_を活用する省略バージョンのみ掲載します。)
foreach (<STDIN>) { <- `-n` がやってくれる
chomp; <- `-l` がやってくれる
my @F = split(/\s/); <- `-a` がやってくれる
`-e` で指定した<Perlのコード>が挿入される
print "\n"; <- `-l` がやってくれる
} <- `-n` がやってくれる
-lを付けるとchompで改行を消して、最後に\nもprintしてくれます。
実践例
説明するためのシンプルな例でPerlのワンライナーを説明しましたが、普段はこれをどのように使っているのか実践例も紹介します。
いまどきの環境だとFluentd(弊社のサポートサービスもあるよ!)などでログを収集して集計していたりするので、サーバにログインしてログを直接集計するということはあまりなさそうですが、nginxのアクセスログからアクセス元のIPを集計する例です。
次のようなログを例に使います。
10.96.245.1 - - [10/Oct/2025:01:41:02 +0000] "GET /redmine/ HTTP/1.1" 200 2354 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"
10.96.245.1 - - [10/Oct/2025:01:41:02 +0000] "GET /redmine/stylesheets/jquery/jquery-ui-1.13.2.css?1759842076 HTTP/1.1" 304 0 "http://10.96.245.73/redmine/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"
...
IPを抽出して集計
変にアクセス数が多いときに特定のIPからのアクセスが多くないか(攻撃されてないか)を確認するときなどに使います。
$ perl -lane 'print $F[0]' /var/log/nginx/access.log | sort | uniq -c
106 10.96.245.1
106 10.96.245.1からのアクセスが106回あったことがわかります。
(頑張ればPerlのみでも実現できますが、sortとuniqコマンドを使うほうがシンプルでわかりやすいので活用しました。)
特定のパスにアクセスしているIPを抽出して集計
もう少し複雑な例で/redmine/へのアクセスのみ集計する場合を考えます。
$ perl -lane 'if ($F[6] eq "/redmine/") { print $F[0] }' /var/log/nginx/access.log | sort | uniq -c
2 10.96.245.1
2件だけでした。/\s/で区切った7つ目(配列のインデックスだと6)にアクセスパスが入っているので$F[6] eq "/redmine/"でフィルターしています。
補足: grepを使うほうがシンプルかも?
$ grep ' /redmine/ ' /var/log/nginx/access.log | perl -lane 'print $F[0]' | sort | uniq -c
2 10.96.245.1
アクセスした時間帯で集計
いつ頃からアクセス数が増えたのか?などを確認するときに使います。
$ perl -lane 'print $F[3]' /var/log/nginx/access.log | perl -F: -lane 'print $F[1]' | sort | uniq -c
74 01
8 02
24 04
1時台に74回アクセスがあった、のように確認します。
ワンライナーの解説です。
最初のperl -lane 'print $F[3]' /var/log/nginx/access.logでタイムスタンプなところを取り出します。
そこだけ実行すると次の通り:
$ perl -lane 'print $F[3]' /var/log/nginx/access.log | head
[10/Oct/2025:01:41:02
[10/Oct/2025:01:41:02
[10/Oct/2025:01:41:02
...
次のperl -F: -lane 'print $F[1]'でHourの部分だけを取り出して集計しています。
(Perlのワンライナーを2つ使いましたが、1つにまとめることもできます。)
まとめ
YAPC::Fukuoka 2025に参加するのですが、普段のククログからはPerl好きが感じられない私なので、Perl好きをアピールするための記事でした。 当日は発表もするのでよろしくお願いします!