🔮

Perlで実践するワンライナーの黒魔術

に公開

はじめに

私が仕事で初めて学んだプログラミング言語はPerlでした。

今から10年以上前、当時先輩のコードで見た$_@_といった特殊変数を駆使したコードには、その可読性に絶望しつつも少し憧れました。
mapgrepが連鎖し、1行で複雑なデータ変換をやってのける、黒魔術のようなコードです。

今、仕事でPerlを書くことはほぼありません。
あの読めない黒魔術コードは現代のチーム開発では完全にアンチパターンです。
私もあの頃のようなコードを許すことはありません。

それでも、Perlは私の中には生き続けています。ワンライナーとして

ログファイルを解析したい、CSVから特定のカラムを抽出したい、ファイルを一括置換したい。
そんなとき、私はPerlを使います。

なぜPerlワンライナーなのか

「sedやawkでいいじゃん」という声が聞こえてきそうです。

sedやawkでもできますが、Perlのほうが圧倒的に表現力が豊かです。

  • sedは置換に特化していますが、複雑なロジックは書けません
  • awkはフィールド処理に強いですが、データ構造が貧弱です
  • Perlはフル機能のプログラミング言語です

正規表現、ハッシュ、配列、モジュール、オブジェクト、複雑な条件分岐、ハッシュでの集計、正規表現の柔軟性、モジュールの活用。
全てをワンライナーで使えます。

しかも、PerlはほぼどのUnix/Linux環境にもプリインストールされています。

Perl ワンライナーの基本

Perlの特殊変数

Perlワンライナーを極めるには、特殊変数を知らねばなりません。
これらは暗黙的に値が設定され、コードを短縮します。

基本的な特殊変数

変数 意味
$_ デフォルトの引数
@_ サブルーチンの引数
@F -aで分割された配列
$. 現在の行番号
$ARGV 現在処理中のファイル名
$/ 入力レコードセパレータ(デフォルトは改行)
$\ 出力レコードセパレータ
$! システムエラーメッセージ
$? 子プロセスのステータス

少しマニアックな特殊変数

ここから少しマニアックな変数を一部紹介します。

変数 意味 使用例
$$ 現在のプロセスID perl -le 'print $$' でPIDを表示
$" リスト区切り文字(デフォルトはスペース) @a=(1,2,3); print "@a"1 2 3
$^O OS名(linux, darwin, MSWin32など) OS判定に使用
$0 実行中のプログラム名 スクリプト名を取得
$1, $2, $3... 正規表現のキャプチャグループ /(\\d+)-(\\d+)/$1$2に値
$& 最後にマッチした文字列全体 /foo/がマッチしたら$&foo
$` マッチより前の文字列 "ab[cd]ef"/cd/なら$``はab[`
$' マッチより後の文字列 "ab[cd]ef"/cd/なら$']ef
%ENV 環境変数のハッシュ $ENV{PATH}でPATHを取得
@ARGV コマンドライン引数の配列 ファイル名のリスト

$$でユニークなファイル名を生成したり、$"でリストの出力形式を変えたり。
これらを自然に使えるようになったとき、あなたも黒魔術師の仲間入りです。

さらなる闇の特殊変数

以下の特殊変数はもはや黒魔術です。

変数 意味
$^V Perlのバージョン番号(5.036など)
$^T スクリプト開始時のエポック秒
$^X Perlインタプリタの実行パス

Perlワンライナーの基本オプション

Perlワンライナーを構成する主要なオプションを理解します。
これらは単体でも強力ですが、組み合わせることで真の力を発揮します。

-e: コードを実行する

perl -e 'print "Hello, Dark World\n"'

-eはPerlコードを直接実行するオプションです。

-n: 暗黙のループ

# ファイルの各行を処理
perl -ne 'print if /error/' access.log

-nはファイルの各行に対して暗黙的にループを回します。
内部的には以下のように展開されます:

while (<>) {
    # あなたのコード
}

<>はダイアモンド演算子と呼ばれ、標準入力やファイルから行を読み込みます。
各行は特殊変数$_に自動的に格納されます。

-p: 自動print

# 全ての行の先頭に行番号を追加
perl -pe '$_ = "$. $_"' file.txt

-p-nと同じですが、各ループの最後に自動的にprint $_を実行します:

while (<>) {
    # あなたのコード
    print;
}

-l: 行末処理の自動化

perl -lne 'print uc' file.txt

-lは二つのことをします:

  1. 入力時に改行を自動削除(chomp相当)
  2. 出力時に改行を自動追加(print\nを追加)

-a: 自動split

# CSVの2列目だけを抽出
echo "foo,bar,baz" | perl -F, -lane 'print $F[1]'

-aは各行を自動的にsplitして配列@Fに格納します。
デフォルトでは空白で分割されますが、-Fで区切り文字を指定できます。

-F: 区切り文字の指定

# コロン区切りファイル(/etc/passwdとか)の処理
perl -F: -lane 'print $F[0] if $F[2] >= 1000' /etc/passwd

-Fで任意の区切り文字(正規表現可)を指定できます。
-aとセットで使うのが基本です。

-i: in-place編集

# ファイル内の全てのfooをbarに置換
perl -i -pe 's/foo/bar/g' file.txt

# バックアップを取りながら置換
perl -i.bak -pe 's/foo/bar/g' file.txt

-iはファイルを直接書き換えます。
バックアップが欲しい時は.bakとか拡張子を指定すれば問題ありません。

-0: レコード区切り文字の変更

