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 中的单个“.”用于终止格式。FORMLIST 由一系列行组成,每行可以是以下三种类型之一
注释,用在第一列中放置“#”来表示。
给出单行输出格式的“图片”行。
提供要插入到前一张图片行中的值的“参数”行。
图片行包含输出字段定义,与文字文本交织在一起。这些行不会进行任何类型的变量插值。字段定义由一组字符组成,用于开始和扩展字段以达到其所需的宽度。这是字段定义的完整字符集
@ 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)或“^”(caret)开头,分别表示我们称之为“常规”或“特殊”字段。填充字符的选择决定了字段是文本还是数字。波浪号运算符不是字段的一部分。让我们详细了解各种可能性。
字段的长度由用多个“<”、“>”或“|”字符填充字段来提供,以分别指定左对齐、右对齐或居中的非数字字段。对于常规字段,将获取值(直到第一个换行符)并根据所选对齐方式打印,截断多余的字符。如果您用“...”终止文本字段,则如果值被截断,将显示三个点。特殊文本字段可用于进行基本的多分行文本块填充;有关详细信息,请参阅"使用填充模式"。
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 和 "警告"。
在文本字段中,插入符号启用了一种填充模式。提供的值必须是一个包含文本字符串的标量变量,而不是一个任意的表达式。Perl 将文本的下一部分放入字段,然后从字符串的前面截断,以便下次引用该变量时,可以打印更多文本。(是的,这意味着变量本身在执行 write() 调用期间被修改,并且不会恢复。)文本的下一部分由一个粗略的换行算法确定。您可以使用回车符 (\r
) 强制换行。您可以通过将变量 $:
(如果您使用的是 English 模块,则为 $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";
好多了!
因为值行可能包含任意表达式(对于 at 字段,而不是插入符字段),您可以将更复杂的处理外包给其他函数,例如 sprintf() 或您自己的函数。例如
format Ident =
@<<<<<<<<<<<<<<<
&commify($n)
.
要将一个真正的 at 或插入符放入字段中,请执行以下操作
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,并在必要时自己打印页脚。
这里还有另一个策略:使用 open(MYSELF, "|-")
(参见 "perlfunc 中的 open")打开一个指向你自己的管道,并始终将 write() 写入 MYSELF 而不是 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;
结束格式的单个点也可能过早地结束通过配置错误的 Internet 邮件服务器传递的邮件消息(根据经验,这种错误配置是常态,而不是例外)。因此,在通过邮件发送格式代码时,你应该缩进它,以便结束格式的点不在左边缘;这将防止 SMTP 意外截断。
词法变量(使用 "my" 声明)在格式中不可见,除非格式是在词法变量的作用域内声明的。
如果程序的环境指定了 LC_NUMERIC 本地化,并且在声明格式时 use locale
生效,则本地化用于指定格式化输出中的小数点字符。格式化输出不能通过在调用 write() 时使用 use locale
来控制。有关本地化处理的进一步讨论,请参见 perllocale。
在要在固定长度文本字段中显示的字符串中,每个控制字符都将被空格替换。(但请记住,在使用填充模式时 \r
的特殊含义。)这样做是为了避免控制字符在某些输出介质上“消失”时出现错位。