hnwの日記

ワンライナーのためのPHPコマンドラインオプション

ワンライナー(1行スクリプト)の代名詞と言えばPerlでしょう*1Perlにはワンライナー向けのコマンドラインオプションが多数用意されています。中でも-neオプションや-peオプションは強力で、入力行ごとの処理をループを使わずに簡潔に記述できます。Perlワンライナー向けコマンドラインオプションの一部はRubyにも輸入されており、Rubyプログラマワンライナーを書くことが多いように見えます。


それに比べるとPHPワンライナーが書きにくい印象があるのではないでしょうか。僕もそう思っていましたが、実はPHP5.0.0からワンライナー向けのコマンドラインオプションが追加されています。それが今回紹介する-R、-F、-B、-Eです。

-Rオプション、-Fオプション

-Rというのは、標準入力を1行ごとに順に処理するオプションです。例を見てみましょう。

$ ls bin/php-*
bin/php-5.0.0
bin/php-5.1.0
bin/php-5.2.0
bin/php-5.2.10
bin/php-5.3.0


例えばこの英小文字を全て英大文字にしたくなったとしましょう。

$ ls bin/php-* | php -R 'echo strtoupper($argn),"\n";'
BIN/PHP-5.0.0
BIN/PHP-5.1.0
BIN/PHP-5.2.0
BIN/PHP-5.2.10
BIN/PHP-5.3.0


このように、-Rオプションを指定すると標準入力の全行について指定した処理を順に行います。入力行は改行を取り除いた状態で$argnに格納されます。また、$argiに行番号(1スタート)が格納されます。これならPHPでもワンライナーがスラスラ書けますね!


詳しくはPHPマニュアルの「PHP をコマンドラインから使用する」を参照してください。


上の例であれば他のツールを使った方が簡潔に書けますけど、PHPの関数を使いたいときには便利な機能だと思います。次の例は、mb_convert_encoding関数をフィルタ的に使うものです。

$ php -R 'echo mb_convert_encoding($argn, "UTF-8", "SJIS-win"), "\n";' < sjis.txt > utf8.txt


PHPの-Rオプションが少々不便に感じるのは、Perlで言うchomp(最後の文字が改行文字だったら取り除く)を自動で行う点です。フィルタのような挙動にしたい場合は取り除かれた改行文字を明示的にechoする必要があることに注意してください。


もしも気に入ったワンライナーが出来たら、-Fオプションを利用して同じ内容をシェルスクリプト化することも可能です。例えば、以下のファイルに実行権限を出しておけば、上のワンライナーと同じ挙動になります。

#!/usr/local/bin/php -F
<?php echo mb_convert_encoding($argn, "UTF-8", "SJIS-win"), "\n";


この-Fオプションというのは-Rオプションの変形と考えればいいでしょう。-Rオプションは行ごとの処理内容をコマンドラインから渡しますが、-Fの場合は行ごとの処理内容をファイルで渡します。


とはいえ、ファイルとして保存する場合は普通に書いた方が後々のメンテや流用の際に便利でしょうから、-Fを使う機会は限られるかもしれません。

-Bオプション、-Eオプション

1行ごとのループを簡潔に記述できるようになると、初期化処理や終了処理などを書きたくなることがあります。例えば、ls -lが出力するファイルサイズの合計が取りたくなったとしましょう。

$ ls -l bin/php-5.*
-rwxr-xr-x   1 hanawa  hanawa   3878892 Jan  4 16:03 bin/php-5.0.0
-rwxr-xr-x   1 hanawa  hanawa  14338952 Jan  4 16:03 bin/php-5.1.0
-rwxr-xr-x   1 hanawa  hanawa  15466908 Jan  4 16:03 bin/php-5.2.0
-rwxr-xr-x   1 hanawa  hanawa  16165864 Jun 19 14:11 bin/php-5.2.10
-rwxr-xr-x   1 hanawa  hanawa  20444708 May 15 00:16 bin/php-5.3.0


ファイルサイズの合計を取ろうと思った場合、-Rオプションだけで足し算することはできますが、足した結果だけを表示することは不可能です。そこで-Bおよび-Eオプションが利用できます。

$ ls -l bin/php-5.* | php -B '$sum=0;' -R '$vals=preg_split("/\s+/",$argn);$sum+=$vals[4];' -E 'echo "Total size of files: ", $sum, "\n";'
Total size of files: 70295324


-Eオプションで、全行の処理が終わったあとに1度だけ実行する処理を指定することができます。同様に、-Bオプションは1行目の処理を行う前に1度だけ実行する処理を書くことができます。


要するに、これらのオプションはawkPerlのBEGINブロックやENDブロックにあたるものです。これでawkPerlと同じことがPHPでも簡単に書けますね!

注意点

Perlの-neや-peでフィルタを書いた場合には、コマンドラインで入力ファイルを指定することも可能です。一方、PHPの-Rでは標準入力しか扱えません。


例示して説明します。Perlでは次のように書けば標準入力に対するフィルタとして動きます。

$ perl -pe 's/hoge/HOGE/g' < hoge.txt


また、次のように書くことで引数で与えられたファイルの全行に対して処理が走ります。

$ perl -pe 's/hoge/HOGE/g' hoge.txt


これらは同じ結果を出力しますが、処理が異なっています。つまり、前者はシェルの機能を利用して、ファイルの内容を標準入力経由でPerlコマンドに受け渡していますが、後者はPerlがファイルの内容を読み出しています。Perlでこのような記述が許されているのは、ワンライナーを気持ちよく書くためだと思いますし、僕も気に入っている機能です。


残念ながら、PHPの-Rオプションでは引数でファイル名を渡すような指定はできず、前者のように標準入力で受け渡すしかありません。PerlPHPの両方を使う人は注意が必要でしょう。

*1:そこはawkだろ、というツッコミが来そうな気もしますが…