Perl Hackers Hub

第64回 少しマニアックなPerlのテクニック―特殊変数,低レベルの標準関数を使いこなす(1)

この記事を読むのに必要な時間:およそ 3.5 分

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはkarupaneruraこと佐藤健太さんで,テーマは「少しマニアックなPerlのテクニック」です。

本稿のサンプルコードは,WEB+DB PRESS Vol.119のサポートサイトから入手できます。すべてのコードは本誌執筆時点(2020年9月)で広く使われている最新版のUbuntu 18.04.5LTSおよびPerl 5.32.0で動作確認しています。

短くシンプルにコードを書き上げるテクニック

みなさんもご存じのとおり,CPANモジュールを使えば高度な処理を簡単に行えます。一方で,サーバでスクリプトを実行したい場合は,サーバの実行環境のperl処理系を使うことになります。

このとき,各サーバにCPANモジュールを新しくインストールするのは難しい場面が多いでしょう。対象環境のCPANモジュールを不用意にアップグレードすると,思わぬ影響が出るかもしれません。また,CPANモジュールの中にはインストール時にCのライブラリとリンクを行うものもあるため,簡単にそれが使えるとも限りません。特に古いサーバに対しては容易ではないでしょう。

そんな場面においては,コンパイル型言語を用いて静的リンクで実行バイナリをビルドしたものを配布できますが,この方法はコンパイルを必要とします。そのため,サーバで実際のデータを読んで動かして調整する必要がある用途では,少々面倒です。

そこで今回は,Perlに慣れている人にもそうでない人にも役立つ,CPANモジュールに頼らずにPerlの基本的な機能を上手に活用するテクニックを紹介します。

まず(1)では,その場で短くシンプルにコードを書き上げるテクニックを解説します。

特殊変数$_を使う

Perlの特色の一つとして,特殊変数$_の存在が挙げられるでしょう。この特殊変数の役割は大きく分けて2つあります。一つは,たとえばforeachのループ値の変数の指定を省略した際に$_へ自動的にループ値が代入されるような,その一連の処理における現在の値を示す役割です。もう一つは,ucなどの組込み関数のデフォルトの引数としての役割です。

$_は暗黙的に使われるものであるため,$_をやみくもに使うと,処理の対象が明示的ではないわかりにくいコードになりがちです。また,ネストしたコードで$_を使うと,どれがどの$_なのか見分けが付きにくく,間違いの温床になります。

しかし,裏を返せば,自分自身しか使わないコードで場面を選んで使う場合は,短いコードで目的を達成できる便利な道具であるとも言えます。また,mapgrepなど処理する対象が自明である場合にも便利です。$_の使いどころを考えるヒントを表1にまとめました。

表1 $_の使いどころを考えるヒント

検討事項使ってもよい場面使うべきでない場面
必要な事前処理少ない多い
扱うデータの種類少ない多い
処理の内容単純複雑
処理の対象自明自明ではない
ワンライナーで使う

$_を便利に活用できる代表的なユースケースとして,ワンライナーが挙げられるでしょう。ワンライナーとは,コマンドライン引数としてプログラムそのものとなるコードを渡して実行するスタイルを指します。

次のコードは,Perlのワンライナーで書いたHello,World!です。

$ perl -e 'print "Hello, World!\n"'

ちょっとした問題を手早く片付ける際に,ワンライナーは便利です。特に,grepsedなどのUNIXコマンドだけでは複雑になる場面でも,Perlのワンライナーであればシンプルに書ける場合があります。

そして,ワンライナーにおいて$_は非常に便利に使えます。たとえば,標準入力をすべて大文字にして標準出力に出力するワンライナーは次のように書けます。

$ perl -pe '$_=uc'

処理系perl-pオプションを使用することで,このコードは次のように展開されます。

B::Deparseを用いて展開したコード

LINE: while (defined($_ = readline ARGV)) {
    $_ = uc $_;
}
continue {
    die "-p destination: $!\n" unless print $_;
}

引数に指定したコードがループの中に展開されます。これは,標準入力または引数に指定したファイルの各行ごとにループして,continueセクションでprintするしくみです。そして,指定したコードのucの引数が,デフォルト引数である$_に補完されています。

別の例として,sedの代替としてperlを使って,正規表現による置換も行えます。

$ perl -pe 's/foo/bar/g'

この例も,パターンマッチ演算子である=~を利用せずに正規表現による置換を行うため,デフォルトの$_が処理の対象になります。結果として,sedのような処理をこれだけで実現できます。

このように,暗黙的に対象を示す変数とそれを支援するためのコマンドラインオプションがあるため,Perlのワンライナーはシンプルに書けます。

ちなみに,処理系としてのperlにはほかにも-n-aなどさまざまなコマンドラインオプションが実装されています。それぞれのオプションが問題にはまれば,本質的な部分の記述だけで問題を解決できます。

foreachと組み合わせて使う

一般的にforeachはループを書くときに使われますが,$_を代入するためだけに使うこともできます。なお,Perlにおいてforeachforと等価ですので,以後はforとして説明します。

次の例では,$fizzbuzz_textがfizzかbuzzを含む場合に文字列を出力します。

say "$fizzbuzz_text includes fizz"
    if $fizzbuzz_text =~ /fizz/;
say "$fizzbuzz_text includes buzz"
    if $fizzbuzz_text =~ /buzz/;

十分わかりやすいですが,$fizzbuzz_textという名前は長く見通しが悪いです。かといって,これを短くすれば意味のわからない命名になりかねません。

forを使って書きなおすと,次のようになります。

for ($fizzbuzz_text) {
    say "$_ includes fizz" if /fizz/;
    say "$_ includes buzz" if /buzz/;
}

forは通常リストに対してループを行いますが,この場合はスカラ変数を指定しているためループ回数が1回のループとなります。また,ループ変数を指定していないため,ループ変数として$_が使われます。結果的にこのforのブロックは,$_$fizzbuzz_textとして扱うブロックとして使えます。そして,先ほどのワンライナーの例と同様に=~演算子を利用せずに正規表現マッチを行っているため,デフォルトの$_がその処理の対象になります。

しくみは少し複雑ですが,イメージさえ理解していればうまく扱えるでしょう。

著者プロフィール

佐藤健太(さとうけんた)

1990年,千葉県生まれ。DeNAにてソフトウェア開発及び運用に従事,Japan Perl Association代表理事も務める。

好きな言語はPerlとGo。日本酒とうどんとロックンロールが好物。バンド活動も行っている。

URL:https://karupas.org/