Perl Hackers Hub

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

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

入出力のフォーマットをコントロールする

Perlはテキスト処理に特化して作られた歴史から,どのようなフォーマットで入出力するのかをある程度コントロールできます。Perlには,入出力のコントロールを行うための特殊変数がいくつか存在します。これらの特性を理解して適材適所で正しく活用すると,シンプルで効率の良いコードが書けます。

入力の行セパレータを変更する

最もよく使われるものは,入力の行セパレータを意味する特殊変数$/です。Perlには,<>演算子や組込み関数のreadlineなど,行単位で入力を扱う機能が存在します。この特殊変数はその行の単位を定めるための区切り文字となります。この特殊変数の値は環境によってデフォルト値が異なっており,たとえば一般的なLinux環境ではLF\nに設定されています。

この特殊変数の値を変更すれば,行セパレータを変更できます。たとえば,Linux環境でCRLF\r\nのファイルをPerlから読み込みたい場合は,$/\r\nを設定して読み込むことで自然と処理できます。

純粋に入力の行セパレータを変更する目的でも$/を利用できますが,最も身近な例はファイルハンドルからすべての内容を読み込む処理でしょう。この処理は,CPANモジュールのFile::SlurpPath::Tinyslurpメソッドなどがその実装として存在するように,Perlにおいては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で局所化して扱うとよいでしょう。

出力の行・列セパレータを変更する

出力においても,入力の場合と同様にセパレータを変更できます。Perlにおいて出力のための基本的なインタフェースは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";

列セパレータを変更するためには,特殊変数$,を使います。これも使い方は同様です。

これらを組み合わせると,CSVComma-SeparatedValuesカンマ区切り)を簡単に出力できます。

local $, = ",";
local $\ = "\n";

print "date", "message";
print "08/22", "too hot!";

これは次のように出力されます。

date,message
08/22,too hot!

素朴に実装するにはjoinなどを使う必要がありますが,これを使えばシンプルな実装になります。$,\tにすれば,TSVTab-Separated Valuesタブ区切り)も出力できます。

配列を文字列に展開する際のセパレータを変更する

出力の列セパレータとなる特殊変数$,に似ていますが,配列を文字列に展開する際のセパレータとなる特殊変数$"も存在します。これは,配列を文字列の中で展開する際に,その要素のセパレータになります。デフォルトでは空白文字になっています。

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_failが真になるとき,failed. result: a, b, cというメッセージで例外を発生させます。

COLUMN ワンライナーにするかスクリプトにするか

ワンライナーで書くほうがよい場面もあれば,スクリプトにしたほうがよい場面もあります。

ワンライナーは,エディタを開く必要もなく楽に編集できるのが大きな利点です。結果を見つつちょっとした調整を繰り返しながら何度も実行したい場合,ワンライナーだと手軽です。また,シェルの履歴にコードがそのまま残るので検索しやすいことも利点でしょう。しかし,処理が複雑になると見通しが悪くなります。

スクリプトにすると,複雑な処理が書きやすくなります。ファイルとして名前を付けて保存するため,公開もしやすくなります。エディタによっては,エディタを開いたまま編集中のスクリプトを実行できる機能がプラグインとしてあるので,ワンライナーと似た書き味で開発できます。

それぞれ適材適所で使い分けて,自分なりの境界線を見定めていくとよいでしょう。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.126

2021年12月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-12539-4

  • 特集1
    開発環境から本番環境まで一気通貫!
    実践コンテナ活用
    VS Code,Docker,Kubernetes,Azure
  • 特集2
    iOS 15開発最前線
    Swift 5.5,UI開発,通知管理,Xcode Cloud
  • 特集3
    作って学ぶ検索エンジンのしくみ
    Goで実装! 膨大な情報からどう高速に探すのか

著者プロフィール

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

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

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

URL:https://karupas.org/