Perl 再入門 : 配列・連想配列 (ハッシュ)・関数呼び出し・例外処理

Perl 再入門、第2弾。

目次

配列を扱う

Perl での配列の扱い方。

# 配列の定義 : 「@」で定義する
my @fruits = ("apple", "orange", "banana");

# 添字で指定して取り出す : 単一要素を取得する際は「$」を使う
print $fruits[0];

# 配列の長さを取得する (JavaScript でいう array.length)
print @fruits;

# 配列の最終要素の添字を取得する (JavaScript でいう array.length - 1)
print $#fruits;

# for を使って順に表示する
for(my $i = 0; $i < @fruits; $i++) {
  print $fruits[$i];
}
# next; → Java でいう continue 相当
# last; → Java でいう break 相当

# foreach を使って順に表示する
foreach my $fruit (@fruits) {
  print $fruit;
}

push(@fruits, "peach");     # 末尾に追加
pop(@fruits);               # 末尾から削除
unshift(@fruits, "peach");  # 先頭に追加
shift(@fruits);             # 先頭から削除

こんな感じ。個人的についつい JavaScript との比較になってしまうが、よく似ているかなと思う。

特徴的なのは、配列変数の宣言時にアットマーク @ を使う点。変数を見るだけで配列だなと分かるのはありがたい。

use strict; 前提で書いているので、for 文における $iforeach 文における $fruit の宣言時にも my を使用している。

連想配列 (ハッシュ) を扱う

Perl で連想配列、いわゆるハッシュを作るには、以下のように書く。

# 変数定義はドル「$」ではなくパーセント「%」
my %myHash = (
  "name" => "my-hash",
  "version" => "0.0.0"
);

# とりあえず全部出力する
print %myHash;

他に、無名ハッシュ {} というモノを使って、ハッシュへのリファレンス (参照) を作るやり方もある。

# 変数定義はドル「$」、つまりスカラ変数
my $myRecord = {
  "name" => "my-record",
  "version" => "0.0.0"
};

# とりあえず全部出力する (リファレンスをデリファレンスして取り出す)
print %{$myRecord};

リファレンスっていうのが謎い。

ハッシュから値を取り出す

ハッシュから特定の値を取り出すには以下のように書く。

# ハッシュ
my %myHash = (
  "name" => "my-hash",
  "version" => "0.0.0"
);
print $myHash{'name'};

# ハッシュのリファレンス
my $myRecord = {
  "name" => "my-record",
  "version" => "0.0.0"
};
print $myRecord->{'name'};

JavaScript の myHash.name とか myHash['name'] みたいな書き方と同じ。ハッシュのリファレンスから取り出す時は -> が必要になるのが違い。

連想配列からキーや値を全部取り出す

Key と Value を全部取り出して順に出力したりするには、each 関数を使ってこんな感じに書く。

# ハッシュ
my %myHash = (
  "name" => "my-hash",
  "version" => "0.0.0"
);
while(my ($hashKey, $hashValue) = each %myHash) {
  print $hashKey . " : " . $hashValue . "\n";
}

# ハッシュのリファレンス
my $myRecord = {
  "name" => "my-record",
  "version" => "0.0.0"
};
while(my ($recordKey, $recordValue) = each %{$myRecord}) {
  print $recordKey . " : " . $recordValue . "\n";
}

キーだけ取るなら keys・値だけ取るなら values 関数がある。

JavaScript の場合、連想配列の順序はわりかし保たれている印象があるが、Perl だと実行する度にコロコロと順序が変わる。

環境変数を引っこ抜くサンプル

ハッシュの扱い方が分かってきたので、試しに環境変数のハッシュである %ENV の中身を画面に表形式で出力してみる。

print "<table border=\"1\">";
while(my ($hashKey, $hashValue) = each %ENV) {
  print "<tr><th>" . $hashKey . "</th><td>" . $hashValue . "</td></tr>";
}
print "</table>";

