Hachioji.pm 日めくりテックトーク

Perlの一文字変数の攻略

xtetsujiです。

昨今は書籍よりも検索エンジンに頼って勉強する人が多くなってきているのかもしれませんが、そういう人を戒めるかのようにPerlでは「ググれない」一文字変数や一文字シンボルやそれに類するものの数々があります。

実際のところ、ちゃんと書籍を通読したり、perldoc perlvar perldoc.jp/perlvar を読めば理解できるのですが、それでもハマる落とし穴といったところを解説していきたいとおもいます。

use English; は使わない

perlvar の解説を読んでいると、一文字変数が嫌な場合は

use English;

とすれば $_$ARG として参照できて嬉しいよ、といった解説が行われていますが、通常 use English; は使いません。というか、use English; を使ったプロジェクトを内外で見聞きしたことが10年の間に私の経験上ありません。

PerlやPerlプログラマは $_ 等をあまりにも自明な変数として扱います。突然 $ARG と書かれても、逆に混乱の元になるだけでしょう。また、そのまま use English; をすると正規表現のパフォーマンスを落としてしまうという罠もあります。

よく使うものだけを覚える

最初にPerl (perlvar) と向きあうと「これを全部覚えないといけないのか…」となったりしますが、そんなことはありません。かくいう私も、10年Perl書いていて意味までスラスラ出てくるのは一部です。分からなければ都度調べればいいんです。

迷えるあなたのため、そんな「これだけは覚えておけばOK」という頻出の一文字変数や一文字シンボルの数々をご紹介します。

$_: 暗黙の変数

Perlのありとあらゆる局面で変数が省略されたときに省略されたものとみなされる変数。最重要変数です。これを押さえればPerlの一文字変数の80%は理解したようなもの(言い過ぎ)。

アンダースコア変数と呼ばれたりすることもありますが、英語でいう it と似たような意味だととえらえてもらえると理解しやすいでしょう。

詳しくは perlvar を見ていただくとして、例えば以下の場合に $_ が参照されます。

  • 特定の組み込み関数や単項演算子の引数が省略された場合に $_ が指定されたものとみなす
  • =~ を伴わないパターンマッチ変数が置かれた場合、 $_ をパターンマッチしようとする
  • for/foreach ループで、リストを受け取る変数を指定しなかった場合、$_ がそれを受け取るようになる
  • grep や map の繰り返し変数
  • while の条件テスト部分で (readline) をそのまま置いた場合に読み込んだ行が $_ に入る

特定の組み込み関数や単項演算子で省略された場合の引数とするケースは少ないと思います。たとえ map で変換する場合にも省略しないほうがよいと思います。

# sqrt() 組み込み関数の第一引数は map の繰り返し変数 $_ だから省略可能
# my @squared = map { sqrt } @numbers;
# だけどこう書きたい
my @squared = map { sqrt($_) } @numbers;

単純な例であれば良いのですが、積み重なってくるとわけがわからなくなってきますし、曖昧さからパースエラーになってしまう可能性もあります。

# ちょっと複雑になった (defined と length の引数省略時は $_ を見る)
# これを許容するかどうかはコーディングルールなどに依存する
my @existed = grep { defined && length } @whole;

どんな場合もチームのコーディングルールだと思うので、それがあるのであればそれに従いましょう。

while(){ ... } 句の読み込みではよく $_ が使われます。

while ( my $line = <STDIN> ) {
    chomp $line;
    if ( $line !~ /^\s+#/ ) { # コメント行を無視
         print $line, "\n";
    }
}

と書くよりも

while (<STDIN>) { # $_ への暗黙の代入が発生する
     chomp; # $_ への chomp が発生する (末尾改行除去)
     if ( !/^\s+#/ ) { # コメント行を無視
          print "$_\n";
     }
}

と書くほうが、個人的に使うプログラムでは簡便でしょう。

Perl 5.10 からグローバル変数 $_ のレキシカル化が可能となって my $_ などと書けるようになりましたが、どちらかというとまだ馴染みのない記法のような気がします。概念としては難しいですが、グローバル変数の局所化である local を使うほうが良さそうです。

