変数

■ 変数の種類を理解する

◆ 基本的な変数の種類

Perl の変数についてもう一度まとめておきます。主な変数の種類には スカラー変数配列連想配列 があります。

◆ スカラー値

スカラー変数は数値や文字列を保持することができます。

$xx = 123;                             # スカラー(数値スカラー)
$xx = "Japan";                         # スカラー(文字列スカラー)
◆ 配列

配列は、数値をキー(添字)とした、スカラー値の集合です。

$xx[0] = "AAA";                        # 配列(リスト)
$xx[1] = "BBB";                        # 配列(リスト)
$xx[2] = "CCC";                        # 配列(リスト)

次のように記述することもできます。

@xx = ( "AAA", "BBB", "CCC" );         # 配列(リスト)
◆ 連想配列

連想配列は、文字列をキーとした、スカラー値の集合です。

$xx{'NAME'} = "Tanaka";                # 連想配列(ハッシュ)
$xx{'AGE'} = 26;                       # 連想配列(ハッシュ)

次のように記述することもできます。

%xx = ( NAME => "Tanaka", AGE => 26 ); # 連想配列(ハッシュ)
◆ 異なる変数の種類での名前の重複

$xx と @xx と %xx はまったく別物です。$xx の値を変更しても、@xx や %xx に影響を与えることはありません。下記の例では、@xx に ( "XYZ" ) を代入しても、$xx は "ABC" のままです。

$xx = "ABC";
@xx = ( "XYZ" );
print $xx;           # ABC と表示される。

■ 省略できるものは省略する($_)

◆ 省略時変数 $_

print() では、「書き出す値を省略すると省略時変数 $_ の値を書き出す」と定義されています。print() の他にも多くの機能で、$_ は省略時の対象となる変数として使用されます。例えば、下記のそれぞれの 2行は同じ意味を持ちます。

# print における $_ の省略
print $_;
print;

#  における $_ の省略
while ($_ = <IN>) { ... }
while (<IN>) { ... }

# chop() における $_ の省略
chop($_);
chop();

# 正規表現マッチングにおける $_ の省略
if ($_ =~ /To:/) { ... }
if (/To:/) { ... }

次のようなスクリプトを考えてみましょう。

open(IN, "data.txt");
while ($line = <IN>) {
    chop($line);
    if ($line =~ /^From:/) { print $line; }
}
close(IN);

省略時変数 $_ を用いて、これを次のように記述することができます。

open(IN, "data.txt");
while (<IN>) {
    chop;
    if (/^From:/) { print; }
}
close(IN);

省略は Perl の美学なのだそうです。しかし、初心者プログラマーにも分かりやすいプログラミングや、省略時変数 $_ の上書きによるバグの埋め込みにも気を配りましょう。

■ 環境変数を参照・設定する($ENV{...})

◆ 環境変数とは

環境変数 とは、親プロセスから子プロセスに受け継がれる変数です。よく使用される環境変数には PATH(パスリスト)や TZ(タイムゾーン)などがあります。例えば CGI の実行においても、親プロセス(Webサーバー)が、CGIスクリプト(子プロセス)に対してブラウザからの情報を伝えるために使用されています。

◆ 環境変数を参照する

環境変数を参照するには特殊変数 $ENV{...} の値を参照します。例えば環境変数 PATH の値を参照するには次のようにします。

print $ENV{'PATH'};       # 環境変数 PATH の値を表示する
◆ 環境変数に値を設定する

例えば、環境変数 TZ に値を設定するには、次のようにします。

$ENV{'TZ'} = "JST-9";     # 環境変数 TZ に JST-9 を設定する
◆ 主な環境変数
環境変数説明
PATHパス名を省略してコマンドを実行した場合に、コマンドを検索する対象となるフォルダの一覧。
TZタイムゾーン。例えば日本の場合、国名を示す JST と、標準時からの時差を示す -9 から、JST-9 となる。
TEMPテンポラリファイルを作成する際によく使用されるフォルダ。
TMPTEMP とほぼ同義。OS やアプリケーションによって異なる。

その他、CGI でよく使用される環境変数については「CGIで参照可能な環境変数一覧」を参照してください。

■ 型グロブを用いる(*xxx)

◆ 型グロブとは

型グロブ *xx は、$xx や @xx や %xx などの変数の総称のようなものです。例えば、変数 $xx、@xx、%xx が存在するとき、*xx を用いることにより、これらの変数への参照をサブルーチンにまとめて渡すことができます。

下記の例ではサブルーチンは、型グロブ *xx を *zz として受け取ります。これにより $zz は $xx、@zz は @xx、%zz は %xx を示すようになります。

glob.pl
$xx = 123;                                 # スカラー
@xx = ( "AAA", "BBB", "CCC" );             # 配列
%xx = ( NAME => "TANAKA", AGE => 26 );     # 連想配列

func(*xx);

sub func {
    local(*zz) = @_;

    # スカラーとして表示
    print "$zz\n";

    # 配列として表示
    foreach $value (@zz) { print "$value "; }
    print "\n";

    # 連想配列として表示
    while (($key, $value) = each(%zz)) { print "$key=$value, "; }
    print "\n";
}

実行結果は次のようになります。

123
AAA BBB CCC
NAME=TANAKE, AGE=26, 

型グロブを用いて実現するテクニックには以下のようなものがあります。

■ 定数を使用する

◆ 変数を用いる例

Perl には定数の概念が無いので、通常は定数も変数として定義します。

$PI = 3.141592653589793;

これは次のように使用します。

$hankei = 2.0;
$menseki = $hankei * $hankei * $PI;

ただし、$PI の値は簡単に変更するとこができるので、定数とは言えません。

◆ サブルーチンを用いる例

$PI = が、サブルーチンとして定義することにより、変更不可能な定数を表現することができます。下記では、3.14... という値を返すサブルーチン PI と、2.71... という値を返すサブルーチン E を定義しています。

sub PI { 3.141592653589793; }
sub E { 2.718281828459045; }

次のように使用することができます。

$hankei = 2.0;
$menseki = $hankei * $hankei * PI;
◆ インラインサブルーチンを用いる例

値を参照する度にサブルーチンを呼び出すと処理が遅くなるので、サブルーチンに () をつけます。こうすると、サブルーチンは インラインサブルーチン として定義されます。インラインサブルーチンを使用すると、必要メモリは多少増えますが、処理は速くなります。インラインサブルーチンは、そのサブルーチンを利用する箇所よりも前に記述してください。

sub PI () { 3.141592653589793; }
sub E () { 2.718281828459045; }

$menseki = $hankei * $hankei * PI; の計算を通常のサブルーチンとインラインサブルーチンで計測したところ、インラインサブルーチンの方が倍くらい早くなりました。

■ 変数を未定義状態にする(undef)

◆ スカラー変数を削除にする

スカラー変数に対して undef() を実行すると、その変数が削除され、メモリが開放されます。

undef($xx);
◆ 配列や連想配列の要素を未定義状態にする

配列の要素に対して undef() を実行した場合、その要素に「未定義値」という特別な値が代入されます。配列の個数は変わりません。要素の削除には delete() を用いてください。

