[Perl入門に戻る]
(perlの第1歩) (出力の工夫) (ファイルの読み込みと行の処理) (ファイルの入出力--補足) (文字列の処理)

Perl の第1歩 -- cal.pl の解説 --

cal.pl の中身は次のようになっています。 まずこれを理解することから始めます。 なお、行頭の数字およびその後ろの空白は説明のため便宜上つけたものです。
 1 #!/usr/bin/perl
 2 
 3 # コマンドライン引数をもらう
 4 die "usage: cal month year\n" unless @ARGV == 2;
 5 ($mon, $year) = @ARGV;
 6 
 7 # その月の末日を計算(2行目は閏年の計算)
 8 $lastday = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[$mon - 1]
 9          + ($mon == 2 && ($year % 4 == 0 && $year % 100 != 0 || $year % 400 == 0));
10 
11 print "      $year/$mon\n S  M Tu  W Th  F  S\n";
12 
13 foreach(("  ") x &getweek($year, $mon), 1 .. $lastday){
14     printf ('%2.2s ' , $_);
15     print "\n" unless ++$days % 7;
16 }
17 
18 print "\n"; exit;
19 
20 # 曜日を得る関数
21 sub getweek{
22     local($year, $month) = @_;
23     if($month == 1 || $month == 2) {$year--;$month += 12;}
24     int($year + int($year/4) - int($year/100) + int($year/400) + int((13*$month+8)/5) + 1) % 7;
25 }

まず、次のことを覚えましょう。

それでは、 cal.pl を読んでいくことにします。

1行目:
#!/usr/bin/perl
すでに Perl入門のページで解説しましたが、 これを第1行に記述し、ファイルに実行属性をつけておけば、 ファイル名を指定するだけでスクリプトを実行することができます。 Windows では .pl という拡張子を perl/jperl のプログラムに関連づけしておけば この記述なしに同様のことを行なうことができます。 DOS でもバッチファイルの記述を工夫してスクリプトを実行する手段があるようです。


4行目:
die "usage: cal month year\n" unless @ARGV == 2;
これは単文です。単文は「式1;」または 「式1 修飾子;」の形をしています。 今の場合「die "usage: cal month year\n"」の部分が式1で 「unless @ARGV == 2」が修飾子です。
まず修飾子から見てみます。 一般に修飾子は次のいずれかの形をしています:
具体例
if式2if ($xyz > 0)
unless    式2    unless ($xyz == 0)
while式2while ($xyz != 0)
until式2until (sin($xyz) <= cos($uvt))
「式」は通常、関数・演算子・変数・定数などから構成されており、 その「値」を持ちます。 上の例では式2のまわりを ( ) で囲んでその値をはっきり評価させていますが、 囲まなくてもかまいません。 上の式2の例ではいずれも1(真)か0(偽)を返す2項演算子が用いられています。 一般に 0 以外の値は真を表します。 if/unless/while/until は常識的な意味を持ちます。
よく使う演算子には次のようなものがあります:
演算子説明使用例
=代入$a = 10
+
-
*
/
加算
減算
乗算
除算
$a + $b
%除算の余り$a % $b
**べき乗$a ** 3
.文字列の連結$filename . ".bak"
x文字列や配列の繰り返し"abc" x 4
("abc") x 4
++
--
インクリメント
デクリメント
++$a
$a++
&&
||
論理積
論理和
($a > 0) && ($b > 0)
==
!=
数値として等しい
等しくない
$a == 0
<
>
より小さい(数値として)
より大きい(数値として)
$a > 0
<=
>=
より小さいか等しい(数値として)
より大きいか等しい(数値として)
$a >= 0
eq
ne
文字列として等しい
等しくない
$str eq "abc"
lt
gt
より小さい(文字列として)
より大きい(文字列として)
$str lt "abc"
le
ge
より小さいか等しい(文字列として)
より大きいか等しい(文字列として)
$str ge "abc"
=~パターンマッチ, 変換, 置換$str =~ m/abc/

ここで一番上の代入演算子について注意をしておきます。 例えば仮に $a の値が 10 だとし、「$a = 12」という式を考えます。 これは式としては 12 という値を持ちます。 しかしこの式を書く意味は 12 という値自体にあるのではなく、 $a の前の値を捨てて 12 という新しい値を代入するという副作用の ほうが重要なのです。 いくつかの演算子はその値よりもむしろその副作用の方に意味があります。 これから学ぶ「関数」も、同様に、その値に意味がある場合もあれば、 むしろその副作用により大きな意味がある場合もあります。 これが数学における演算子や関数と異なる点です。

さて、4行目の場合の式2は「@ARGV == 2」です。ここででてくる@ARGVとは スクリプトが実行されたときに付加された引数(パラメータ)の作る配列です。 例えばもし

           cal.pl 9 2000
のように実行されたとすると @ARGV は ("9", "2000") となります。 比較演算子 == の右辺は数値であるにもかかわらず、左辺は配列です。 このような場合は左辺の配列に対してその要素の個数を対応させ、 その数値が右辺と比較されます。 このスクリプトは「月」と「年」の2つの引数を指定して実行するように なっていますので、引数の個数が2個であるかどうかを調べて、 もし2個でない場合(unless!)、式1が評価されます。 単文の場合、式1の値よりむしろその副作用の方に意味があります。 式1は die という関数とその引数からなりたっています。 今重要なのは、この関数の値ではなく、 指定された文字列を画面に出力してプログラムを終了するという副作用です。

まとめると、第4行の意味は「もしスクリプトの引数の個数が2個でなければ、 usage: cal month yearという文字列(プラス改行文字)を画面に出力して スクリプトの実行を終了せよ」という命令になります。


5行目:
($mon, $year) = @ARGV;

この行に到達しているということは、@ARGV の要素の個数が2であるということです。 ここでは @ARGV という配列の2つの要素のうち最初のものを $mon 変数に、 2番目のものを $year 変数に代入しています。


8行目〜9行目:
$lastday = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[$mon - 1]
          + ($mon == 2 && ($year % 4 == 0 && $year % 100 != 0 || $year % 400 == 0));

この行では $mon と $year を使ってなにやら計算し、その結果を $lastday に代入しています。代入演算子 = の右辺があまりに長いので途中で改行して2行に渡っています。 実は、第7行のコメントにあるように、これは指定された月に何日あるかを計算しているのです。 $mon が 2 でうるう年のときは通常の日数に 1 を加えています。


11行目:
print "      $year/$mon\n S  M Tu  W Th  F  S\n";

print は指定された文字列あるいはコンマで区切られた複数の文字列を(順に) 画面に出力する関数です。 引数が省略された場合は $_ という特殊変数の値が出力されます。 ここでは、カレンダーの第1行目と第2行目が出力されます。 文字列が " で囲まれていますので、 $mon や $year にはコマンドラインで指定された 月や年がはいります (数値は10進数表示の文字列と見なされます)。 第4行目でもでてきましたが、「\n」は改行文字を表しますので、 文字列はここで改行して出力されます。


13行目〜16行目:
foreach(("  ") x &getweek($year, $mon), 1 .. $lastday){
    printf ('%2.2s ' , $_);
    print "\n" unless ++$days % 7;
}

13行目の終わりから16行目にかけて { と } で囲まれた部分があります。 一般に単文などのコマンドを並べて { } で囲んだものをブロックといいます。 ブロックは以下のようなフロー制御の複合コマンド中で用います。

      if (式) ブロック
      if (式) ブロック1 else ブロック2
      if (式1) ブロック1 elsif (式2) ブロック2 .... else ブロックn
      while (式) ブロック
      for (式1; 式2; 式3) ブロック
      foreach 変数 (配列) ブロック
foreach の複合コマンドでは、配列の各要素が順に forach の直後に記述される変数に代入され、 ブロックが1回ずつ実行されます。 変数名が省略されているときは $_ という特殊変数に代入されます。 実際には見やすくするために上の13行目〜16行目のように適当な改行やインデントを行ないます。

13行目で指定された配列は、連続する2個の空白文字からなる文字列をある個数だけ並べた配列に、1 から $lastday までの数値を順に追加してできる配列です。 その「ある個数」とは下の21行目から25行目で定義された getweek という関数(サブルーチン)によって定められています。 これに関してはあとで見ますが、考えている月のついたちが何曜日であるかを 0(日曜)から 6(土曜)の数値で与えるものです。 この数値は、その月のカレンダーでついたちの前の空欄を何個用意すればよいか、 という数に一致します。

ブロック内では、第14行では printf という関数が用いられています。 これは print と同様文字列を出力しますが、「フォーマット(書式)」を指定することができます。 この場合では "%2.2s "という部分がフォーマットを指定する文字列です。 そのフォーマット文字列中の%2.2sのような部分を変換文字列といい、フォーマット文字列の後ろに並ぶ変数や定数が順に、指定された変換により文字列になり変換文字列の部分に代入されます。

変換文字列は % で始まり変換文字で終わります。 その中間にオプションが指定できます。

よく使う変換文字
 d 対応する引数を10進数として出力
 f 対応する引数をddd.dddd の書式で出力
 s 対応する引数を文字列として出力
オプション使用例
 %6d 6桁のフィールドに右詰めで表示。余ったフィールドは空白。
 %-6d 6桁のフィールドに左詰めで表示。余ったフィールドは空白。
 %06d  6桁のフィールドに右詰めで表示。余ったフィールドは0で埋める。
 %6s  6桁のフィールドに右詰めで文字列を表示。余ったフィールドは空白。
 %6.2s  6桁のフィールドに右詰めで最初の2文字のみ表示。余ったフィールドは空白。

実験用のスクリプトで試して下さい: testprintf.pl -- スクリプトおよび出力

結局 14 行目では空白または日付が2桁表示の右詰めで出力され、 その後ろにスペースがひとつ追加されます。

15行目では、まず $days が 1 増やされた後 (これでカレンダーの左上からのマスの位置がわかります)、 $days が 7 で割り切れるときのみ(つまり土曜日であるときのみ)、 改行文字を出力します。


18行目:
print "\n"; exit;

ここでは最後にもう一度改行して、スクリプトの実行を終了します。


21行目〜25行目:
sub getweek{
    local($year, $month) = @_;
    if($month == 1 || $month == 2) {$year--;$month += 12;}
    int($year + int($year/4) - int($year/100) + int($year/400) + int((13*$month+8)/5) + 1) % 7;
}

ここでは13行目で使われている getweek という関数(サブルーチン)を定義しています。 サブルーチンは使う前に定義してもいいですし、この例のように後ろでまとめて定義してもかまいません。 ただ、後ろで定義するときは getweek を使用する時、これはサブルーチンである、 ということを知らせるために先頭に & をつけて用います。

この関数は引数を2つとることを前提にしています。 関数の引数の配列は @_ で与えられます。 22行目で、その2つの成分を局所変数 $year と $month に 代入しています。 この $year と上にでてくる $year は同じ名前ですが、こちらの $year の方は、 この関数の定義内だけで有効な変数ですので、へんなことがおこらないかと心配する必要はありません。 与えられた $year と $month から、Zeller の公式を使ってその月のついたちの曜日を求めています。 これがこの関数の値となります。 なお、int() は与えられた数値の整数部分だけを取り出す関数です。


以上、超特急で perl について学び cal.pl を読みました。 少し、わかってきたような気がしますか?
[Perl入門に戻る]