本連載では第一線の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
などの組込み関数のデフォルトの引数としての役割です。
$_
は暗黙的に使われるものであるため、$_
をやみくもに使うと、処理の対象が明示的ではないわかりにくいコードになりがちです。また、ネストしたコードで$_
を使うと、どれがどの$_
なのか見分けが付きにくく、間違いの温床になります。
しかし、裏を返せば、自分自身しか使わないコードで場面を選んで使う場合は、短いコードで目的を達成できる便利な道具であるとも言えます。また、map
やgrep
など処理する対象が自明である場合にも便利です。$_
の使いどころを考えるヒントを表1にまとめました。
表1 $_の使いどころを考えるヒント
検討事項 | 使ってもよい場面 | 使うべきでない場面 |
必要な事前処理 | 少ない | 多い |
扱うデータの種類 | 少ない | 多い |
処理の内容 | 単純 | 複雑 |
処理の対象 | 自明 | 自明ではない |
ワンライナーで使う
$_
を便利に活用できる代表的なユースケースとして、ワンライナーが挙げられるでしょう。ワンライナーとは、コマンドライン引数としてプログラムそのものとなるコードを渡して実行するスタイルを指します。
次のコードは、Perlのワンライナーで書いたHello,World!です。
ちょっとした問題を手早く片付ける際に、ワンライナーは便利です。特に、grep
やsed
などのUNIXコマンドだけでは複雑になる場面でも、Perlのワンライナーであればシンプルに書ける場合があります。
そして、ワンライナーにおいて$_
は非常に便利に使えます。たとえば、標準入力をすべて大文字にして標準出力に出力するワンライナーは次のように書けます。
処理系perl
の-p
オプションを使用することで、このコードは次のように展開されます。
引数に指定したコードがループの中に展開されます。これは、標準入力または引数に指定したファイルの各行ごとにループして、continue
セクションでprint
するしくみです。そして、指定したコードのuc
の引数が、デフォルト引数である$_
に補完されています。
別の例として、sed
の代替としてperl
を使って、正規表現による置換も行えます。
この例も、パターンマッチ演算子である=~
を利用せずに正規表現による置換を行うため、デフォルトの$_
が処理の対象になります。結果として、sed
のような処理をこれだけで実現できます。
このように、暗黙的に対象を示す変数とそれを支援するためのコマンドラインオプションがあるため、Perlのワンライナーはシンプルに書けます。
ちなみに、処理系としてのperl
にはほかにも-n
や-a
などさまざまなコマンドラインオプションが実装されています。それぞれのオプションが問題にはまれば、本質的な部分の記述だけで問題を解決できます。
foreach
と組み合わせて使う
一般的にforeach
はループを書くときに使われますが、$_
を代入するためだけに使うこともできます。なお、Perlにおいてforeach
はfor
と等価ですので、以後はfor
として説明します。
次の例では、$fizzbuzz_text
がfizzかbuzzを含む場合に文字列を出力します。
十分わかりやすいですが、$fizzbuzz_text
という名前は長く見通しが悪いです。かといって、これを短くすれば意味のわからない命名になりかねません。
for
を使って書きなおすと、次のようになります。
for
は通常リストに対してループを行いますが、この場合はスカラ変数を指定しているためループ回数が1回のループとなります。また、ループ変数を指定していないため、ループ変数として$_
が使われます。結果的にこのfor
のブロックは、$_
を$fizzbuzz_text
として扱うブロックとして使えます。そして、先ほどのワンライナーの例と同様に=~
演算子を利用せずに正規表現マッチを行っているため、デフォルトの$_
がその処理の対象になります。
しくみは少し複雑ですが、イメージさえ理解していればうまく扱えるでしょう。
入出力のフォーマットをコントロールする
Perlはテキスト処理に特化して作られた歴史から、どのようなフォーマットで入出力するのかをある程度コントロールできます。Perlには、入出力のコントロールを行うための特殊変数がいくつか存在します。これらの特性を理解して適材適所で正しく活用すると、シンプルで効率の良いコードが書けます。
入力の行セパレータを変更する
最もよく使われるものは、入力の行セパレータを意味する特殊変数$/
です。Perlには、<>
演算子や組込み関数のreadline
など、行単位で入力を扱う機能が存在します。この特殊変数はその行の単位を定めるための区切り文字となります。この特殊変数の値は環境によってデフォルト値が異なっており、たとえば一般的なLinux環境ではLF(\n
)に設定されています。
この特殊変数の値を変更すれば、行セパレータを変更できます。たとえば、Linux環境でCRLF(\r\n
)のファイルをPerlから読み込みたい場合は、$/
に\r\n
を設定して読み込むことで自然と処理できます。
純粋に入力の行セパレータを変更する目的でも$/
を利用できますが、最も身近な例はファイルハンドルからすべての内容を読み込む処理でしょう。この処理は、CPANモジュールのFile::Slurp
やPath::Tiny
のslurp
メソッドなどがその実装として存在するように、Perlにおいてはslurp
と呼ばれるのが一般的です。
slurp
を$/
を使わずに素朴に実装すると、次のようになります。
$/
を使えば、これをより簡単に実装できます。
$/
をundef
とするとPerlはファイルの終端までを1行として読み込みます。これによって、slurp
相当の処理を簡単に実現できます。
なお、このような特殊変数の変更は、その影響範囲を限定するために、local
を使ったダイナミックスコープで局所化して行うことが一般的です。
ほかの特殊変数を扱う場合でも、必要に応じてlocal
で局所化して扱うとよいでしょう。
出力の行・列セパレータを変更する
出力においても、入力の場合と同様にセパレータを変更できます。Perlにおいて出力のための基本的なインタフェースはprint
です。デフォルトでは出力のセパレータは規定されず、print
を連続して呼び出してもprint
の引数の内容が単に続けて出力されます。
print
は一般的には1つの引数の例しか示されませんが、実は複数の引数を出力できます。
これも同様にデフォルトのセパレータは規定されず、つながって出力されます。
ここでは便宜上、print
の呼び出しごとに末尾に付くセパレータを行セパレータ、print
の引数の間に付くセパレータを列セパレータとして説明します。
行セパレータを変更するためには、特殊変数$\
を使います。使い方は先ほど説明した$/
と同様です。
列セパレータを変更するためには、特殊変数$,
を使います。これも使い方は同様です。
これらを組み合わせると、CSV(Comma-SeparatedValues、カンマ区切り)を簡単に出力できます。
これは次のように出力されます。
素朴に実装するにはjoin
などを使う必要がありますが、これを使えばシンプルな実装になります。$,
を\t
にすれば、TSV(Tab-Separated Values、タブ区切り)も出力できます。
配列を文字列に展開する際のセパレータを変更する
出力の列セパレータとなる特殊変数$,
に似ていますが、配列を文字列に展開する際のセパレータとなる特殊変数$"
も存在します。これは、配列を文字列の中で展開する際に、その要素のセパレータになります。デフォルトでは空白文字になっています。
これもさまざまな用途がありますが、リストを読みやすく整形する用途が主でしょう。以下はエラーメッセージを生成する例です。
この例では、たとえば@result
が("a", "b", "c"
)となり、かつis_fail
が真になるとき、failed. result: a, b, c
というメッセージで例外を発生させます。
<続きの(2)はこちら。>
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT