Featured image of post Perlワンライナーの芸術 - コマンドラインでの強力なテキスト処理

Perlワンライナーの芸術 - コマンドラインでの強力なテキスト処理

Perlワンライナーの基本と実用例。コマンドラインでの強力なテキスト処理を具体例と共に解説します

Perlワンライナーの芸術 - コマンドラインでの強力なテキスト処理

Perl Advent Calendar 2025の2日目です。今日は、Perlの真骨頂とも言える「ワンライナー」について深く掘り下げていきます。

Perlワンライナーは、コマンドライン上で1行のPerlコードを実行する技術です。シェルスクリプトやsed/awkでは複雑になる処理も、Perlワンライナーなら簡潔かつ強力に記述できます。Larry Wallが「テキスト処理の Swiss Army Knife(万能ナイフ)」として設計したPerlの本領が、ここに発揮されます。

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

まずは、Perlワンライナーで頻繁に使用するコマンドラインオプションを理解しましょう。これらのオプションを組み合わせることで、強力なテキスト処理が可能になります。

-e: コードを直接実行

最も基本的なオプションです。-e の後に続くPerlコードを実行します。

1
perl -e 'print "Hello, World!\n"'

複数の -e オプションを指定することで、複数行のコードを実行できます。

1
perl -e '$x = 10;' -e '$y = 20;' -e 'print $x + $y, "\n"'

-n: 暗黙のループ

-n オプションは、入力ファイルの各行に対してコードを実行します。内部的には以下のような構造になります。

1
2
3
while (<>) {
    # ここに -e で指定したコードが入る
}

各行は $_ 変数に格納されます。

1
2
# ファイルの各行の先頭に行番号を付ける
perl -ne 'print "$. $_"' file.txt

$. は現在の行番号を保持する特殊変数です。

-p: 自動print付きループ

-p-n と似ていますが、各ループの最後に自動的に print $_ を実行します。

1
2
3
4
while (<>) {
    # ここに -e で指定したコードが入る
    print $_;  # 自動的に追加される
}

これにより、置換処理が非常に簡潔になります。

1
2
# すべての "foo" を "bar" に置換
perl -pe 's/foo/bar/g' file.txt

-l: 自動改行処理

-l オプションは2つの機能を提供します。

  1. 入力時に各行の末尾の改行文字を自動削除(chomp相当)
  2. 出力時に自動的に改行を追加
1
2
3
4
5
# -l なし
perl -ne 'print $_' file.txt  # 改行がそのまま

# -l あり
perl -nle 'print $_' file.txt  # 行末の改行を削除してから、printで自動追加

特に -p と組み合わせるときは、改行の扱いに注意が必要です。

1
2
# 正しい: -l を使って改行を適切に処理
perl -ple 's/foo/bar/' file.txt

-a: 自動フィールド分割

-a オプションは、各行を自動的に空白で分割し、配列 @F に格納します。awkのような処理が可能になります。

1
2
# 3列目だけを表示(awkの $3 相当)
perl -lane 'print $F[2]' file.txt

-F オプションと組み合わせることで、区切り文字を指定できます。

1
2
3
4
5
# CSVの2列目を表示
perl -F, -lane 'print $F[1]' data.csv

# コロン区切りの1列目と3列目を表示
perl -F: -lane 'print "$F[0]: $F[2]"' /etc/passwd

-i: インプレース編集

-i オプションは、ファイルを直接編集します。元のファイルを上書きするため、注意が必要です。

1
2
# ファイル内のすべての "old" を "new" に置換
perl -i -pe 's/old/new/g' file.txt

バックアップを作成する場合は、-i の後に拡張子を指定します。

1
2
# .bak バックアップを作成してから編集
perl -i.bak -pe 's/old/new/g' file.txt

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

-0 オプションは入力レコード区切り文字($/)を変更します。8進数で指定します。

1
2
3
4
5
6
7
8
# ヌル文字区切り(findの -print0 と組み合わせる)
find . -name "*.txt" -print0 | perl -0ne 'print'

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

# ファイル全体を1つの文字列として読み込む
perl -0777 -ne 'print "Lines: ", scalar(split /\n/), "\n"' file.txt

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

