for や foreach は、決まった回数のループを作るのに便利だ。 一方、ある条件が満たされている間だけループしたいとか、ある条件が満たされない間ループしたいというケースもある。 そういう場合は、for ではなく while を使う。
指定したファイルの内容を表示するプログラムを作ってみよう。 Unix には、まさにそういうことをしてくれるツールがすでにある(cat という名前だ)が、とにかく練習のつもりで試してみよう。 まず最初に、あまり省略しないで書いてみよう。
while ($s = <ARGV>) { print $s; }
まず最初に、while 文の説明だ。 while は大体次のような構造をしている。
while (条件式) { ループ; }
ようするに、for 文の条件式だけを抜き取ったような構造になっている。 条件式が真の間、ループ部を実行してくれるのである。
さてサンプルプログラムの条件式は「$s = <ARGV>」だった。 まず「<ARGV>」の部分は、「コマンドラインに指定されていた引数をファイル名と見なしてオープンし、そこから1行読み込む」という意味である。 このオープンと読み込みは、全自動で行われる。 もし、コマンドラインでファイルがひとつも指定されていなければ、そのときに限って標準入力から読み込みを受け付ける。 その後読み込まれた1行は、変数 $s に代入される。 もしファイルが空になり、コマンドラインに指定されているファイルがなくなったら、<ARGV> は偽を返す。 そのため「$s = <ARGV>」という式そのものが偽となり、while ループも終了する。 while ループの中では、単に変数 $s を表示するだけである。
このファイルを cat.pl という名前でセーブし、次のようにして実行してみよう。
% perl cat.pl kuku.pl foreach $i (1 .. 9) { foreach $j (1 .. 9) { $ans = $i * $j; print "\t$ans"; } print "\n"; }
<ARGV> は、ファイル名をいくつ指定してもよいという特徴がある。 Perl は次々にファイルを開き、1行ずつ読み込んでくれる。
さて、先ほどの cat.pl は説明のために冗長に書いていた。 実は Perl には「決まりきったことは省略して書ける」という特徴がある。 たとえば「コマンドラインに指定されたファイルを読み込んで…」というのは、 Perl にとっては日常的な作業のひとつだ。 そこで短く「<>」と書くだけでよいことになっている。 さらに変数への代入も省略すると、それは「$_ という変数への代入」ということになる。 というわけで先ほどのプログラムはこう書ける。
while (<>) { print $_; }
ちょっと記号ぽくて意味が分からないが、こういう書き方はしょっちゅう出てくるので感覚的に覚えてしまうようにしよう。
ついでにいうと、print 文は何も指定せずにただ「print;」と書くと「変数 $_ を表示する」という動作をしてくれちゃうので、最終的にはここまで短くできる。
while (<>) { print; }
C 言語になれた読者なら、{ } は冗長だから省略できるのでは? と思うかもしれない。 が、Perl ではこの { } は省略不可能なので、いちいち書かなくてはならない。
このループを使えば、ファイルの行数を数えるのも楽にできる。 試してみよう。
while (<>) { $n++; } print "$n\n";
Perl では、初期化していない変数は全部 0 と見なされるので、空のファイルを渡されても問題なく「0」と表示してくれる。
ちょっと書き換えれば、ファイルに行番号を付けて表示するようにもできる。
while (<>) { $n++; print "$n\t$_"; }
print 文で、改行していない点に注目してほしい。 <>は、ファイルから1行読み込むときに、改行も一緒に読み込むのだ (この点は awk とちょっと違う)。 だから「print "$n\t$_\n"」と書くと、一回余計に改行してしまう。
さてせっかく行数を数えたのだから、次は文字数を数えてしまおう。 文字数は、length という関数を使えばすぐに求まる。
while (<>) { $len += length($_); } print "$len\n";
「+=」というのは初のお目見えだろう。 これは「A = A + X」という表記を省略して「A += X」と書く方法だ。 この書式は、プラスに限らずすべての演算子で可能だ。 変数を2倍したいなら「$a *= 2;」と書けるわけだ。 念のため、上の例を冗長に書き直すと「$len = $len + length($_);」となる。