for (1..100) { # $_ に 1から100まで入る
    {
        # この中の $_ は $text と同じ意味になる
        local $_ = $text;
        # パターンマッチ演算子は $_ に対しては $_ =~ を省略可能
        s/censored//g;
        s/自主規制//g;
        s/ダメキーワード//g;
        ...
        $text = $_;
    }
    # ここからは $_ は for で代入された数字に戻る
    print "step is $_\n";

$_ の世界は奥深いのですが、その深層はこの辺にしましょう。また次の機会で。

@_: 暗黙の配列

サブルーチンを定義した場合に、引数が入っている配列です。

abc("foo", "bar", "buz", "quux");

sub abc {
    print "abc is ", "@_", "\n";
}

可変長変数でない場合、通常は、@_ のまま使わず、

sub abc { 
    my $foo = shift @_;
    my $bar = shift @_;
    my $buz = shift @_;
    my $quux = shift @_;
    ...
}

などとするか、shift / unshift / push / pop の省略時の配列が @_ であることを利用して

sub abc { 
    my $foo = shift;
    my $bar = shift;
    my $buz = shift;
    my $quux = shift;
    ...
}

と書くことが多いでしょう。Perlのサブルーチンを定義すると shift だらけになるのですが、それが嫌であれば

sub abc { 
    my ($foo, $bar, $buz, $quux) = @_;
    ...
}

などとしてもよいのではないでしょうか。もちろん後続の処理で @_ の「残りの長さ」を見ている場合には通用しないかもしれませんが。このあたりもコーディングルールに依存する部分ですね。

いにしえのPerlでは、丸括弧なし & 付きのサブルーチン呼び出しで、現在のスコープの @_ がサブルーチン内の @_ に渡されるという機能がありましたが、これは現在では見かけませんし、推奨されない記法でしょう。

@_ = qw(sapporo sendai tokyo osaka fukuoka);
&abc; # 上の @_ の内容が「代入」される
sub abc {
    print "@_";
}

その他、無効コンテキスト (左辺値への代入がない) で split 関数を使うと、split した結果が @_ に入るという機能が昔のPerlにありましたが、Perl5.12あたりで無効化されたようですね。昔から推奨されなかった書き方ですので大きな問題はないとは思いますが、レガシーなソースコードを抱えている方は一度 split の使い方を確認してみるとよいかもしれません。

$$: 現在のプロセスID

リファレンスを習いたての人だと、スカラーリファレンスのデリファレンスと見間違えそうな感じもしますが、単体で $$ と書くと、現在のPerlプロセスのプロセスID (PID) を取得することができます。

Perlインタープリタ実行のたびに変更されると考えて差し支えないの無い変数ですので、例えばログを書くといった場合に使うと良いかもしれません。

print "Start. PID=$$\n";

open my $log_fh, '>', "/tmp/program.$$.log"
    or die;

print {$log_fh} "[DEBUG] ...";

forkやスレッドを使っている場合には、プロセスIDの扱い方はまた違ってくるとは思いますが、初歩的なプログラムではこのような使い方をして大丈夫でしょう。

$0: 現在のファイル名

現在の実行中のファイル名を取得します。

トークン __FILE__ も同様ですが、実行プログラムから呼ばれたモジュールの中では、$0 は依然として実行プログラム自体を返すという特性がありますが、__FILE__ はモジュールの名前を返すという違いがあります。

普段は直接 perl インタープリタに渡されないモジュール *.pm の中に以下のようなコードを書いて、テストとして使われたこともありますが、現在では Test::Moreprove などのソフトウェアテスト用のモジュールを使ってテストを書くことが推奨されます。

# 昔のプログラムによくある古式ゆかしいテスト
# *.pm 中で…
if ( $0 eq __FILE__ ) {
    # テストを書く
}

また、$0 は書き換えが可能です。パスワード文字列などの秘匿な文字列を引数で受け取らざるをえないプログラムがあったとして、同じサーバに入っている他のユーザが ps コマンドなどでそれをカジュアルに知ることを防ぐために書き換えをするなどといった用途が考えられます。

# プロセス中のパスワード文字列を消す
$0 =~ s/--password=\S+//;

ただ、パスワードはもっと安全な渡し方をしたほうがいいですね。

$a, $b: sort の特殊パッケージ変数

通常の変数っぽい $a $b ですが、実はこの変数は

  • use strict; をしているのに、my 等の宣言無しで使っても警告をされない

といった特殊な変数です。これは sort の特殊パッケージ変数であるからなのです。

my @sorted_users = sort { $a->{name} cmp $b->{name} } @users;

といった sort のブロック内で暗黙に比較される2つの変数の代表として使われる $a$b。そのため、この変数はグローバル変数として予約されているのです。

一文字変数が完全悪であるかは議論が分かれますが、スコープの狭い範囲では数値に $i$j という変数を使ってもいいし、座標系や数学では $x $y $z という変数は不自然ではないでしょう。ただ、Perlでは $a$b 2つの変数の使用は sort 等の一部の場面以外では推奨されません。間違って使った場合に警告が出ないのは困りますからね。

$^V: Perlのバージョン

Perlのバージョン番号が入っています。

通常は use 5.018;use v5.18; といった書き方でバージョン指定をすることがありますが、バージョンごとに書き分けたい処理がある場合にはこの変数で分岐するといいかもしれません。とはいえ、後方互換性に非常に気を使っているPerlでは使用機会は少なでしょう。

$^O: Perlが動いているOS

Perlが動作しているOSの名前が入っています。

今の Mac OS X で動作させると「darwin」という文字列が返ってきます。また、一般的なLinuxディストリビューションで実行させると「linux」という文字列が返ってきます。

特定のOS以外で動作できないプログラムは、これを使って最初に例外を投げるといった処理が考えられます。

if ( $^O ne 'darwin' ) {
    die "This program is only running Mac OS X";
}

$&, $\``,$'`: 使ってはいけない正規表現マッチ変数

この記事で言いたいことはこれだったのかもしれません。この3つの変数は一度たりとも使ってはいけません。一度でも使うとPerlの正規表現に深刻なパフォーマンスの低下が表れます。

$&, $\``,$'` それぞれ「直前の正規表現マッチの全体」、「直前の正規表現マッチの前部分」「直前の正規表現マッチの後部分」を表しますが、Perlは一度でもこの変数が使われると、それを覚えようとして全ての正規表現に気を使うようになり、性能低下を引き起こします。

繰り返しますが、この変数は使ってはいけません。use English; をそのまま使うと、この変数を「触ってしまう」ことも use English; が避けられる理由です。use English qw(-no_match_vars); というこれを避ける宣言もありますが、use English; は使わないものとしたほうが良いと思います。

$数字: 直前の正規表現のキャプチャ

これは他の言語でもおなじみかもしれませんが、直前の正規表現のキャプチャ結果を返します。

"abcdefghijklmn" =~ /(\w\w\w)(\w\w\w(\w\w\w))/;
# この場合 $1 には abc、$2 には defghi、$3 には ghi が入る

数字の付番は、通常の丸括弧の開きが登場した順番です。キャプチャを必要としない単純なグループ化の場合は (?: ) といったグルーピングを使います。

$|: 出力バッファリングをするか

通常は未定義値となっている $| ですが、これに真と評価される値を入れると、出力バッファリングをオフにできます。

細かい説明は省きますが通常は、STDOUT (標準出力) が select されており、$| への代入は STDOUT への出力バッファリングのオンオフを指示します。

小さな出力を print で大量に行なった場合、表示がすぐに行われなかったり warn のほうが先に出力されたり (warn が使う STDERR は出力バッファリングを行いません) するのが都合が悪い場合には $| に 1 などを代入して出力バッファリングを抑止します。

$| = 1; # 出力バッファリングをオフ
for (1..10_000) {
    print "STDOUT: $_\n";
    warn  "STDERR: $_\n";
}

出力が巨大になってくるとI/Oへの負荷を抑える意味でバッファリングが働いてきます。大量の出力が随時やってくるログを随時読み込む場合等、出力が即座にやってきて欲しい時などに指定されます。

上記の例も環境依存で結果が変わってくると思います。 $| の行をコメントアウトしたりして、その結果を見てみると面白いでしょう。

$@: 例外

Perlでは例外の補足をブロックの eval { ... } で行い、例外を投げることは die で行います。その時に投げられた例外が $@ に入ります。込み入ったプログラムを書いていくPerl中級者以上では最重要変数の一つです。

local $@; # 一応グローバル変数 $@ のローカル化を行っておく
eval {
    # LWP::UserAgent があるかわからないので試す
    require LWP::UserAgent;
};
if ( $@ ) {
    # たぶん LWP::UserAgent がなかった…
    # 代わりの処理を書く
} 

$!: エラー

$@ のエラー版です。エラーが発生してもプログラムは流れるものの、その状態がここにはいります。

代表的なものとしては openclose でのエラーです。

open my $fh, '<', "nothing,txt";
print "error: $!";
# No such file or directory

時として意図しないような警告文書が入っていることがあるので、パースして処理というのはなかなか難しいでしょう。これは $@ で自前で die で例外文章を投げていない場合も同様です。


「もっと一文字変数あるんじゃない」という意見、確かにごもっともですが、ここでは初心者・中級者が押さえておくものに限定して簡単な解説を書いていきました。というかここまで書いて疲れました。検索しづらい一文字変数ですが、これだけ押さえればPerl中級者として立派にやっていけることでしょう。困ったら perlvar を見ればいいんです。プラグマやPerlの深層に関わっているもっと面白い一文字変数やその仲間達については、次回の執筆機会をいただけたときにでも書きたいと思いますので、期待してくださる方はブクマやMention などで応援してください。

次は ichigotake さんが書いてくださるそうです。期待しましょう!

created by
OGATA Tetsuji
created at
last modified at
2013-09-17 00:58
comments powered by Disqus