perlform - Perl formats
Perlは、あなたが単純なレポートやチャートを作るのを助けてくれるよ
うな機構を持っています。これを容易にするために、Perlは出力するペ
ージを見栄えよくするためのプログラムの作成を助けてくれます。現在
のページにおいて何行出力したとか、ページヘッダーを出力するタイミ
ングなどのことを保持しつづけることができます。キーワードはFORTRAN から借りました:
format()が宣言のためのもので、write()が実行のた めのものです。perlfunc中のそれぞれのエントリを参照してくださ
い。幸運にも、そのレイアウトは非常に読みやすく、BASICのPRINT USING
文のようなものです。
パッケージやサブルーチンと同様に、formatも実行されるのではなく宣 言されるものなので、プログラムの任意の場所に置くことができます (ただし、通常は全てまとめておくのが良いでしょう)。これらは、Perl の他のすべての“型”とは分離された名前空間を持っています。これは つまり、“Foo”という名前の関数を持っているとき、それは“Foo”と 名前が付けられているフォーマットとは違うものなのだということです。 しかしながら、与えられたファイルハンドルに結び付けられたフォーマ ットのデフォルトの名前は、そのファイルハンドルと同じ名前になりま す。したがって、STDOUT のデフォルトのフォーマットの名前はは“STDOUT”で あり、TEMPというファイルハンドルに対するデフォルトフォーマットの 名前は“TEMP”となります。これらは同じもののように見えますが、そうでは ないのです。
出力レコードフォーマットは以下のように宣言されます:
format NAME =
FORMLIST
.
名前が省略された場合、format “STDOUT”が定義されます。FORMLIST は、それぞれが以下の三つのいずれかである行の並びから構成されます。
第一カラムに`#'が置かれることによって示されるコメント。
出力行のフォーマットを与える“ピクチャー”行。
直前のピクチャー行に値を押し込むための引数行。
ピクチャー行はそのまま見た通りに出力されますが、値の置き換えが行 われる幾つかのフィールドは例外です。ピクチャー行中の各フィールド は``@'' (アットマーク) もしくは ``^'' (キャレット)のいずれかで始まり ます。これらの行に対しては、いかなる変数展開(variable interpolation) もなされません。アットマークフィールド(配列マーカーの@と混同しな いように)は一般的なフィールドです。その他の種類のフィールドであ るキャレットフィールドは、基本的複数行ブロックの詰め込みを行うた めに用いられます。フィールドの長さは、左寄せ、右寄せ、センタリン グをそれぞれ指定する"<``, ''>``, ''|`` といったキャラクターで 埋められたフィールドで与えられます。変数が指定された幅よりも大き ければ、丸めが行われます。
右揃え(right justification)をするための別のフォームとして、数値 フィールドを指定するキャラクター“#(“.”を付加することもできま す)を使うこともできます。このやり方で小数点の位置を揃えることも できます。これらのフィールドに改行が含まれていた場合、その改行 までのテキストが出力されます。特別なフィールドである“@*”は複数 行の出力に使うことができ、値の切り捨ても行いません。このフィール ドはそれだけが行に置かれているようにします。
following line で指定された値は、ピクチャーフィールドと同じ順序 になります。値を提供する式はカンマで分けられていなければなりませ ん。式は、行の処理が行われるより前にリストコンテキストで評価され ます。ですから、一つのリスト式が複数行の要素を生成することもでき ます。この式はそれがカーリーブレースで囲まれている場合には、二行 以上に広げられることもできます。その場合、開きブレースは一行目の 最初のトークンでなければなりません。ある式が小数部を持った数値と して評価され、数値指定に対応するピクチャーは出力に現れます(つま り、埋め込みの“.”以外の複数の“#”を除くすべてのピクチャー です)。そして、小数点のために使われるキャラクターは常にカレン トのLC_NUMERICロカールによって決定されます。これはたとえば、実行 時にドイツ語ロカールが指定されている環境いる場合には、デフォルト の“.”ではなく“,”が使われるということです。詳しい情報は perllocale と WARNINGS を参照してください。
@ではなく^で始まっているピクチャーフィールドは特別なものとして扱
われます。#フィールドを使うことで、そのフィールドは値がundefine
であったときにブランクにされます。その他の型のフィールドのために
は、キャレットが詰め込みモードを有効にします。指定する値は任意の
式ではなく、テキスト文字列を保持しているスカラー変数の名前でなく
てはなりません。Perlはできる限りのテキストをフィールドに押し込ん
でから文字列の先頭をたたき落とす(chop)ので、次にその変数を参照し
たときには残りの文字列を出力できるのです(これはつまり、その変数
自身がwrite()の実行中に変更されてしまうということであり、元には
戻らないということです)。通常は、縦方向に積み重ねられたフィール
ドの並びを使ってテキストのブロックを出力することができます。最後
のフィールドの終端に“...”を置くと、それは出力対象のテキストが
長すぎて全体を出力できないときに出力に付加されます。$:という 変数(Englishモジュールを使っていれば $FORMAT_LINE_BREAK_CHARACTERS)
の内容を変更することによって、そこで改行することのできる文字を設
定することができます。
キャレットを使ったフィールドは、可変長のレコードを生成することが できます。フォーマットされるテキストが短ければ、行の任意の場所に ``‾'' (ティルデ) を置くことによって空行を抑制することができます。 ティルデは出力に応じて空白に変換されます。最初のティルデに続けて 二個目のティルデを置くと、その行はその行にある全てのフィールドが 出力されるまでくり返されます。(もし at varietyのフィールドでこれ を使うのなら、永遠に同じ値を呼び出す度に返すような式を使ってはい けません!)
ページの先頭の処理は、デフォルトではカレントのファイルハンドルに “_TOP”を付けた名前のフォーマットによって取り扱われます。これは、 各ページの先頭で呼び出されます。perlfuncを参照してくだ さい。
例:
# /etc/passwd ファイルに関するレポート
format STDOUT_TOP =
Passwd File
Name Login Office Uid Gid Home
------------------------------------------------------------------
.
format STDOUT =
@<<<<<<<<<<<<<<<<<< @||||||| @<<<<<<@>>>> @>>>> @<<<<<<<<<<<<<<<<<
$name, $login, $office,$uid,$gid, $home
.
# バグレポートフォームからのレポート
format STDOUT_TOP =
Bug Reports
@<<<<<<<<<<<<<<<<<<<<<<< @||| @>>>>>>>>>>>>>>>>>>>>>>>
$system, $%, $date
------------------------------------------------------------------
.
format STDOUT =
Subject: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$subject
Index: @<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$index, $description
Priority: @<<<<<<<<<< Date: @<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$priority, $date, $description
From: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$from, $description
Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$programmer, $description
‾ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
‾ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
‾ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
‾ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
‾ ^<<<<<<<<<<<<<<<<<<<<<<<...
$description
.
同じ出力チャネルにおいてprint()とwrite()を混ぜて使うことは可能で すが、$- ($FORMAT_LINES_LEFT)を自分で扱う必要があるでしょ う。
フォーマット変数
カレントのフォーマット名は $‾ ($FORMAT_NAME)という変数に格 納され、カレントの top of formatの名前は$^ ($FORMAT_TOP_NAME) に格納されています。カレントの出力ページ数は$% ($FORMAT_PAGE_NUMBER) にあり、ページ当たりの行数は$= ($FORMAT_LINES_PER_PAGE)に あります。そのハンドルが出力時に自動フラッシュするかどうかは$|
($OUTPUT_AUTOFLUSH)にあります。各ページの先頭の前(一ページ目 を除く)に出力される文字列は$^L ($FORMAT_FORMFEED)に格納さ れています。これらの変数はファイルハンドルごとに設定されるものな
ので、異なるファイルハンドルに効果を及ぼすには、そのファイルハン
ドルへselect()を行う必要があります:
select((select(OUTF),
$‾ = "My_Other_Format",
$^ = "My_Top_Format"
)[0]);
ちょっと見づらいですね。でもこれは一般的なイディオムなんです。で すからこれを見たときにもそんなに驚かないでください。少なくとも、 以前のファイルハンドルを保持するために一時変数を使うことができま す(一般的にはこちらのほうが良いやりかたです。なぜなら、読みやす くなるばかりでなく、式の途中でデバッガーのシングルステップが使え るからです):
$ofh = select(OUTF);
$‾ = "My_Other_Format";
$^ = "My_Top_Format";
select($ofh);
Englishモジュールを使っていれば、変数名もわかりやすいものにできます:
use English;
$ofh = select(OUTF);
$FORMAT_NAME = "My_Other_Format";
$FORMAT_TOP_NAME = "My_Top_Format";
select($ofh);
しかし、これでもまだ妙なselect()があります。FileHandleモジュール を使いましょう。そうすれば、これらの特殊変数の代わりに小文字メソ ッド(lowercase method)を使ってアクセスできるようになります。
use FileHandle;
format_name OUTF "My_Other_Format";
format_top_name OUTF "My_Top_Format";
ずいぶん良くなりましたね!
注意事項
values 行には任意の式(キャレットフィールドではなくアットマークフ ィールドで)を含めることができるので、sprintf()や自分で作ったよう なその他の関数を使ってより整った処理を行うことができます。例を挙 げてみましょう:
format Ident =
@<<<<<<<<<<<<<<<
&commify($n)
.
本当のキャレットやアットマークをフィールドに挿入するは以下のよう にします:
format Ident =
I have an @ here.
"@"
.
行全体を中央寄せするには以下のようにします:
format Ident =
@|||||||||||||||||||||||||||||||||||||||||||||||
"Some text line"
.
“ページの幅に関係なくフィールドをページの右端に浮かせておく”よ うな組み込みの方法はありません。フィールドがどこに置かれるのかを 指定する必要があります。本当にお薦めはできないのですが、カレント のカラムの数に基づいてその場でフォーマットを生成することが可能な ので、そうしてからeval()するという手が使えます:
$format = "format STDOUT = ¥n"
. '^' . '<' x $cols . "¥n"
. '$entry' . "¥n"
. "¥t^" . "<" x ($cols-8) . "‾‾¥n"
. '$entry' . "¥n"
. ".¥n";
print $format if $Debugging;
eval $format;
die $@ if $@;
これは以下のようなフォーマットを生成するでしょう:
format STDOUT =
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$entry
^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<‾‾
$entry
.
以下の例は、fmt(1)のようなことをするちょっとしたプログラムです:
format = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ‾‾ $_
.
$/ = '';
while (<>) {
s/¥s*¥n¥s*/ /g;
write;
}
フッター
$FORMAT_TOP_NAME はカレントのヘッダーフォーマットの名前を保持し
ていているのですが、フッターに対して同じことを自動的に行う適切な
仕掛けはありません。評価を行ってみるまではフォーマットがどれくら
い大きなものになるのか知ることができないということが、大きな問題
です。これはTODOリストにもあります。
一つの戦略: あなたが固定サイズのフッターを使うのであれば、write() する前に $FORMAT_LINES_LEFTをチェックすればフッターを適切にprint することができます。
別の戦略: open(MYSELF, "|-")(perlfuncを参照)を使っ て自分自身に対するパイプをオープンして常にSTDOUTではなくMYSELFに
write()するようにし、子プロセスではそのSTDINからの入力を、ヘッダ
ーとフッターを再構成するために処理します。これは非常にお手軽とい
うわけではありませんが、やればできます。
フォーマット機構に対する低水準アクセスのために、formline()を使っ たり、直接$^A($ACCUMULATOR )にアクセスすることができます。
例を挙げましょう:
$str = formline <<'END', 1,2,3;
@<<< @||| @>>>
END
print "Wow, I just stored `$^A' in the accumulator!¥n";
また、printf()に対するsprintf()と同じことをwrite()に対して行うサ ブルーチンswrite()を作成するために以下のようにできます:
use Carp;
sub swrite {
croak "usage: swrite PICTURE ARGS" unless @_;
my $format = shift;
$^A = "";
formline($format,@_);
return $^A;
}
$string = swrite(<<'END', 1, 2, 3);
Check me out
@<<< @||| @>>>
END
print $string;
フォーマットを終了する独立したドットは、間違ってコンフィグレーシ ョンされているインターネットメイラー(そして経験によれば、そうい った間違ったコンフィグレーションは通例のものであって、例外ではあ りません)を通して渡されるメイルメッセージを早まって終わらせてしま う可能性もあります。ですから、メイルを通じてフォーマットコードを 送るときには、フォーマットを終端するドットがレフトマージンに乗ら ないようにインデントすべきでしょう。これによりSMTPが途中で切ってし まうことを防ぐでしょう。
(“my”を使って宣言された)lexical変数は、フォーマットがそのlexi cal変数のスコープの内側で宣言されていない限りは、フォーマットの 内側では不可視になります(バージョン5.001までは全ての場所で不可視 でした)。
formatは、perlにおいてプログラムのロカールからの情報に左右されな
い唯一の部分です。プログラムの環境がLC_NUMERICロカールを指定した
場合でも常に指定した小数点キャラクターがフォーマットされた出力に
現れます。Perlは、use localeが有効でない場合にはその他のロカ
ールの処理を全て無視します。フォーマットされた出力をC<use locale> プラグマによって制御することはできません。なぜなら、このプラグマ
はプログラムのブロック構造に対して結び付けられるものであり、かつ、
過去とのしがらみ(historicak reasons)にっよりフォーマットはブロッ
ク構造の外側にあるのです。ロカールの処理に関する議論はperllocale
を参照してください。
式の内側では、空白キャラクター、¥n、¥t、¥fは単一のスペースと みなされます。したがって、以下のフィルターがフォーマット中の 値に適用されたと考えることができます。
$value =‾ tr/¥n¥t¥f/ /;
残りの空白キャラクター、¥rはピクチャー行が許しているのであれば 新しい行を出力します。