@xx = ( "AAA", "BBB", "CCC" );
undef($xx[1]);
for ($i = 0; $i <= $#xx; $i++) { print "xx[$i] = $xx[$i]\n"; }

結果は次のようになります。

xx[0] = AAA
xx[1] =
xx[2] = CCC

連想配列の場合も同様です。「未定義値」という特別な値を持つ要素となります。

%xx = ( "AAA" => "aaa", "BBB" => "bbb", "CCC" => "ccc" );
undef($xx{'BBB'});
while (($name, $value) = each(%xx)) { print "$name = $value\n"; }

結果は次のようになります。

CCC = ccc
BBB =
AAA = aaa
◆ 配列や連想配列を削除する

@xx や %xx に対して undef を行うと、配列や連想配列内のすべての要素を削除してメモリを開放します。

undef(@xx);
undef(%xx);

■ 要素を削除する(delete)

◆ 要素の削除

delete() は連想配列の要素を削除してメモリを開放します。スカラー変数や、配列変数や、連想配列全体を削除するには undef() を用いてください。perl v5.6 では配列要素に対しても delete の使用が可能になりました。$xx[2] を削除しても $xx[3] が $xx[2] にシフトすることはありません。この場合の削除には splice() を用いてください。配列の最後の要素が削除された場合は、$#配列名 の値も変動します。

# delete($xx);    使用不可
# delete(@xx);    使用不可
# delete(%xx);    使用不可
delete($xx[0]);
delete($xx{'AAA'});

undef された要素は defined() では偽になりますが、exists() では真になります。delete された要素は、defined() でも exists() でも偽になります。

■ 複数の変数を一度に削除する(reset)

◆ 変数の一括削除

reset() は、スカラー値、配列、連想配列に関わらず、指定した変数をすべて削除します。"x" は x で始まるすべての変数、"ab" は a または b ではじまる全ての変数、"a-z" は小文字で始まるすべての変数を削除します。

reset "x";     # 変数 x* をリセット
reset "ab";    # 変数 a*、b* をリセット
reset "a-z";   # 変数 a* ~ z* をリセット

reset "A-Z" とすると、@INC や %ENV まで削除してしまうので注意が必要です。とてもマニアックなスクリプトを書くとき以外、この機能を利用することはないでしょう。

■ 変数や要素の定義状態を調べる(defined, exists)

◆ 定義されているかを調べる(defined)

defined() は、変数が定義されており、かつ、「未定義値」以外の値をもっているかどうかを調べます。

if (defined($xx)) { print "$xx\n"; }
if (defined($xx[0])) { print "$xx[0]\n"; }
if (defined($xx{'AAA'})) { print "$xx{'AAA'}\n"; }
if (defined(@xx)) { print "\@xx\n"; }
if (defined(%xx)) { print "\%xx\n"; }
◆ 存在するかを調べる(exists)

exists() は、連想配列の要素が存在するかどうかを調べます。たとえ、要素が undef されていても、要素としては存在するので、exists は真を返します。perl v5.6 以降では配列にも対応しています。

# if (exists($xx))    使用不可
# if (exists(@xx))    使用不可
# if (exists(%xx))    使用不可
if (exists($xx[0])) { print "$xx[0]\n"; }
if (exists($xx{'AAA'})) { print "$xx{'AAA'}\n"; }
◆ defined と exists の違い

配列や連想配列の要素には、「存在しているけど定義されていない」という状態があります。下記の場合、$xx[0] は存在していて値もある、$xx[1] は存在しているけど未定義の状態、$xx[2] は存在していない定義もされていないという状態になります。

%xx = ( "AAA" => "aaa", "BBB" => "bbb", "CCC" => "ccc");
undef($xx{'AAA'});
delete($xx{'BBB'});
foreach $key ("AAA", "BBB", "CCC") {
    if (exists($xx{$key})) { print "xx{$key} exists\n"; }
    if (defined($xx{$key})) { print "xx{$key} defined\n"; }
}

これを実行すると次のようになります。

xx{AAA} exists
xx{CCC} defined
xx{CCC} exists

■ 変数名を動的に作成する($$xxx)

◆ 変数名の動的生成

$name の値が "To" の時、${$name} は $To と同じ意味を持ちます。この仕組みを利用して、変数名をダイナミックに生成することができます。

$name = "To";
${$name} = 'aaa@xxx.yyy.zzz';     # $To = '...' と同じ意味
print "$To\n";

混乱が無ければ、${$name} は $$name と記述することもできます。

$$name = 'aaa@xxx.yyy.zzz';       # $To = '...' と同じ意味
◆ 応用例

例えば、次のような内容のファイル(data.txt)があるとします。

NAME=Tanaka
AGE=26
ADDRESS=Tokyo

これを次のようなプログラムで読みこむことにより、各行の値を $NAME、$AGE、$ADDRESS で参照できるようになります。

open(IN, "data.txt");
while ($line = <IN>) {
    $line =~ s/[\r\n]*$//;       # 改行コードを削除
    $line =~ /([^=]*)=(.*)/;     # 「名前=値」に分解
    if ($1) { ${$1} = $2; }      # $名前 = 値
}
close(IN);

print "NAME = $NAME\n";
print "AGE = $AGE\n";
print "ADDRESS = $ADDRESS\n";

実行結果は次のようになります。

NAME = Tanaka
AGE = 26
ADDRESS = Tokyo

■ リファレンスを用いる(\$xxx, $$xxx)

◆ リファレンス

リファレンスは Perl 5 で新しく実装された型です。C言語のポインタに相当します。ある変数のリファレンスを得るには \ を用います。

$name = "Tanaka";
$ref = \$name;

$ref を表示すると例えば、SCALAR(0x17652cc) のように表示されます。これは、$name というスカラーの値が、メモリの 0x17652cc 番地に SCALAR 型で格納されていることを示します。

print "$ref\n";           # SCALAR(0x17652cc) と表示される
◆ デリファレンス

リファレンスから元の値を得るには $$変数名 を用います。これを デリファレンス と呼びます。

print "$$ref\n";              # Tanaka と表示される
◆ リファレンス先の型を求める(ref)

ref() は、引数が参照値であれば、その元の型によって SCALAR、ARRY、HASH、CODE、REF、GLOB、LVALUE などの 型名(→型のいろいろ)を返します。

print ref($ref) . "\n";   # SCALAR と表示される
◆ 応用例

リファレンスを用いて実現されるテクニックには次のようなものがあります。

■ 表形式のデータを保持する

◆ 無名参照

$p = [ ]; や、$p = { }; は、無名配列無名連想配列 へのリファレンス(無名参照)を生成します。無名参照は、それを参照する変数がひとつ以上ある間存在し、無くなると自動消滅します。

$p = { };
$p->{NAME} = "Tanaka";

無名参照を用いてたとえば、下記のような表形式のファイルを読み込むことができます。

Tanaka 26 Tokyo
Suzuki 32 Osaka
Yamada 18 Nagoya

スクリプトは次のようになります。

open(IN, "data.txt");
while (<IN>) {
    s/[\r\n]*$//;                         # 改行を削除
    ($name, $age, $addr) = split(/\s+/);  # ホワイトスペースで分割
    $p = {};                       # 無名参照オブジェクトを生成
    $p->{NAME} = $name;            # 名前(NAME)を設定
    $p->{AGE} = $age;              # 年(AGE)を設定
    $p->{ADDR} = $addr;            # 住所(ADDR)を設定
    push(@persons, $p);            # 無名参照オブジェクトを配列に加える
}
close(IN);

foreach $p (@persons) {
    print "NAME=$p->{NAME}, AGE=$p->{AGE}, ADDR=$p->{ADDR}\n";
}

実行結果は次のようになります。

NAME=Tanaka, AGE=26, ADDR=Tokyo
NAME=Suzuki, AGE=32, ADDR=Osaka
NAME=Yamada, AGE=18, ADDR=Nagoya

Copyright (C) 2002 杜甫々