コレだと出力順序がコロコロ変わるので、keys 関数でキーを先に取得し、それを sort してから出力してみよう。

# %ENV からキーを取得し昇順にソートする
my @keys = keys %ENV;
my @keys = sort { $a cmp $b } @keys;

print "<table border=\"1\">";
foreach my $key (@keys) {
  print "<tr><th>" . $key . "</th><td>" . $ENV{$key} . "</td></tr>";
}
print "</table>";

コレで良い感じ。REQUEST_METHOD とか QUERY_STRING とかが見えるので、CGI での処理も良い感じにできそう。

カレントディレクトリ配下のファイル名を取得するコードを元に、関数の呼び出し方について

Perl の opendir という関数を使うと、Perl CGI ファイルがあるディレクトリを起点に、ディレクトリとファイルの一覧が取得できる。

# 変数 $dir はファイルハンドル。ココではディレクトリを読み込んだり閉じたりする時に使う
my $dir;
opendir $dir, "./";
my @files = readdir $dir;
closedir $dir;

# 取得したファイル名を順に表示する
foreach my $file (@files) {
  print $file;
}

opendirreaddirclosedir は組み込みの関数。関数呼び出しはカッコ () を付けても付けなくても呼べる。

my $dir;
opendir($dir, "./");
@files = readdir($dir);
closedir($dir);

カッコの有無は、標準関数やモジュールからインポートした時の場合は特に変わりがない。sub でサブルーチンを定義した場合に、関数定義よりも手前に関数呼び出しがある場合は、カッコ () が必須。

また、関数名の手前にアンパサンド & を指定しても関数呼び出しができる。

sub myTest {
  print "\nmyTest\n";
}
# 関数定義が手前にあるので、以下のどれでも動く
myTest;
myTest();
&myTest;
&myTest();
# 関数定義が後にあるので、コチラは実行されない
myTest;
# ↓ コチラなら実行される
myTest();
&myTest;
&myTest();

sub myTest {
  print "\nmyTest\n";
}

どういう使い分けのルールがあるのかよく分からないが、こんな意見もあった。

◆ 関数名の後の『()』小カッコについて
組み込み関数やインポートしたモジュール関数は、『()』小カッコを付けない方がよい。
カッコを付けてもOKですが、ユーザ定義関数以外であることを明示するために『()』を付けない方がよい。

ユーザが定義できる関数(サブルーチン)は、『()』小カッコを付ける。
関数をロジックの前置きに定義すると、カッコなしでもOKですが、そうしない。『()』小カッコを付けてユーザ定義関数を明示する。

呼び出し元の書き方の違いで、ユーザ定義関数だと分かるように、ユーザ定義関数にはカッコを付ける、というのが良いのかも。

例外処理

Perl の標準では、trycatch という構文はない。代わりに evalif を使った書き方が多い。

my $dir;
eval {
  opendir($dir, "./no-dir") or die "Can't open the directory. $!";
};
if($@) {
  print("MyError : $@");
  exit 1;
}

# 正常時の処理…
my @files = readdir($dir);
closedir($dir);

evaltry 部分。例外を吐きそうなところには or die() という関数を使い、throw するエラーメッセージを用意しておく感じ。$! とすると標準のエラーメッセージが返せるので、or die($!) が一番シンプル。eval はブレースで囲まれているがセミコロンが必要なので、eval {}; となる。

直後の if ブロックが catch 相当。$@ がエラーメッセージになるので、その有無で判定する。

もし上手くいかない場合は、こんな内容が print される。

MyError : Can't open the directory. at /Library/WebServer/CGI-Executables/test.cgi line 16.

line 16 というのは opendir 部分のこと。

今回はココまで

配列や連想配列の扱い、ファイルやディレクトリの操作、例外のハンドリング方法などが分かってきて、段々と CGI でよくやる処理への備えができてきた感じ。