# パラグラフモード(空行で区切る)
perl -00 -ne 'print if /keyword/' file.txt

# ファイル全体を一度に読み込む(スラープモード)
perl -0777 -pe 's/foo\nbar/baz/g' file.txt

-0は入力レコードセパレータ($/)を変更します。

  • -00: パラグラフモード(空行区切り)
  • -0777: ファイル全体を一つの文字列として読み込む

-M: モジュールのロード

# JSONを整形
echo '{"foo":"bar"}' | perl -MJSON -0777 -ne 'print JSON->new->pretty->encode(decode_json($_))'

# 日時処理
perl -MTime::Piece -le 'print localtime->strftime("%Y-%m-%d")'

-Mでモジュールを読み込めます。
CPANの広大なエコシステムをワンライナーで使えます。

黒魔術の実践

ここからが本番です。シェルやAWKでは面倒な処理を、Perlワンライナーで美しく解決する例を見ていきましょう。

例1: 順序保持してファイルの重複行削除

perl -ne 'print unless $seen{$_}++' file.txt
  • $seen{$_}++はハッシュに行を記録しつつ、値をインクリメント
  • 初回は0(偽)なのでunlessで出力
  • 2回目以降は1以上(真)なので出力されない

これがPerlの美学です。sort | uniqでは順序が変わりますが、Perlなら順序を保持できます。

例2: ファイルの特定範囲の行抽出

# 10行目から20行目まで
perl -ne 'print if 10..20' file.txt

範囲演算子..の真骨頂です。

これは「フリップフロップ演算子」と呼ばれ、行番号で範囲指定できます。

# "START"から"END"までの行を抽出
perl -ne 'print if /START/../END/' file.txt

パターンマッチでも使えます。
sedでもできますが、Perlの方が直感的で強力です。

例3: ログファイルのin-place編集 + 正規表現よる置換

本番環境のログを開発環境に持ってくるとき、パスワードやAPIキーをBase64エンコードして隠したい。
-iオプションと/e修飾子の組み合わせで、マッチした部分をコード実行できます。

# password=xxx の値をBase64エンコード
perl -i.bak -MMIME::Base64 -pe 's/(password=)(\S+)/$1.encode_base64($2,"")/ge' app.log
  • -i.bakでバックアップを作成
  • -MMIME::Base64でモジュールをロード
  • s///ge/e修飾子で置換部分をコードとして評価
  • encode_base64($2,"")で2番目のキャプチャグループ(パスワード部分)をエンコード

これがPerlの正規表現の真髄です。

APIキーのようなものもまとめてエンコードした時も同様です:

perl -i -MMIME::Base64 -pe 's/(api_key=)(\S+)/$1.encode_base64($2,"")/ge; s/(password=)(\S+)/$1.encode_base64($2,"")/ge' app.log

複数の置換を;で連結。1行で複雑な処理が完結します。

/e修飾子は黒魔術の極み。 置換部分で任意のPerlコードが実行できます。

例4: 実践的なログからの処理性能計算

実務でよくある場面です。以下のようなログがあるとします:

...
2025-10-10 10:00:00 INFO: BEGIN
2025-10-10 10:00:01 INFO: processing item 1
2025-10-10 10:00:02 INFO: processing item 2
2025-10-10 10:00:05 INFO: END - processed 28 records.
...

BEGINからENDまでの処理時間と、1レコードあたりの処理時間を計算したい。

AWKでやる?正規表現と時刻計算の組み合わせは面倒です。Perlならこうです:

perl -MTime::Piece -ne 'BEGIN{sub p{Time::Piece->strptime($_[0],"%Y-%m-%d %H:%M:%S")}}$s=p($1)if/^(\S+ \S+).*BEGIN/;/^(\S+ \S+).*END.*(\d+)/&&printf"Total: %.2fs, Per record: %.3fs\n",($e=p($1))-$s,($e-$s)/$2' app.log

出力:

Total: 5.00s, Per record: 0.179s

何をやっているのか?

  • BEGIN{sub p{...}}で時刻パース関数を定義(Time::Piece->strptimeを何度も書きたくない)
  • $s=p($1)if/^(\S+ \S+).*BEGIN/でBEGINのタイムスタンプを変数$sに保存
  • /^(\S+ \S+).*END.*(\d+)/&&printf...でENDを見つけたら即座に計算して出力
  • ($e=p($1))-$sで経過時間を計算しつつ$eに代入
  • ($e-$s)/$2で1レコードあたりの時間を計算($2はレコード数)

これこそがPerlワンライナーの真価です。

シェルスクリプトとAWKでもできないことはないですが、外部コマンドを呼んだりエポック秒に変換したりと非常に面倒です。
Perlなら標準モジュールで完結し、しかも短い。

最後に

Perlワンライナーは読みにくいです。
メンテナンスしにくいです。

だが、それがいい。

Perl開発をしたLarry Wallの言葉に 「TIMTOWTDI(ティムトゥディ)」という哲学があります。

There Is More Than One Way To Do It - やり方は一つじゃない。

Perlを使う人は皆自分にとって一番うまくいくと思う方法、自分に合った方法で解決できます。

皆さんが書くワンライナーは、大体はその場限りの使い捨てスクリプトです。
読めない?問題ないです、誰も読みません。読むのは自分だけです。
メンテナンス性?使い捨てです。実行したら消えます。

皆さんも黒魔術の使い手へ踏み出してみて下さい。

Rakuten Volunteers Tech Blog

Discussion