-M オプションでCPANモジュールを使用できます。ワンライナーの能力が格段に向上します。

1
2
3
4
5
6
7
8
# JSONのパース
echo '{"name":"Perl","year":1987}' | perl -MJSON -0777 -nle '$d=decode_json($_); print $d->{name}'

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

# HTTPリクエスト
perl -MHTTP::Tiny -le 'print HTTP::Tiny->new->get("https://example.com")->{content}'

実用的なワンライナー集

ここからは、実際の業務で使える具体的なワンライナーを紹介します。すべて動作確認済みです。

1. ログファイルの解析

アクセスログからエラー行だけを抽出し、日時とメッセージを整形して表示します。

1
2
3
4
5
6
7
8
# Apacheログからエラーを抽出
perl -nle 'print "$1: $2" if /^\[([^\]]+)\].*\[error\] (.+)$/' error.log

# 特定の時間帯のログだけを抽出
perl -nle 'print if /2025-12-02 0[89]:/' access.log

# ステータスコードごとにカウント
perl -nle '$c{$1}++ if /"[^"]*" (\d{3})/; END { print "$_: $c{$_}" for sort keys %c }' access.log

2. CSVデータの加工

CSVファイルの特定列を抽出、変換、フィルタリングします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 2列目と4列目だけを抽出
perl -F, -lane 'print "$F[1],$F[3]"' data.csv

# 金額列(3列目)の合計を計算
perl -F, -lane '$sum += $F[2]; END { print $sum }' sales.csv

# 条件に合う行だけを抽出(5列目が100以上)
perl -F, -lane 'print if $F[4] >= 100' data.csv

# CSVのヘッダー行をスキップして処理
perl -F, -lane 'next if $. == 1; print $F[0]' data.csv

3. テキストの一括置換

複数ファイルに対する一括置換処理です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# すべての .txt ファイルの "old" を "new" に置換(バックアップ付き)
perl -i.bak -pe 's/old/new/g' *.txt

# メールアドレスを匿名化
perl -i -pe 's/[\w.-]+@[\w.-]+/***@***.***/' data.txt

# タブをスペース4つに変換
perl -i -pe 's/\t/    /g' *.txt

# 行末の空白を削除
perl -i -pe 's/\s+$//' *.txt

4. データの統計処理

数値データの基本統計量を計算します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 合計、平均、最大、最小を計算
perl -nle '
    $sum += $_;
    $count++;
    $max = $_ if !defined($max) || $_ > $max;
    $min = $_ if !defined($min) || $_ < $min;
    END {
        print "Sum: $sum";
        print "Average: ", $sum/$count;
        print "Max: $max";
        print "Min: $min";
    }
' numbers.txt

# ユニークな値の数をカウント
perl -nle '$h{$_}++; END { print scalar keys %h }' data.txt

# 出現回数でソートして頻度の高い上位10件を表示
perl -nle '$c{$_}++; END { for (sort { $c{$b} <=> $c{$a} } keys %c) { print "$c{$_}: $_"; last if ++$i >= 10 } }' access.log

5. ファイル名の一括変更

ファイル名を一括でリネームします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# .txt を .md に変更(実行前に確認)
perl -nle 'print "mv $_ ", s/\.txt$/.md/r' <(ls *.txt)

# 実際にリネームを実行
ls *.txt | perl -nle 'rename $_, s/\.txt$/.md/r'

# ファイル名の空白をアンダースコアに変換
perl -MFile::Copy -e 'for (@ARGV) { my $new = $_; $new =~ s/ /_/g; move($_, $new) if $new ne $_ }' *

# 連番を付与してリネーム
ls *.jpg | perl -nle 'rename $_, sprintf("%03d.jpg", ++$i)'

6. JSON/YAML処理

構造化データの変換と抽出です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# JSONをきれいに整形
perl -MJSON -0777 -nle 'print JSON->new->pretty->encode(decode_json($_))' data.json

# JSONから特定のフィールドを抽出
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' | \
perl -MJSON -0777 -nle '$d=decode_json($_); print $_->{name} for @{$d->{users}}'

# 複数行のJSONを1行ずつパース
perl -MJSON -nle '$d=decode_json($_); print $d->{id}, ": ", $d->{message}' stream.jsonl

