本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはkarupaneruraこと佐藤健太さんで、
本稿のサンプルコードは、
短くシンプルにコードを書き上げるテクニック
みなさんもご存じのとおり、
このとき、
そんな場面においては、
そこで今回は、
まず
特殊変数$_
を使う
Perlの特色の一つとして、$_
の存在が挙げられるでしょう。この特殊変数の役割は大きく分けて2つあります。一つは、foreach
のループ値の変数の指定を省略した際に$_
へ自動的にループ値が代入されるような、uc
などの組込み関数のデフォルトの引数としての役割です。
$_
は暗黙的に使われるものであるため、$_
をやみくもに使うと、$_
を使うと、$_
なのか見分けが付きにくく、
しかし、map
やgrep
など処理する対象が自明である場合にも便利です。$_
の使いどころを考えるヒントを表1にまとめました。
検討事項 | 使ってもよい場面 | 使うべきでない場面 |
---|---|---|
必要な事前処理 | 少ない | 多い |
扱うデータの種類 | 少ない | 多い |
処理の内容 | 単純 | 複雑 |
処理の対象 | 自明 | 自明ではない |
ワンライナーで使う
$_
を便利に活用できる代表的なユースケースとして、
次のコードは、
$ perl -e 'print "Hello, World!\n"'
ちょっとした問題を手早く片付ける際に、grep
やsed
などのUNIXコマンドだけでは複雑になる場面でも、
そして、$_
は非常に便利に使えます。たとえば、
$ 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
にはほかにも-n
や-a
などさまざまなコマンドラインオプションが実装されています。それぞれのオプションが問題にはまれば、
foreach
と組み合わせて使う
一般的にforeach
はループを書くときに使われますが、$_
を代入するためだけに使うこともできます。なお、foreach
はfor
と等価ですので、for
として説明します。
次の例では、$fizzbuzz_
がfizzかbuzzを含む場合に文字列を出力します。
say "$fizzbuzz_text includes fizz"
if $fizzbuzz_text =~ /fizz/;
say "$fizzbuzz_text includes buzz"
if $fizzbuzz_text =~ /buzz/;
十分わかりやすいですが、$fizzbuzz_
という名前は長く見通しが悪いです。かといって、
for
を使って書きなおすと、
for ($fizzbuzz_text) {
say "$_ includes fizz" if /fizz/;
say "$_ includes buzz" if /buzz/;
}
for
は通常リストに対してループを行いますが、$_
が使われます。結果的にこのfor
のブロックは、$_
を$fizzbuzz_
として扱うブロックとして使えます。そして、=~
演算子を利用せずに正規表現マッチを行っているため、$_
がその処理の対象になります。
しくみは少し複雑ですが、
入出力のフォーマットをコントロールする
Perlはテキスト処理に特化して作られた歴史から、
入力の行セパレータを変更する
最もよく使われるものは、$/
です。Perlには、<>
演算子や組込み関数のreadline
など、\n
)
この特殊変数の値を変更すれば、\r\n
)$/
に\r\n
を設定して読み込むことで自然と処理できます。
純粋に入力の行セパレータを変更する目的でも$/
を利用できますが、File::Slurp
やPath::Tiny
のslurp
メソッドなどがその実装として存在するように、slurp
と呼ばれるのが一般的です。
slurp
を$/
を使わずに素朴に実装すると、
my $all_of_texts = '';
for my $line (<$fh>) {
$all_of_texts .= $line;
}
$/
を使えば、
$/ = undef;
my $all_of_texts = <$fh>;
$/
をundef
とするとPerlはファイルの終端までを1行として読み込みます。これによって、slurp
相当の処理を簡単に実現できます。
なお、local
を使ったダイナミックスコープで局所化して行うことが一般的です。
my $all_of_texts = do {
# 初期化せず局所化するとundefになる
local $/;
<$fh>;
};
ほかの特殊変数を扱う場合でも、local
で局所化して扱うとよいでしょう。
出力の行・列セパレータを変更する
出力においても、print
です。デフォルトでは出力のセパレータは規定されず、print
を連続して呼び出してもprint
の引数の内容が単に続けて出力されます。
# "helloworld"と出力される
print "hello";
print "world";
print
は一般的には1つの引数の例しか示されませんが、
# "helloworld"と出力される
print "hello", "world";
これも同様にデフォルトのセパレータは規定されず、
ここでは便宜上、print
の呼び出しごとに末尾に付くセパレータを行セパレータ、print
の引数の間に付くセパレータを列セパレータとして説明します。
行セパレータを変更するためには、$\
を使います。使い方は先ほど説明した$/
と同様です。
local $\ = "\n";
# "hello\nworld\n"と出力される
print "hello";
print "world";
列セパレータを変更するためには、$,
を使います。これも使い方は同様です。
これらを組み合わせると、
local $, = ",";
local $\ = "\n";
print "date", "message";
print "08/22", "too hot!";
これは次のように出力されます。
date,message 08/22,too hot!
素朴に実装するにはjoin
などを使う必要がありますが、$,
を\t
にすれば、
配列を文字列に展開する際のセパレータを変更する
出力の列セパレータとなる特殊変数$,
に似ていますが、$"
も存在します。これは、
my @arr = qw/foo bar baz/;
# "foo bar baz"と出力される
print "@arr\n";
これもさまざまな用途がありますが、
my @result = doit(); # 何かしらの処理
if (is_fail(@result)) { # 結果から失敗を判定
local $" = ', ';
die "failed. result: @result";
}
この例では、@result
が("a", "b", "c"
)となり、is_
が真になるとき、failed. result: a, b, c
というメッセージで例外を発生させます。
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT