実用 perl ワンライナー

こちらの記事で紹介されているawkでの操作について、Perlではどう書けるかを確認したものです。
http://qiita.com/b4b4r07/items/45d34a434f05aa896d69

awkは難しいけど、Perlならちょっとだけ普通のプログラミング言語に近いから何とか…（あるいは昔CGI書いてたから何とか…）というニッチ層向け。

# 使用するコマンドラインオプション

各オプションの詳細は `perldoc perlrun` コマンドで参照できます。

## `-e 'Perlプログラム'`

パラメータをそのままプログラムとして実行します。ワンライナーの根本。

## `-E 'Perlプログラム'`

`-e` と同じですが、いくつかの追加機能が有効になります。ワンライナーでは主に、末尾に改行を追加して `print` する関数 `say` を使えるようにするために指定します。

## `-n`

入力の各行に対して指定したプログラムを実行します。行のデータは変数 `$_` に、行番号は `$.` に格納されています。

## `-p`

`-n` と同じく入力の各行に対して指定したプログラムを実行し、さらに最後に `print $_` します。全ての行に対して結果を必ず出力する場合は、`-n` よりも便利です。

## `-a`

`-n` か `-p` と合わせて指定すると、awk のように各行のデータが自動でフィールドに分解されます。フィールドの配列は `@F` に格納されます。

## `-F<パターン>`

`-a` で使用する区切り文字を、指定したパターンに変更します（デフォルトでは空白）。

## `-l`

`-n` か `-p` と合わせて指定すると、入力（`$_`）から自動的に改行（行区切り文字）を取り除き、出力に改行を追加します。

## `-C<オプション>`

ワンライナーの入力データは

    cat input.txt | perl -e '<プログラム>'

のように標準入力として渡されるか、

    perl -e '<プログラム>' input.txt

のようにコマンド引数として渡されますが、これらの入力はどちらもデフォルトでは単なるバイト列として扱われます。

たいていの処理はそれで特に問題なく実行できますが、もし入力がUTF-8文字列で、かつそれを「文字単位で」操作したい場合（文字数カウントなど）は、この `-C` フラグを指定して、UTF-8文字列として扱わせることができます。

設定する場合、`<オプション>` 部分には上記2つの入力方法・標準出力・標準エラー出力等の全てをUTF-8化することを意味する `SD` を指定して、 `-CSD` としておけば良いでしょう。


# 最初の行を表示する

    perl -ne 'print if $.==1'

行番号が 1 の場合のみ行のデータを `print` します。Perlでは多くの関数で、パラメータを省略すると `$_` が指定されたものと見なされます。`print` も `print $_` と同じです。

または、乱暴に以下でも良いです。

    perl -ne 'print;exit'

# N行目を表示する

    perl -ne 'print if $.==10'

10行目を表示する例です。

# 最後の行を表示する

    perl -ne '$x=$_; END{print $x}'

`BEGIN{}` と `END{}` ブロックで、`-n` や `-p` の処理全体の前処理、後処理を記述できます。awk と違って `END` の時点では `$_` の値は消えているので、行の値を自分で変数に退避する必要があります。

# 空行を削除する

    perl -ane 'print if @F'

`if @F` はフィールド数が真（`0` でない値）かどうかをテストします。空白のみの行は `@F` は空（フィールド数0）になるので、除外されます。 

    perl -lne 'print if $_'

`-l` によって改行が除かれるので、（空白も含めて）空の行だと `if $_` が偽になり、除外されます。

    perl -ne 'print if /./'

正規表現 `/./` は任意の1文字ですが、改行にはマッチしないため、改行だけで他に文字のない行は除外されます。

# 文字数をカウントする

    perl -nE '$n+=length; END{say $n}'

`length` はパラメータ（省略すると `$_`）の文字数を返します。取得した文字数を加算し、最後に出力します。出力に改行が無いと見づらいので、この例では `say` を使用しています。

デフォルトでは入力はバイト列扱いなので文字数＝バイト数となりますが、（UTF-8の）マルチバイト文字を1文字としてカウントしたい場合は、以下のように `-C` オプションを使用して入力をUTF-8文字列として扱わせます。

    perl -CSD -nE '$n+=length; END{say $n}'

# 単語数をカウントする

    perl -anE '$n+=@F; END{say $n}'

先程と同様ですが、フィールド数を加算対象としています。

# 行数をカウントする

    perl -nE 'END{say $.}'

何も処理せず、最後に現在の行番号を出力するだけです。

# 行末の空白やタブを削除する

    perl -pe 's/[ \t]+$//'

`s/正規表現/置換文字列/` で `$_` の値が置換されます。置換後の値が `-p` で自動的に出力されます。

# Unix の改行コードに変換する

    perl -pe 's/\r$//'

パターンが異なるだけで、先程と同じです。

# Windows の改行コードに変換する

    perl -pe 's/$/\r/'

パターンが異なるだけで、先程と同じです。

# 逆順出力をする

    perl -ne 'unshift @a,$_; END{print @a}'

`unshift <配列>, <値>` で配列の先頭に各行を挿入していき、最後に配列全体を出力します。`push` で末尾に追加し、`reverse` で逆順にしても良いかもしれません。

# 重複するレコードを削除する

    perl -ne 'print if !$a{$_}++'

awk版と考え方は同じです。連想配列 `%a` で、まだ `$_` がキーとして存在しなかった場合のみ、 `$a{$_}++` は偽の値である `0` になります。

`-a` `-F` オプションで特定列に対して一意処理をすることも可能です。

    # タブ区切りの2列目で uniq
    perl -F'\t' -ane 'print if !$a{$F[1]}++'

# 行番号を付ける

    perl -pe 's/^/$. /'

行の先頭を行番号とスペースで置換しています。

# 標準出力にそのまま出力する

    perl -pe ''

何もしなければそのまま出力されます。

# 正規表現にマッチした行を表示する

    perl -ne 'print if /hogehoge/'

ただし、`-C` オプションを使用して入力をUTF-8として扱っており、かつパターンにマルチバイト文字を指定する場合は、以下のように `use utf8` を指定してプログラム側もUTF-8文字列として解釈させる必要があります。

    perl -CSD -ne 'use utf8; print if /テスト/'

# 正規表現にマッチしない行を表示する

    perl -ne 'print if !/hogehoge/'

`-C` オプションに関する注意は上と同じです。

# コメント行を削除する

    perl -ne 'print if !/^#/'

複数行コメントの場合

    perl -ne 'print if !(/\/\*/../\*\//)'

`..` 演算子は、左側のパターンにマッチしてから右側のパターンにマッチするまで真になります。コメントとそうでない部分が同じ行内にあるかもしれないので、丸ごと読み込んで置換した方が素直かもしれません。

    perl -ne '$_=do{local $/;<>}; s/\/\*.+\*\///sg; print'

# 指定行から指定行までを表示する

    # 10行目から20行目までを表示する
    perl -ne 'print if 10..20'

`..` 演算子は数値に対しても使用でき、その場合は行番号の範囲を意味します。

# 偶数行を表示する

    perl -ne 'print if $.%2==0'

# 奇数行を表示する

    perl -ne 'print if $.%2'

Perlでも1は真ですので、省略してOKです。

# 特定のフィールドのみを抜き出す

フィールド数でフィルタするには、以下のようにします。

    # フィールド数が2〜3の行を抜き出す
    perl -ane 'print if @F>=2 && @F<=3'

特定のフィールドを抜き出すには `$F[1]`（Perlの添字は0からなので、2番目の要素を意味します）のように配列要素を出力します。

    # フィールド数が2〜3の行から、2番目のフィールドを抜き出す
    perl -anle 'print $F[1] if @F>=2 && @F<=3'
    perl -anE 'say $F[1] if @F>=2 && @F<=3'