入出力(4) @ARGV 変数で引数処理

プログラムを起動する際に渡す引数を、「コマンドライン引数」「パラメータ」もしくは単に「引数」などと呼びますが、Perlスクリプトを実行する際にもこの引数を渡す事ができます。
スクリプトを実行する際に渡された引数は、コードの中では特殊変数 @ARGV で参照する事ができるそうです。なぜ配列変数かというと、空白区切りで複数の引数を与える事ができるからですね。
試しに、以下のようなスクリプトを書いて「printer.pl」として保存しました。

# printer.pl
use strict;
 
for (my $i = 0; $i < @ARGV; $i++) {
  print $i, ":", $ARGV[$i], "\n";
}

内容は @ARGV の要素を番号付きで表示するだけです。
そして、このスクリプトを引数付きで実行してみました。スクリプトに引数を渡すには、単に perl コマンドを呼び出す際に、後ろに追加していけば良いみたいですね。

D:\home\palmo>perl printer.pl aaa bbb ccc
0:aaa
1:bbb
2:ccc

"aaa bbb ccc" が実際に渡した引数ですが、自動的に空白で区切られて @ARGV に格納されているのがわかりますね。$ARGV[0] が1番目の引数になっています。C言語では argv[1] が1番目の引数だったので、ちょっと違いますね。
ちなみに、C言語の argv[0] にはプログラム自分自身のファイル名が入りますが、Perl ではスクリプト自分自身のファイル名は $0 という特殊変数で参照できます。

# showself.pl
 
print "Hello! I am $0";
D:\home\palmo>perl showself.pl
Hello! I am showself.pl


@ARGV を使って逆ポーランド記法による計算機を作ってみます。(^_^)
「1 + 2」のように数字と数字の間に演算子を置く記法を「中置記法」と呼ぶのに対し、逆ポーランド記法は「後置記法」と呼ばれ、「1 2 +」のように演算子を後ろに置きます。
「2 + 3 * 4 / 6」のような中置記法で書かれた数式を計算しようとすると、演算子の優先順位を考えなくてはいけないので式の最後まで確かめないと計算を始められませんが、逆ポーランド記法では「2 3 4 * 6 / +」となり、計算は常に左から行ないます。しかもスタックがあれば簡単に計算できます。

  1. 式から一番左のトークン(数字か演算子)を1つ取り出す
    1. 数字なら
      1. それを計算用スタックに積む
    2. 演算子なら
      1. 計算用スタックの一番上から2つの数字を取り出す
      2. 2つの数字と演算子で計算する
      3. 計算結果を計算用スタックに積む
  2. 1 に戻る

を繰り返せば、最終的に計算結果が計算用スタックに積まれます。


作成する計算機に渡す事ができるのは数字と四則演算子(+-*/)だけにします。Perl なので特に意識しなくても小数や16進数は扱えそうですね。
というわけで作ってみたら、案外すんなりと書けました。

use strict;
 
sub calc_rpn {
  my @stack = ();
  for my $token (@_) {
    if ($token =~ m|^[-+*/]$|) {
      my ($left, $right) = splice @stack, -2, 2;
      push @stack, eval("$left$token$right");
    } else {
      push @stack, $token;
    }
  }
 
  return $stack[0];
}
 
if (defined(@ARGV)) {
  print "The answer is ", calc_rpn(@ARGV), "\n";
} else {
  print "No arguments given.\n";
}

calc_rpn 関数は逆ポーランド記法RPN: Reverse Polish Notation)のトークンリストを受け取って計算した結果を返します。
まず計算用スタックとなる @stack を初期化しておき、渡されたトークンリストの中のトークンを1つずつ処理していきます。
トークンが演算子(-+*/)なら、スタックから末尾2つを $left $right として取り出して、演算子でつなぎ、eval で計算します。計算した結果をスタックの末尾に push で積みます。
トークンが演算子以外なら、項とみなしてそのままスタックに積みます。
これでトークンリストを処理し終えると、式の文法が正しければ $stack[0] が答えとなるはずです。エラーチェックをあまりしていないのはご勘弁を。(^_^;)


作成したスクリプトrpn.pl として保存して、実行してみました。

D:\home\palmo>perl rpn.pl 1 2 + 5 +
The answer is 8

D:\home\palmo>perl rpn.pl 2 3 4 * 6 / +
The answer is 4

D:\home\palmo>perl rpn.pl 3 2.5 * 0x10 +
The answer is 23.5

ちゃんと計算できています。小数や16進数が混ざっていても計算できるのは、eval のおかげですね。(^_^)
実は eval しているので、こんな事もできてしまいます。

D:\home\palmo>perl rpn.pl 1 sqrt(5) + 2 /
The answer is 1.61803398874989

\frac{1+\sqrt{5}}{2}黄金比です。なんと、関数の呼び出し(sqrt)もできてしまいます。


便利ですが、これは一歩間違えれば大変危険になるので注意しなくてはいけませんね。渡している引数をそのまま評価している事になるからです。
例えばこれをパラメータを渡せば計算結果を表示する、という CGI に加工して、ウェブ上に設置したらどうでしょう。すると、パラメータとしてコードを渡せば、誰でも好きなコードをサーバー内部で実行できてしまうのです。大変危険な状態です。
eval を使わずに、演算子の種類によって計算を行うようにすれば、この問題は避けられますが、関数の呼び出しは自力で実装しなくてはなりません。
難しいところです。(^_^;)