eval を用いて、簡単な電卓を作成してみます。eval { ... } は、... で指定した文字列を Perl の構文として実行し、その結果を返します。構文中にエラーがあった場合は、特殊変数 $@ にそのエラーメッセージを設定します。
for (;;) { print "> "; $input = <STDIN>; $ans = eval $input; if ($@) { print "$@\n"; } else { print "$ans\n"; } }
実行例を下記に示します。
% perl dentaku.pl > 5+3 # 5 + 3 を計算 8 > (5+3)*6 # 括弧付きの場合は優先 48 > sqrt(2) # sin() cos() などの数学関数も使用可能 1.4142135623731 > 0xef # 16進数を10進数に変換 239 > $a=5 # 変数 $a に値を代入 5 > $b=3 # 変数 $b に値を代入 3 > $a+$b # 変数を用いた演算結果を表示 8 > $ans * 5 # 前の演算結果を $ans で参照 40 > ^C # Ctrl を押しながら C キーで終了
簡易電卓と言いながらも、変数は使用できるし、sin()、cos()、sqrt() などの数学関数も使用できるし、for や if などの Perl 構文をすべて使用できるので、とても高機能な電卓であるともいえます。
Perl のサンプルとして、簡単なかけ算ゲームを作ってみます。まず、2~9 までの乱数 $a と $b を求め、キーボードから入力した値と比較して、正解なら「やったっ!!」を、不正解なら「ちがうよ。」を表示しています。一種の教育ソフトですね。
$mondai = 10; # 問題数 $seikai = 0; # 正解数 $t1 = time(); for ($i = 0; $i < $mondai; $i++) { $a = int(rand(8)) + 2; # int()、rand() 参照 $b = int(rand(8)) + 2; print "$a × $b = "; $in = <STDIN>; if ($in == $a * $b) { print "やったっ!!\n"; $seikai++; } else { print "ちがうよ。\n"; } } $t2 = time(); $keika = $t2 - $t1; print "$mondai 問中 $seikai 個正解しました。\n"; print "$keika 秒かかりました。\n";
実行例を下記に示します。
% perl kakezan.pl 7 × 8 = 54 ちがうよ。 7 × 8 = 56 やったっ!! :
rand() を用いて 1~100 の乱数を求め、入力した数値と比較して大きければ「もっと大きな数です。」を、小さければ「もっと小さな数です。」を、正解であれば「正解!!」を表示して終了します。
$count = 0; $ans = int(rand(100)) + 1; print "1~100の数字を当ててください。\n"; for (;;) { print "input> "; $in = <stdin>; $count++; if ($in < $ans) { print "もっと大きな数です。\n"; } if ($in > $ans) { print "もっと小さな数です。\n"; } if ($in == $ans) { print "正解!!\n"; print "$count 回で正解しました。\n"; exit(1); } }
実行例を下記に示します。
% perl kazuate.pl 1~100の数字を当ててください。 input> 50 もっと小さな数です。 input> 30 もっと大きな数です。 input> 40 正解!! 3 回で正解しました。 %
UNIX の cat コマンドを Perl で模倣してみます。cat コマンドは、引数でファイル名(複数可能)が指定されればそのファイルを、指定されなければ標準入力を読み込んで、これを標準出力に書き出します。MS-DOS の TYPE コマンドに似ています。cat コマンドの使用例を以下に示します。
% cat file1.txt ← file1.txt の内容を書き出す % cat file1.txt file2.txt ← file1.txt, file2.txt の内容を書き出す % cat *.txt ← *.txt にマッチする全ファイルを書き出す % cat A.txt B.txt > C.txt ← A.txt と B.txt を連結して C.txt に書き出す %
Perl による簡単な模倣スクリプトは次のようになります。while (<>) { ... } は、引数が指定されていればそのファイルを、指定されていなければ標準入力を読み込む機能をもっています。
while (<>) { print; }
実行例は次のようになります。
% perl mycat.pl file1.txt % perl mycat.pl file1.txt file2.txt % perl mycat.pl *.txt % perl mycat.pl A.txt B.txt > C.txt %
cat コマンドの語源は連結(concatenate)で、ファイルを連結する目的も持たせて開発されました。次のようにして、fileA と fileB を連結した fileC ファイルを作成します。
% cat fileA fileB > fileC
ところが、Windows 環境で前述の mycat1.pl を用いてバイナリファイルを連結すると、困ったことになります。ファイルの中にたまたま \n にマッチするデータがあるとこれを \n として読み出し、書き込み時に \r\n に変換してしまいます。この問題を解決するには、読み込み側と書き込み側のハンドルを両方ともバイナリモードに設定してやる必要があります。修正版のコードは次のようになります。
#!/usr/local/bin/perl binmode(STDOUT); if ($#ARGV == -1) { binmode(STDIN); while (<STDIN>) { print; } } else { while ($file = <@ARGV>) { if (!open(IN, $file)) { print STDERR "Can't open $file\n"; next; } binmode(IN); while (<IN>) { print; } close(IN); } }
※ コマンドらしく使用するために、先頭行に #!/usr/local/bin/perl を指定しています。(→「スクリプトを自己起動方式にする」)
UNIX のコマンドである grep を Perl で模倣します。grep は、指定したファイルの中から特定の文字を含む行を探して表示するコマンドです。
#!/usr/local/bin/perl # 引数を得る if ($#ARGV == -1) { print STDERR "使い方: mygrep キーワード [ファイル...]\n"; exit(1); } else { $keyword = shift(@ARGV); } # 標準入力、または指定されたファイルについて処理を行う if ($#ARGV == -1) { doit($keyword, *STDIN, ""); } else { foreach $file (<@ARGV>) { open(IN, $file) || die "Can't open $file\n"; doit($keyword, *IN, $file); # →ファイルハンドルを引数として渡す close(IN); } } # 指定されたキーワードを含む行を表示する sub doit { local($keyword, *FILE, $file) = @_; local($line); while ($line = <FILE>) { if ($line =~ /$keyword/) { if ($file) { print "$file: "; } print $line; } } }
*.htm にマッチするすべてのファイルから文字列 ABCDEFG を含むファイルを検索するには次のようにします。
% perl mygrep.pl ABCDEFG *.htm
UNIX の wc コマンドを Perl で模倣してみます。wc は、引数で指定したファイルそれぞれの、ライン数、ワード数、バイト数を数えて表示するコマンドです。
#!/usr/local/bin/perl $tline = 0; # 総ライン数 $tword = 0; # 総ワード数 $tsize = 0; # 総バイト数 @ARGV = <@ARGV>; for ($i = 0; $i <= $#ARGV; $i++) { open(IN, $ARGV[$i]); $line = 0; $word = 0; $size = (lstat(IN))[7]; while (<IN>) { $line++; s/^\s+//; @cols = split(/\s+/); $word += ($#cols + 1); } close(IN); $tline += $line; $tword += $word; $tsize += $size; printf("%7d %7d %7d %s\n", $line, $word, $size, $ARGV[$i]); } printf("%7d %7d %7d %s\n", $tline, $tword, $tsize, "total");
実行例を下記に示します。各ファイル毎に、左から順にライン数、ワード数、バイト数が表示されます。最終行には合計が表示されます。
% perl mywc.pl *.htm 138 666 5368 app1.htm 447 942 25828 app2.htm 25 40 867 app3.htm : : : : 13391 34177 516654 total
UNIX コマンドの which を Perl で模倣します。which コマンドは、指定したコマンドがどの場所にあるのか、その絶対パス名を探して表示するコマンドです。mywhich.pl は Windows で動作します。
#!/usr/local/bin/perl if ($#ARGV == -1) { print STDERR "使い方: mywhich コマンド名\n"; exit(1); } else { $cmd = $ARGV[0]; } @exts = ("", ".exe", ".com", ".bat"); # 実行可能ファイルの拡張子一覧 $found = 0; # 見付かったかどうかのフラグ # 環境変数 PATH そのそれぞれについて foreach $path (split(/;/, $ENV{'PATH'})) { $path =~ s/\\$//; # それぞれの拡張子について foreach $ext (@exts) { $file = "$path\\$cmd$ext"; if ((-x $file) && (!-d $file)) { print "$file\n"; $found = 1; } } } if ($found == 0) { print "見つかりませんでした。\n"; }
実行例を下記に示します。例ではメモ帳(notepad.exe)のフルパス名を調べています。notepad.exe は、C:\WINDOWS\SYSTEM32 と C:\WINDOWS の下にあることが分かります。
% perl mywhich.pl notepad C:\WINDOWS\system32\notepad.exe C:\WINDOWS\notepad.exe