perlform - Perl 格式
Perl 有一種機制可以幫助您產生簡單的報表和圖表。為了方便,Perl 協助您編寫輸出頁面,接近列印後的樣貌。它可以追蹤一些事情,例如一頁有多少行、您在第幾頁、什麼時候列印頁首等。關鍵字取自 FORTRAN:format() 用於宣告,write() 用於執行;請參閱 perlfunc 中的條目。幸運的是,版面更易於閱讀,更像 BASIC 的 PRINT USING 陳述式。可以把它想成是功能較差的 nroff(1)。
格式與套件和子常式一樣,是宣告而非執行,因此它們可以在程式中的任何地方出現。(不過通常最好將它們放在一起。)它們在 Perl 中有自己的命名空間,不同於所有其他「類型」。這表示如果您有一個名為「Foo」的函式,它與有一個名為「Foo」的格式不同。但是,與特定檔案處理常式關聯的格式的預設名稱與檔案處理常式的名稱相同。因此,STDOUT 的預設格式稱為「STDOUT」,而檔案處理常式 TEMP 的預設格式稱為「TEMP」。它們看起來一樣。它們不一樣。
輸出記錄格式宣告如下
format NAME =
FORMLIST
.
如果省略名稱,則定義「STDOUT」格式。第 1 欄中的單一「.」用於終止格式。FORM_LIST 包含一連串行,其中每一行可能是三種類型之一
註解,表示方式為在第一欄中輸入「#」。
「圖片」行,提供單一輸出行的格式。
引數行,提供值以插入前一個圖片行。
圖片行包含輸出欄位定義,與文字穿插。這些行不會進行任何變數內插。欄位定義由一組字元組成,用於啟動和延伸欄位至所需寬度。這是欄位定義的完整字元組
@ start of regular field
^ start of special field
< pad character for left justification
| pad character for centering
> pad character for right justification
# pad character for a right-justified numeric field
0 instead of first #: pad number with leading zeroes
. decimal point within a numeric field
... terminate a text field, show "..." as truncation evidence
@* variable width field for a multi-line value
^* variable width field for next line of a multi-line value
~ suppress line with all fields empty
~~ repeat line until all fields are exhausted
圖片行中的每個欄位都以「@」(at)或「^」(插入符號)開頭,分別表示我們將稱之為「常規」或「特殊」欄位。填充字元的選擇決定欄位是文字或數字。波浪符號運算子不是欄位的一部分。讓我們詳細瞭解各種可能性。
欄位的長度透過使用多個「<」、「>」或「|」字元填充欄位來提供,分別以左對齊、右對齊或置中指定非數字欄位。對於常規欄位,值(直到第一個換行符號)會根據所選的對齊方式擷取並列印,並截斷多餘的字元。如果您以「...」終止文字欄位,如果值被截斷,將顯示三個點。特殊文字欄位可用於執行基本的文字區塊填滿;有關詳細資訊,請參閱「使用填滿模式」。
Example:
format STDOUT =
@<<<<<< @|||||| @>>>>>>
"left", "middle", "right"
.
Output:
left middle right
使用「#」作為填充字元會指定數字欄位,並右對齊。一個選用的「.」定義小數點的位置。如果第一個「#」使用「0」(零),則格式化的數字必要時會以前導零填充。如果值未定義,特殊數字欄位會空白。如果產生的值會超過指定的寬度,則欄位會以「#」填滿作為溢位證據。
Example:
format STDOUT =
@### @.### @##.### @### @### ^####
42, 3.1415, undef, 0, 10000, undef
.
Output:
42 3.142 0.000 0 ####
欄位「@*」可用於列印多行、未截斷的值;它應該(但不必)單獨出現在一行上。最後一個換行符號會被切斷,但所有其他字元都會逐字發出。
與「@*」一樣,這是一個變寬欄位。提供的值必須是純量變數。Perl 會將文字的第一行(直到第一個「\n」)放入欄位中,然後切斷字串的前端,以便下次參照變數時,可以列印更多文字。變數不會還原。
Example:
$text = "line 1\nline 2\nline 3";
format STDOUT =
Text: ^*
$text
~~ ^*
$text
.
Output:
Text: line 1
line 2
line 3
值以與圖片欄位相同的順序指定在下列格式行中。提供值的表達式必須以逗號分隔。在處理該行之前,它們全部在列表內容中進行評估,因此單一列表表達式可以產生多個列表元素。如果表達式包含在大括號中,則可以分佈在多行中。如果是這樣,開啟大括號必須是第一行中的第一個標記。如果表達式評估為帶有小數部分的數字,且對應的圖片指定小數部分應出現在輸出中(即除了多個「#」字元之外的任何圖片沒有內嵌「.」),則用於小數點的字元由目前的 LC_NUMERIC 區域設定決定(如果 use locale
有效)。這表示,例如,如果執行時期環境碰巧指定德語區域設定,則會使用「,」而不是預設的「.」。有關更多資訊,請參閱 perllocale 和 "WARNINGS"。
在文字欄位上,插入符號會啟用一種填滿模式。值必須是包含文字字串的純量變數,而不是任意表達式。Perl 會將文字的下一部分放入欄位中,然後將字串的前端切除,以便下次參照變數時,可以印出更多文字。(是的,這表示變數本身會在執行 write() 呼叫期間變更,且不會還原。)文字的下一部分由粗略的斷行演算法決定。您可以使用換行字元 (\r
) 強制換行。您可以透過將變數 $:
(如果您使用英文模組,則為 $FORMAT_LINE_BREAK_CHARACTERS)變更為所需字元的列表,來變更可以斷行的合法字元。
通常,您會在與同一個純量變數相關聯的垂直堆疊中使用一系列欄位,以印出文字區塊。您可能希望以文字「...」結束最後一個欄位,如果文字太長而無法全部顯示,則會出現在輸出中。
使用插入符號欄位可能會產生所有欄位都為空白的列。您可以在列中的任何位置放置一個 "~"(波浪號)字元來抑制此類列。波浪號會在輸出時轉換為一個空格。
如果您在列中的任何位置放置兩個相鄰的波浪號字元 "~~",則該列將重複,直到列中的所有欄位都用盡,即未定義。對於特殊(插入符號)文字欄位,這遲早會發生,但是如果您使用 @ 類型的文字欄位,您提供的表達式最好不要永遠給出相同的數值!(shift(@f)
是會運作的一個簡單範例。)不要在這些列中使用常規(@)數字欄位,因為它永遠不會變為空白。
表單頂端處理預設由一個格式處理,其名稱與目前的檔案句柄相同,並附加 "_TOP"。它會在每一頁的頂端觸發。請參閱 "write" in perlfunc。
範例
# a report on the /etc/passwd file
format STDOUT_TOP =
Passwd File
Name Login Office Uid Gid Home
------------------------------------------------------------------
.
format STDOUT =
@<<<<<<<<<<<<<<<<<< @||||||| @<<<<<<@>>>> @>>>> @<<<<<<<<<<<<<<<<<
$name, $login, $office,$uid,$gid, $home
.
# a report from a bug report form
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
)中,而目前的表單頂端格式名稱則在 $^
($FORMAT_TOP_NAME
)中。目前的輸出頁數儲存在 $%
($FORMAT_PAGE_NUMBER
)中,而頁面上的列數則在 $=
($FORMAT_LINES_PER_PAGE
)中。是否自動刷新此句柄上的輸出則儲存在 $|
($OUTPUT_AUTOFLUSH
)中。在每一頁頂端(第一頁除外)之前輸出的字串儲存在 $^L
($FORMAT_FORMFEED
)中。這些變數是針對每個檔案句柄設定的,因此您需要選取不同的檔案句柄才能影響它們
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 模組即可。現在,你可以使用小寫方法名稱來存取這些特殊變數
use FileHandle;
format_name OUTF "My_Other_Format";
format_top_name OUTF "My_Top_Format";
好多了!
因為 values 行可能包含任意表達式(對於 at 欄位,不是 caret 欄位),你可以將更複雜的處理工作交給其他函式,例如 sprintf() 或你自己的函式。例如
format Ident =
@<<<<<<<<<<<<<<<
&commify($n)
.
要將一個真正的 at 或 caret 放入欄位中,請執行此操作
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 包含當前標題格式的名稱,但沒有對應的機制可以自動對頁尾執行相同操作。在評估格式之前不知道格式有多大是主要問題之一。它在待辦事項清單中。
這裡有一個策略:如果你有一個固定大小的頁尾,你可以透過在每次 write() 之前檢查 $FORMAT_LINES_LEFT 來取得頁尾,並在必要時自己列印頁尾。
這裡有另一個策略:使用 open(MYSELF, "|-")
(請參閱 "open" in perlfunc)對自己開啟一個管道,並始終對 MYSELF 進行 write(),而不是 STDOUT。讓你的子處理程序整理其 STDIN 以重新排列標題和頁尾,隨你喜歡。不太方便,但可行。
對於格式化機制的低階存取,你可以使用 formline() 並直接存取 $^A
($ACCUMULATOR 變數)。
例如
$str = formline <<'END', 1,2,3;
@<<< @||| @>>>
END
print "Wow, I just stored '$^A' in the accumulator!\n";
或者要建立一個 swrite() 子常式,它對 write() 的作用就像 sprintf() 對 printf() 的作用,請執行此操作
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」宣告)在格式中不可見,除非格式在詞彙變數的範圍內宣告。
如果程式環境指定 LC_NUMERIC 地區設定,且在宣告格式時「use locale」生效,則會使用地區設定來指定格式化輸出的十進位小數點字元。在呼叫 write() 時,無法透過「use locale」控制格式化輸出。請參閱 perllocale 以進一步瞭解地區設定處理。
在固定長度文字欄位中要顯示的字串內,每個控制字元都會以空白取代。(但在使用填滿模式時,請記住 \r 的特殊意義。)這麼做是為了避免在某些輸出媒體上控制字元「消失」時發生未對齊的情況。