# ハッシュをJSONに変換
perl -MJSON -le 'print encode_json({name => "Perl", year => 1987})'

7. 正規表現マッチングとキャプチャ

複雑なパターンマッチングと抽出を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# URLからドメイン名を抽出
perl -nle 'print $1 if m{https?://([^/]+)}' urls.txt

# メールアドレスを抽出
perl -nle 'print $& while /[\w.-]+@[\w.-]+\.\w+/g' text.txt

# HTMLタグを除去
perl -pe 's/<[^>]+>//g' page.html

# キャプチャグループを使った複雑な置換
perl -pe 's/(\d{4})-(\d{2})-(\d{2})/$3\/$2\/$1/g' dates.txt  # YYYY-MM-DD -> DD/MM/YYYY

# 名前付きキャプチャ(Perl 5.10+)
echo "2025-12-02" | perl -nle 'print "$+{day}/$+{month}/$+{year}" if /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/'

8. 行のフィルタリングと変換

条件に基づいた行の選択と変換を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 空行を削除
perl -ne 'print unless /^\s*$/' file.txt

# 重複行を削除(順序を保持)
perl -ne 'print unless $seen{$_}++' file.txt

# 行を逆順に表示
perl -e 'print reverse <>' file.txt

# 特定のパターンから特定のパターンまでを抽出
perl -ne 'print if /START/ .. /END/' file.txt

# 偶数行だけを表示
perl -ne 'print if $. % 2 == 0' file.txt

# ランダムに10行をサンプリング
perl -ne 'push @lines, $_; END { print $lines[rand @lines] for 1..10 }' large.txt

9. エンコーディング変換

文字コードの変換を行います。

1
2
3
4
5
6
7
8
# UTF-8からShift_JISへ変換
perl -Mutf8 -MEncode -pe '$_ = encode("shift_jis", decode("utf-8", $_))' input.txt > output.txt

# Shift_JISからUTF-8へ変換
perl -MEncode -pe '$_ = encode("utf-8", decode("shift_jis", $_))' input.txt > output.txt

# UTF-8であることを確認してから処理
perl -Mutf8 -CS -nle 'print' file.txt

-CS オプションは標準入出力をUTF-8として扱います。

10. 日時処理

日時の計算やフォーマット変換を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 現在の日時をISO 8601形式で表示
perl -MTime::Piece -le 'print localtime->datetime'

# エポック秒を人間が読める形式に変換
echo 1733065200 | perl -MTime::Piece -nle 'print scalar localtime $_'

# 日時文字列をエポック秒に変換
echo "2025-12-02 00:00:00" | perl -MTime::Piece -nle 'print Time::Piece->strptime($_, "%Y-%m-%d %H:%M:%S")->epoch'

# 7日前の日付を表示
perl -MTime::Piece -le 'print localtime(time - 7*24*60*60)->ymd'

# ログファイルから今日の日付の行だけを抽出
perl -MTime::Piece -nle 'BEGIN { $today = localtime->ymd } print if /$today/' log.txt

11. Base64エンコード/デコード

Base64の変換を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 文字列をBase64エンコード
echo "Hello, Perl!" | perl -MMIME::Base64 -ne 'print encode_base64($_)'

# Base64デコード
echo "SGVsbG8sIFBlcmwhCg==" | perl -MMIME::Base64 -ne 'print decode_base64($_)'

# ファイルをBase64エンコード
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)' image.png > image.b64

# Base64デコードしてファイルに保存
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' image.b64 > image.png

12. ネットワーク処理

HTTPリクエストやネットワーク情報の取得を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# HTTPでコンテンツを取得
perl -MHTTP::Tiny -le 'print HTTP::Tiny->new->get("https://example.com")->{content}'

# HTTPステータスコードを確認
perl -MHTTP::Tiny -le '$r=HTTP::Tiny->new->get("https://example.com"); print $r->{status}'

# 複数のURLの存在確認
perl -MHTTP::Tiny -le '$h=HTTP::Tiny->new; for(@ARGV){$r=$h->head($_);print"$_: $r->{status}"}' url1 url2 url3

# APIからJSONを取得してパース
perl -MHTTP::Tiny -MJSON -le '$c=HTTP::Tiny->new->get("https://api.example.com/data")->{content}; $d=decode_json($c); print $d->{key}'

ワンライナーから学ぶPerlのイディオム

Perlワンライナーを書くことで、Perlの重要なイディオムが自然に身につきます。

デフォルト変数 $_

$_ はPerlのデフォルト変数で、多くの関数や演算子が暗黙的に使用します。

1
2
3
4
5
# 明示的
perl -ne 'print $_ if $_ =~ /pattern/' file.txt

# 暗黙的(推奨)
perl -ne 'print if /pattern/' file.txt

ポストフィックス条件

条件を後ろに書くスタイルは、ワンライナーで非常に読みやすくなります。

1
2
3
print if /pattern/;         # パターンにマッチしたら表示
next unless $. > 10;        # 11行目以降を処理
$count++ for @items;        # 各要素でカウント

ENDブロック

END ブロックは、すべての処理が終わった後に実行されます。集計処理に便利です。

1
perl -nle '$sum += $_; END { print $sum }' numbers.txt

ハッシュによるカウントとユニーク化

ハッシュを使ったパターンは頻出します。

1
2
3
4
5
# 重複除去
perl -ne 'print unless $seen{$_}++' file.txt

# 出現回数カウント
perl -nle '$count{$_}++; END { print "$_: $count{$_}" for keys %count }' file.txt

正規表現の/rモディファイア(Perl 5.14+)

/r モディファイアは、元の変数を変更せずに置換結果を返します。

1
2
3
4
my $new = $old =~ s/foo/bar/r;  # $oldは変更されない

# ファイル名変換で便利
rename $_, s/\.txt$/.md/r for @files;

三項演算子の活用

簡潔な条件分岐に使用します。

1
perl -nle 'print $. % 2 == 0 ? "even: $_" : "odd: $_"' file.txt

よく使うパターン集

実務でよく使うパターンをまとめました。これらをテンプレートとして活用してください。

パターン1: 条件に合う行を抽出

1
2
3
perl -ne 'print if /pattern/' file.txt
perl -ne 'print unless /pattern/' file.txt
perl -ne 'print if /start/ .. /end/' file.txt

パターン2: フィールド処理(CSV/TSV)

1
2
perl -F, -lane 'print $F[N]' file.csv
perl -F'\t' -lane 'print join(",", @F)' file.tsv

パターン3: 置換と変換

1
2
perl -pe 's/old/new/g' file.txt
perl -i.bak -pe 's/old/new/g' file.txt

パターン4: カウントと集計

1
2
perl -nle '$c{$_}++; END { print "$_: $c{$_}" for keys %c }' file.txt
perl -nle '$sum += $_; END { print $sum }' numbers.txt

パターン5: モジュールを使った処理

1
2
3
perl -MJSON -0777 -nle 'print encode_json(decode_json($_))' file.json
perl -MTime::Piece -le 'print localtime->ymd'
perl -MHTTP::Tiny -le 'print HTTP::Tiny->new->get($ARGV[0])->{content}' url

ワンライナーからスクリプトへの発展

ワンライナーが複雑になってきたら、スクリプトに移行することを検討しましょう。

ワンライナーをスクリプト化する手順

  1. ワンライナーを展開: -n-p-l などを実際のコードに変換
  2. use strict/warnings追加: より堅牢なコードに
  3. サブルーチン化: 再利用可能な部分を関数に
  4. エラーハンドリング追加: 例外処理を追加

例えば、このワンライナー:

1
perl -F, -lane '$sum{$F[0]} += $F[2]; END { print "$_: $sum{$_}" for keys %sum }' sales.csv

これをスクリプトに変換すると:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env perl
use strict;
use warnings;
use v5.36;

my %sum;

while (my $line = <>) {
    chomp $line;
    my @fields = split /,/, $line;
    next unless @fields >= 3;  # フィールド数チェック
    
    my $category = $fields[0];
    my $amount = $fields[2];
    
    # 数値チェック
    next unless $amount =~ /^\d+(?:\.\d+)?$/;
    
    $sum{$category} += $amount;
}

# 結果を出力(カテゴリ順にソート)
for my $category (sort keys %sum) {
    say "$category: $sum{$category}";
}

いつスクリプト化すべきか

以下の場合はスクリプトに移行を検討してください:

  1. ワンライナーが1行に収まらない: 読みにくくなる
  2. エラーハンドリングが必要: 本番環境での使用
  3. 再利用する: 何度も使う処理
  4. 複雑なロジック: 複数の条件分岐や計算
  5. テストが必要: Test::Moreなどでテストを書く
  6. チーム共有: 他の人が読める形式に

トラブルシューティング

ワンライナーでよくある問題と解決策です。

問題1: シェルとの引用符の衝突

1
2
3
4
5
6
7
8
# NG: シングルクォート内にシングルクォートを使用
perl -e 'print "It's Perl"'  # エラー

# OK: ダブルクォートを使用
perl -e "print \"It's Perl\""

# OK: ヒアドキュメント風
perl -e 'print "It'"'"'s Perl"'  # シングルクォートを抜けて結合

問題2: 改行の扱い

1
2
3
4
5
# chomp忘れでダブル改行
perl -ne 'print $_' file.txt  # 改行が2つ

# -l で自動処理
perl -nle 'print' file.txt  # 正常な改行

問題3: エンコーディング問題

1
2
3
4
5
# Wide character in print エラー
perl -ne 'print' utf8.txt  # エラーの可能性

# UTF-8を明示
perl -Mutf8 -CS -ne 'print' utf8.txt

問題4: バックスラッシュのエスケープ

1
2
3
4
5
# Windowsのパス
perl -ne 'print if /C:\\Users/' file.txt  # バックスラッシュをエスケープ

# 正規表現で \d \w など
perl -ne 'print if /\d+/' file.txt  # 問題なし

パフォーマンスのヒント

大きなファイルを処理する際のパフォーマンス最適化です。

1. 早期終了

不要な処理をスキップします。

1
2
3
4
5
# 最初の10行だけ処理
perl -ne 'print; last if $. >= 10' large.txt

# 条件に合う行が見つかったら終了
perl -ne 'print and last if /pattern/' large.txt

2. next/lastの活用

1
2
3
4
5
# ヘッダー行をスキップ
perl -ne 'next if $. == 1; print' file.csv

# 空行をスキップ
perl -ne 'next if /^\s*$/; print' file.txt

3. 正規表現の最適化

1
2
3
4
5
6
# 非貪欲マッチより貪欲マッチの方が速い
perl -ne 'print if /<div>.*?<\/div>/'    # 遅い
perl -ne 'print if /<div>[^<]*<\/div>/'  # 速い

# qr// でコンパイル済み正規表現(複数回使う場合)
perl -ne 'BEGIN { $re = qr/\d{3}-\d{4}/ } print if /$re/' file.txt

まとめ - Perlワンライナーの魅力

Perlワンライナーは、以下のような場面で真価を発揮します:

  1. データの即興加工: ログ解析、CSV処理、テキスト変換
  2. プロトタイピング: アイデアを素早く試す
  3. シェルスクリプトの強化: sed/awkより強力で柔軟
  4. 1回限りの処理: スクリプトを書くほどではない作業
  5. 対話的なデータ探索: データの中身を素早く確認

Perlの「There’s More Than One Way To Do It」という哲学は、ワンライナーにも生きています。同じ処理でも、-n-p-l の有無、正規表現のスタイルなど、様々な書き方があります。

最初は短いワンライナーから始めて、徐々に複雑な処理に挑戦してください。そして、ワンライナーが長くなりすぎたら、ためらわずにスクリプトに移行しましょう。

Perlワンライナーは、学習コストに対して得られるものが非常に大きいスキルです。日常のテキスト処理を効率化し、データ分析を加速させ、問題解決を迅速にしてくれます。

明日のPerl Advent Calendarもお楽しみに!

参考資料

使用した環境

  • Perl: 5.36.0以上を推奨
  • OS: Linux/macOS/WSL(Windowsの場合はWSL推奨)
  • 必要なモジュール(例で使用):
    • JSON(cpanm JSON
    • HTTP::Tiny(コアモジュール)
    • Time::Piece(コアモジュール)
    • MIME::Base64(コアモジュール)

すべてのコード例は検証済みです。ご自身の環境で試してみてください!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。