perlop - Perl 运算符和优先级
在 Perl 中,运算符决定执行的操作,与操作数的类型无关。例如 $x + $y
始终是数值加法,如果 $x
或 $y
不包含数字,则会尝试先将其转换为数字。
这与许多其他动态语言形成对比,在这些语言中,操作由第一个参数的类型决定。这也意味着 Perl 有一些运算符的两个版本,一个用于数值比较,另一个用于字符串比较。例如 $x == $y
比较两个数字是否相等,而 $x eq $y
比较两个字符串。
不过,也有一些例外:x
可以是字符串重复或列表重复,具体取决于左操作数的类型,而 &
、|
、^
和 ~
可以是字符串或数值位运算。
运算符优先级和结合性在 Perl 中的工作方式与数学中的工作方式基本相同。
运算符优先级意味着某些运算符的结合性比其他运算符更紧密。例如,在 2 + 4 * 5
中,乘法具有更高的优先级,因此 4 * 5
被组合在一起作为加法的右操作数,而不是 2 + 4
被组合在一起作为乘法的左操作数。就好像表达式写成 2 + (4 * 5)
,而不是 (2 + 4) * 5
。因此,表达式得到 2 + 20 == 22
,而不是 6 * 5 == 30
。
运算符结合性定义了当连续使用相同运算符时会发生什么:通常它们会向左或向右分组。例如,在 9 - 3 - 2
中,减法是左结合的,因此 9 - 3
被组合在一起作为第二个减法的左操作数,而不是 3 - 2
被组合在一起作为第一个减法的右操作数。就好像表达式写成 (9 - 3) - 2
,而不是 9 - (3 - 2)
。因此,表达式得到 6 - 2 == 4
,而不是 9 - 1 == 8
。
对于简单运算符,它们会先计算所有操作数,然后以某种方式组合这些值,优先级和结合性(以及括号)会对这些组合操作施加一些顺序要求。例如,在 2 + 4 * 5
中,优先级隐含的组合意味着 4 和 5 的乘法必须在 2 和 20 的加法之前执行,仅仅是因为该乘法的结果是加法操作数之一。但操作顺序并非完全由此决定:在 2 * 2 + 4 * 5
中,两个乘法必须在加法之前执行,但组合并没有说明两个乘法执行的顺序。事实上,Perl 有一个通用规则,即运算符的操作数按从左到右的顺序计算。一些运算符,如 &&=
,具有特殊的计算规则,可能导致操作数根本不计算;一般来说,表达式中的顶层运算符控制操作数的计算。
一些比较运算符,由于它们的结合性,会与一些具有相同优先级的运算符(但绝不会与不同优先级的运算符)链式连接。这种链式连接意味着每个比较都对它周围的两个参数进行,每个内部参数参与两个比较,比较结果隐式地进行 AND 操作。因此 "$x < $y <= $z"
的行为与 "$x < $y && $y <= $z"
完全相同,假设 "$y"
是一个看起来很简单的标量。AND 操作会像 "&&"
一样短路,一旦一个比较结果为假,就会停止比较序列。
在链式比较中,每个参数表达式最多计算一次,即使它参与两个比较,但计算结果会为每个比较获取。(如果短路意味着它对任何比较都不需要,则根本不会计算它。)这在内部参数的计算很昂贵或不确定时很重要。例如,
if($x < expensive_sub() <= $z) { ...
并不完全像
if($x < expensive_sub() && expensive_sub() <= $z) { ...
而是更接近
my $tmp = expensive_sub();
if($x < $tmp && $tmp <= $z) { ...
因为子例程只调用一次。但是,它也不完全像后一种代码,因为链式比较实际上不涉及任何临时变量(命名或其他):没有赋值。这在表达式是对普通子例程的调用时没有太大区别,但在 lvalue 子例程中或如果参数表达式通过其他方式产生某种不寻常的标量时,则更重要。例如,如果参数表达式产生一个绑定标量,那么表达式最多计算一次以产生该标量,但该标量的值可能会获取最多两次,一次用于它实际使用的每个比较。
在这个例子中,表达式只计算一次,绑定标量(表达式的结果)为使用它的每个比较获取。
if ($x < $tied_scalar < $z) { ...
在下一个示例中,表达式只会被评估一次,并且绑定的标量在表达式内的操作过程中只会被获取一次。该操作的结果会在每次比较时被获取,这通常无关紧要,除非该表达式结果由于运算符重载而也是神奇的。
if ($x < $tied_scalar + 42 < $z) { ...
一些运算符是非结合性的,这意味着使用相同优先级的这些运算符的序列是语法错误。例如,"$x .. $y .. $z"
是一个错误。
Perl 运算符具有以下结合性和优先级,从最高优先级到最低优先级列出。从 C 借来的运算符保持彼此之间相同的优先级关系,即使 C 的优先级略微混乱。(这使得 C 程序员更容易学习 Perl。)除了极少数例外,这些运算符都只对标量值起作用,而不是数组值。
left terms and list operators (leftward)
left ->
nonassoc ++ --
right **
right ! ~ ~. \ and unary + and -
left =~ !~
left * / % x
left + - .
left << >>
nonassoc named unary operators
nonassoc isa
chained < > <= >= lt gt le ge
chain/na == != eq ne <=> cmp ~~
left & &.
left | |. ^ ^.
left &&
left || //
nonassoc .. ...
right ?:
right = += -= *= etc. goto last next redo dump
left , =>
nonassoc list operators (rightward)
right not
left and
left or xor
在以下部分中,这些运算符将被详细介绍,顺序与它们在上面表格中出现的顺序相同。
许多运算符可以针对对象进行重载。参见 overload。
TERM 在 Perl 中具有最高优先级。它们包括变量、引号和类似引号的运算符、任何括号内的表达式,以及任何参数被括号包围的函数。实际上,在这个意义上并没有真正的函数,只有列表运算符和一元运算符表现得像函数,因为你把参数放在括号里。这些都在 perlfunc 中有记录。
如果任何列表运算符(print()
等)或任何一元运算符(chdir()
等)后面紧跟着一个左括号作为下一个标记,则运算符和括号内的参数将被视为具有最高优先级,就像正常的函数调用一样。
在没有括号的情况下,像 print
、sort
或 chmod
这样的列表运算符的优先级要么非常高,要么非常低,这取决于你是在查看运算符的左侧还是右侧。例如,在
@ary = (1, 3, sort 4, 2);
print @ary; # prints 1324
sort
右边的逗号在 sort
之前被评估,但左边的逗号在之后被评估。换句话说,列表运算符倾向于吞噬所有跟随它的参数,然后在处理前面的表达式时表现得像一个简单的 TERM。要小心使用括号
# These evaluate exit before doing the print:
print($foo, exit); # Obviously not what you want.
print $foo, exit; # Nor is this.
# These do the print before evaluating exit:
(print $foo), exit; # This is what you want.
print($foo), exit; # Or this.
print ($foo), exit; # Or even this.
还要注意
print ($foo & 255) + 1, "\n";
可能不会像你第一眼看到的那样做。括号包含 print
的参数列表,该列表被评估(打印 $foo & 255
的结果)。然后,在 print
的返回值(通常为 1)上加 1。结果类似于
1 + 1, "\n"; # Obviously not what you meant.
要正确地执行你想要的操作,你必须写
print(($foo & 255) + 1, "\n");
有关此内容的更多讨论,请参阅 "命名一元运算符"。
同样被解析为术语的是 do {}
和 eval {}
结构,以及子例程和方法调用,以及匿名构造函数 []
和 {}
。
另请参阅本节末尾的 "引用和类似引用的运算符",以及 "I/O 运算符"。
"->
" 是一个中缀解引用运算符,就像在 C 和 C++ 中一样。如果右侧是 [...]
、{...}
或 (...)
下标,那么左侧必须是数组、哈希或子例程的硬引用或符号引用。(或者从技术上讲,如果它是用于赋值的数组或哈希引用,则是一个能够保存硬引用的位置。)请参阅 perlreftut 和 perlref。
否则,右侧是方法名或包含方法名或子例程引用的简单标量变量,并且(如果它是方法名)左侧必须是对象(已祝福的引用)或类名(即包名)。请参阅 perlobj。
解引用情况(与方法调用情况相反)在一定程度上由 postderef
特性扩展。有关该特性的详细信息,请参阅 "perlref 中的后缀解引用语法"。
"++"
和 "--"
的工作方式与 C 中相同。也就是说,如果放在变量之前,它们会在返回值之前将变量递增或递减 1,如果放在之后,则在返回值之后递增或递减。
$i = 0; $j = 0;
print $i++; # prints 0
print ++$j; # prints 1
请注意,就像在 C 中一样,Perl 没有定义何时递增或递减变量。您只知道它会在返回值之前或之后完成。这也意味着在同一个语句中修改变量两次会导致未定义的行为。避免使用以下语句
$i = $i ++;
print ++ $i + $i ++;
Perl 不会保证上述语句的结果。
自动递增运算符具有一些额外的内置魔法。如果您递增一个数字变量,或者该变量曾经在数字上下文中使用过,那么您将获得正常的递增。但是,如果该变量自设置以来仅在字符串上下文中使用过,并且其值不是空字符串,并且与模式 /^[a-zA-Z]*[0-9]*\z/
匹配,则递增将作为字符串进行,保留每个字符在其范围内的值,并进行进位
print ++($foo = "99"); # prints "100"
print ++($foo = "a0"); # prints "a1"
print ++($foo = "Az"); # prints "Ba"
print ++($foo = "zz"); # prints "aaa"
undef
始终被视为数字,特别是在递增之前被更改为 0
(因此,对 undef
值的后递增将返回 0
而不是 undef
)。
自动递减运算符并不神奇。
二元 "**"
是指数运算符。它的绑定比一元减运算符更紧密,因此 -2**4
是 -(2**4)
,而不是 (-2)**4
。(这是使用 C 的 pow(3)
函数实现的,该函数实际上在内部对双精度数进行操作。)
请注意,某些指数表达式是未定义的:这些包括 0**0
、1**Inf
和 Inf**0
。不要期望这些特殊情况有任何特定的结果,结果是平台相关的。
一元 "!"
执行逻辑否定,即“非”。另请参阅 not
,了解此运算符的较低优先级版本。
一元 "-"
如果操作数是数字,则执行算术否定,包括任何看起来像数字的字符串。如果操作数是标识符,则返回由减号与标识符连接而成的字符串。否则,如果字符串以加号或减号开头,则返回以相反符号开头的字符串。这些规则的一个影响是 -bareword
等效于字符串 "-bareword"
。但是,如果字符串以非字母字符开头(不包括 "+"
或 "-"
),Perl 将尝试将字符串转换为数字,并执行算术否定。如果字符串不能干净地转换为数字,Perl 将给出警告 Argument "the string" isn't numeric in negation (-) at ...。
一元 "~"
执行按位否定,即 1 的补码。例如,0666 & ~027
是 0640。(另请参阅 "整数算术" 和 "按位字符串运算符"。)请注意,结果的宽度是平台相关的:~0
在 32 位平台上是 32 位宽,而在 64 位平台上是 64 位宽,因此,如果您期望特定的位宽,请记住使用 "&"
运算符屏蔽掉多余的位。
从 Perl 5.28 开始,尝试对包含序数值大于 255 的字符的字符串进行取反将导致致命错误。
如果通过 use feature 'bitwise'
或 use v5.28
启用了“按位”功能,则一元 "~"
始终将其参数视为数字,而运算符的另一种形式 "~."
始终将其参数视为字符串。因此,~0
和 ~"0"
在 32 位平台上都将给出 2**32-1,而 ~.0
和 ~."0"
都将产生 "\xff"
。在 Perl 5.28 之前,此功能会在 "experimental::bitwise"
类别中产生警告。
一元运算符 "+"
没有任何作用,即使在字符串上也是如此。它在语法上用于将函数名与圆括号内的表达式分隔开,否则该表达式将被解释为函数参数的完整列表。(参见上面 "术语和列表运算符(左向)" 中的示例。)
一元运算符 "\"
创建引用。如果其操作数是一个单一的带符号事物,它将创建一个指向该对象的引用。如果其操作数是一个带圆括号的列表,那么它将创建指向列表中提到的事物的引用。否则,它将把其操作数置于列表上下文中,并创建一个指向操作数提供的列表中的标量的引用列表。参见 perlreftut 和 perlref。不要将此行为与反斜杠在字符串中的行为混淆,尽管这两种形式都传达了保护下一个事物免受插值的含义。
二元运算符 "=~"
将一个标量表达式绑定到一个模式匹配。某些操作默认情况下搜索或修改字符串 $_
。此运算符使这种操作能够作用于其他字符串。右操作数是一个搜索模式、替换或转写。左操作数是应该被搜索、替换或转写的对象,而不是默认的 $_
。在标量上下文中使用时,返回值通常表示操作的成功与否。例外情况是带有 /r
(非破坏性)选项的替换(s///
)和转写(y///
),它们会导致 **r**eturn 值成为替换的结果。在列表上下文中的行为取决于特定的运算符。有关详细信息,请参见 "正则表达式引用类运算符",有关使用这些运算符的示例,请参见 perlretut。
如果右操作数是一个表达式而不是一个搜索模式、替换或转写,它将在运行时被解释为一个搜索模式。请注意,这意味着它的内容将被插值两次,因此
'\\' =~ q'\\';
是不允许的,因为正则表达式引擎最终会尝试编译模式 \
,它会将其视为语法错误。
二元运算符 "!~"
与 "=~"
相同,只是返回值在逻辑意义上被取反。
带有非破坏性替换(s///r
)或转写(y///r
)的二元运算符 "!~"
是一个语法错误。
二元运算符 "*"
将两个数字相乘。
二元运算符 "/"
将两个数字相除。
二元运算符 "%"
是模运算符,它计算第一个操作数除以第二个操作数的余数。给定整数操作数 $m
和 $n
:如果 $n
为正数,则 $m % $n
等于 $m
减去小于或等于 $m
的最大 $n
倍数。如果 $n
为负数,则 $m % $n
等于 $m
减去不小于 $m
的最小 $n
倍数(即结果将小于或等于零)。如果操作数 $m
和 $n
是浮点数,并且 $n
的绝对值(即 abs($n)
)小于 (UV_MAX + 1)
,则运算中只使用 $m
和 $n
的整数部分(注意:这里 UV_MAX
表示无符号整数类型的最大值)。如果右操作数的绝对值(abs($n)
)大于或等于 (UV_MAX + 1)
,则 "%"
计算方程 ($r = $m - $i*$n)
中的浮点余数 $r
,其中 $i
是一个特定的整数,它使 $r
与右操作数 $n
的符号相同(**不是**与左操作数 $m
相同,如 C 函数 fmod()
),并且绝对值小于 $n
的绝对值。注意,当 use integer
在作用域内时,"%"
可以直接访问 C 编译器实现的模运算符。此运算符对于负操作数的定义并不明确,但它执行速度更快。
二元运算符 x
是重复运算符。在标量上下文中,或者如果左操作数既不包含在括号中也不在 qw//
列表中,它执行字符串重复。在这种情况下,它为左操作数提供标量上下文,并返回一个字符串,该字符串由左操作数字符串重复右操作数指定的次数组成。如果 x
在列表上下文中,并且左操作数包含在括号中或 qw//
列表中,它执行列表重复。在这种情况下,它为左操作数提供列表上下文,并返回一个列表,该列表由左操作数列表重复右操作数指定的次数组成。如果右操作数为零或负数(对于负数会发出警告),则返回一个空字符串或一个空列表,具体取决于上下文。
print '-' x 80; # print row of dashes
print "\t" x ($tab/8), ' ' x ($tab%8); # tab over
@ones = (1) x 80; # a list of 80 1's
@ones = (5) x @ones; # set all elements to 5
二元运算符 "+"
返回两个数字的和。
二元运算符 "-"
返回两个数字的差。
二元运算符 "."
连接两个字符串。
二进制运算符 "<<"
返回其左侧参数左移右侧参数指定的位数后的值。参数应为整数。(另请参见 "整数运算"。)
二进制运算符 ">>"
返回其左侧参数右移右侧参数指定的位数后的值。参数应为整数。(另请参见 "整数运算"。)
如果 use integer
(参见 "整数运算")生效,则使用带符号的 C 整数(算术移位),否则使用无符号的 C 整数(逻辑移位),即使对于负移位数也是如此。在算术右移中,符号位在左侧被复制,在逻辑右移中,左侧填充零位。
无论哪种方式,实现都不会生成大于 Perl 构建时使用的整数类型大小(32 位或 64 位)的结果。
按负位数进行移位意味着反向移位:左移变为右移,右移变为左移。这与 C 语言不同,C 语言中负移位是未定义的。
按超过整数大小的位数进行移位,大多数情况下会得到零(所有位都移出),但如果在 use integer
下对负移位数进行右移超过位数,则结果为 -1。这与 C 语言不同,C 语言中按太多位进行移位是未定义的。C 语言中常见的行为是“按字长位数取模移位”,例如
1 >> 64 == 1 >> (64 % 64) == 1 >> 0 == 1 # Common C behavior.
但这完全是偶然的。
如果你厌倦了受限于平台的原生整数,use bigint
编译指示可以巧妙地完全绕过这个问题。
print 20 << 20; # 20971520
print 20 << 40; # 5120 on 32-bit machines,
# 21990232555520 on 64-bit machines
use bigint;
print 20 << 100; # 25353012004564588029934064107520
各种命名一元运算符被视为具有一个参数的函数,可以带可选的括号。
如果任何列表运算符(print()
等)或任何一元运算符(chdir()
等)后面紧跟着一个左括号作为下一个标记,则括号内的运算符和参数被视为最高优先级,就像普通的函数调用一样。例如,因为命名一元运算符的优先级高于 ||
chdir $foo || die; # (chdir $foo) || die
chdir($foo) || die; # (chdir $foo) || die
chdir ($foo) || die; # (chdir $foo) || die
chdir +($foo) || die; # (chdir $foo) || die
但是,因为 "*"
的优先级高于命名运算符
chdir $foo * 20; # chdir ($foo * 20)
chdir($foo) * 20; # (chdir $foo) * 20
chdir ($foo) * 20; # (chdir $foo) * 20
chdir +($foo) * 20; # chdir ($foo * 20)
rand 10 * 20; # rand (10 * 20)
rand(10) * 20; # (rand 10) * 20
rand (10) * 20; # (rand 10) * 20
rand +(10) * 20; # rand (10 * 20)
关于优先级,文件测试运算符,如 -f
、-M
等,被视为命名一元运算符,但它们不遵循此函数括号规则。这意味着,例如,-f($file).".bak"
等同于 -f "$file.bak"
。
另请参见 "术语和列表运算符(左向)"。
返回真或假的 Perl 运算符通常返回可以安全用作数字的值。例如,本节中的关系运算符和下一节中的相等运算符返回 1
表示真,以及一个特殊版本的已定义空字符串 ""
,它被计为零,但免于有关不正确数字转换的警告,就像 "0 but true"
一样。
二元 "<"
如果左操作数在数值上小于右操作数,则返回真。
二元 ">"
如果左操作数在数值上大于右操作数,则返回真。
二元 "<="
如果左操作数在数值上小于或等于右操作数,则返回真。
二元 ">="
如果左操作数在数值上大于或等于右操作数,则返回真。
二元 "lt"
如果左操作数在字符串上小于右操作数,则返回真。
二元 "gt"
如果左操作数在字符串上大于右操作数,则返回真。
二元 "le"
如果左操作数在字符串上小于或等于右操作数,则返回真。
二元 "ge"
如果左操作数在字符串上大于或等于右操作数,则返回真。
一系列关系运算符,例如 "$x < $y <= $z"
,执行链式比较,如上面 "运算符优先级和结合性" 一节中所述。请注意,它们不会与相等运算符链式连接,相等运算符的优先级较低。
二元 "=="
如果左操作数在数值上等于右操作数,则返回真。
二元 "!="
如果左操作数在数值上不等于右操作数,则返回真。
二元 "eq"
如果左操作数在字符串上等于右操作数,则返回真。
二元 "ne"
如果左操作数在字符串上不等于右操作数,则返回真。
一系列上述等号运算符,例如 "$x == $y == $z"
,执行链式比较,方式如上面“运算符优先级和结合性”一节所述。注意,它们不会与关系运算符链式连接,因为关系运算符的优先级更高。
二元 "<=>"
返回 -1、0 或 1,具体取决于左操作数在数值上小于、等于还是大于右操作数。如果您的平台支持 NaN
(非数字)作为数值,则使用它们与 "<=>"
返回 undef。NaN
不 "<"
、"=="
、">"
、"<="
或 ">="
任何东西(即使是 NaN
),因此这 5 个返回 false。 NaN != NaN
返回 true,NaN !=
其他任何东西 也是如此。如果您的平台不支持 NaN
,那么 NaN
只是一个数值为 0 的字符串。
$ perl -le '$x = "NaN"; print "No NaN support here" if $x == $x'
$ perl -le '$x = "NaN"; print "NaN support here" if $x != $x'
(请注意,bigint、bigrat 和 bignum 这些 pragma 都支持 "NaN"
。)
二元 "cmp"
返回 -1、0 或 1,具体取决于左操作数在字符串上小于、等于还是大于右操作数。
这里我们可以看到 <=> 和 cmp 之间的区别,
print 10 <=> 2 #prints 1
print 10 cmp 2 #prints -1
(类似于 gt 和 >、lt 和 < 等)。
二元 "~~"
对其参数进行智能匹配。智能匹配将在下一节中介绍。
双边排序运算符 "<=>"
和 "cmp"
以及智能匹配运算符 "~~"
相对于彼此以及相对于具有相同优先级的等号运算符是非结合性的。
"lt"
、"le"
、"ge"
、"gt"
和 "cmp"
使用当前 LC_COLLATE
本地化指定的排序顺序,前提是包含排序的 use locale
表单有效。请参阅 perllocale。不要将它们与 Unicode 混用,只在传统的 8 位本地化编码中使用它们。标准的 Unicode::Collate
和 Unicode::Collate::Locale
模块为排序问题提供了更强大的解决方案。
对于不区分大小写的比较,请查看 Perl v5.16 或更高版本中提供的 "perlfunc 中的 fc" 不区分大小写折叠函数。
if ( fc($x) eq fc($y) ) { ... }
二元 isa
当左操作数是右操作数给出的类(或从该类派生的子类)的对象实例时,计算结果为真。如果左操作数未定义,不是祝福的对象实例,也不派生自右操作数给出的类,则运算符计算结果为假。右操作数可以以裸字或产生字符串类名的标量表达式给出类
if( $obj isa Some::Class ) { ... }
if( $obj isa "Different::Class" ) { ... }
if( $obj isa $name_of_class ) { ... }
此功能从 Perl 5.31.6 开始可用,前提是通过 use feature 'isa'
启用。此功能由当前作用域中的 use v5.36
(或更高版本)声明自动启用。
二元 ~~
首次出现在 Perl 5.10.1 中(5.10.0 版本的行为不同),对它的参数进行“智能匹配”。这主要在 perlsyn 中描述的 when
结构中隐式使用,尽管并非所有 when
子句都调用智能匹配运算符。在所有 Perl 运算符中独一无二的是,智能匹配运算符可以递归。智能匹配运算符是 实验性的,其行为可能会发生变化。
它也是独一无二的,因为所有其他 Perl 运算符都对其操作数施加上下文(通常是字符串或数字上下文),自动将这些操作数转换为那些施加的上下文。相反,智能匹配从其操作数的实际类型中推断上下文,并使用该类型信息来选择合适的比较机制。
~~
运算符“多态”地比较其操作数,根据其实际类型(数字、字符串、数组、哈希等)确定如何比较它们。与它共享相同优先级的相等运算符一样,~~
对真返回 1,对假返回 ""
。它通常最好读作“在”、“在里面”或“包含在”,因为左操作数通常在右操作数里面查找。这使得智能匹配操作数的操作数顺序通常与正则表达式匹配运算符相反。换句话说,“较小”的东西通常放在左操作数中,而“较大”的东西放在右操作数中。
智能匹配的行为取决于其参数的类型,如以下表格所示。表格中第一个应用类型的行决定了智能匹配的行为。因为实际发生的事情主要由第二个操作数的类型决定,所以表格按右操作数排序,而不是按左操作数排序。
Left Right Description and pseudocode
===============================================================
Any undef check whether Any is undefined
like: !defined Any
Any Object invoke ~~ overloading on Object, or die
Right operand is an ARRAY:
Left Right Description and pseudocode
===============================================================
ARRAY1 ARRAY2 recurse on paired elements of ARRAY1 and ARRAY2[2]
like: (ARRAY1[0] ~~ ARRAY2[0])
&& (ARRAY1[1] ~~ ARRAY2[1]) && ...
HASH ARRAY any ARRAY elements exist as HASH keys
like: grep { exists HASH->{$_} } ARRAY
Regexp ARRAY any ARRAY elements pattern match Regexp
like: grep { /Regexp/ } ARRAY
undef ARRAY undef in ARRAY
like: grep { !defined } ARRAY
Any ARRAY smartmatch each ARRAY element[3]
like: grep { Any ~~ $_ } ARRAY
Right operand is a HASH:
Left Right Description and pseudocode
===============================================================
HASH1 HASH2 all same keys in both HASHes
like: keys HASH1 ==
grep { exists HASH2->{$_} } keys HASH1
ARRAY HASH any ARRAY elements exist as HASH keys
like: grep { exists HASH->{$_} } ARRAY
Regexp HASH any HASH keys pattern match Regexp
like: grep { /Regexp/ } keys HASH
undef HASH always false (undef cannot be a key)
like: 0 == 1
Any HASH HASH key existence
like: exists HASH->{Any}
Right operand is CODE:
Left Right Description and pseudocode
===============================================================
ARRAY CODE sub returns true on all ARRAY elements[1]
like: !grep { !CODE->($_) } ARRAY
HASH CODE sub returns true on all HASH keys[1]
like: !grep { !CODE->($_) } keys HASH
Any CODE sub passed Any returns true
like: CODE->(Any)
Right operand is a Regexp:
Left Right Description and pseudocode
===============================================================
ARRAY Regexp any ARRAY elements match Regexp
like: grep { /Regexp/ } ARRAY
HASH Regexp any HASH keys match Regexp
like: grep { /Regexp/ } keys HASH
Any Regexp pattern match
like: Any =~ /Regexp/
Other:
Left Right Description and pseudocode
===============================================================
Object Any invoke ~~ overloading on Object,
or fall back to...
Any Num numeric equality
like: Any == Num
Num nummy[4] numeric equality
like: Num == nummy
undef Any check whether undefined
like: !defined(Any)
Any Any string equality
like: Any eq Any
备注
智能匹配隐式地取消引用任何非祝福的哈希或数组引用,因此HASH
和ARRAY
条目在这些情况下适用。对于祝福的引用,Object
条目适用。涉及哈希的智能匹配只考虑哈希键,从不考虑哈希值。
“like”代码条目并不总是完全的再现。例如,智能匹配运算符只要有可能就会短路,但grep
不会。此外,grep
在标量上下文中返回匹配次数,而~~
只返回真或假。
与大多数运算符不同,智能匹配运算符知道如何特殊处理undef
use v5.10.1;
@array = (1, 2, 3, undef, 4, 5);
say "some elements undefined" if undef ~~ @array;
每个操作数都在修改后的标量上下文中被考虑,修改是数组和哈希变量通过引用传递给运算符,运算符隐式地取消引用它们。每对的两个元素都是相同的
use v5.10.1;
my %hash = (red => 1, blue => 2, green => 3,
orange => 4, yellow => 5, purple => 6,
black => 7, grey => 8, white => 9);
my @array = qw(red blue green);
say "some array elements in hash keys" if @array ~~ %hash;
say "some array elements in hash keys" if \@array ~~ \%hash;
say "red in array" if "red" ~~ @array;
say "red in array" if "red" ~~ \@array;
say "some keys end in e" if /e$/ ~~ %hash;
say "some keys end in e" if /e$/ ~~ \%hash;
两个数组智能匹配,如果第一个数组中的每个元素都与第二个数组中对应的元素智能匹配(即“在”),递归地。
use v5.10.1;
my @little = qw(red blue green);
my @bigger = ("red", "blue", [ "orange", "green" ] );
if (@little ~~ @bigger) { # true!
say "little is contained in bigger";
}
因为智能匹配运算符在嵌套数组上递归,所以这仍然会报告“red”在数组中。
use v5.10.1;
my @array = qw(red blue green);
my $nested_array = [[[[[[[ @array ]]]]]]];
say "red in array" if "red" ~~ $nested_array;
如果两个数组相互智能匹配,那么它们是彼此值的深层副本,如本例所示
use v5.12.0;
my @a = (0, 1, 2, [3, [4, 5], 6], 7);
my @b = (0, 1, 2, [3, [4, 5], 6], 7);
if (@a ~~ @b && @b ~~ @a) {
say "a and b are deep copies of each other";
}
elsif (@a ~~ @b) {
say "a smartmatches in b";
}
elsif (@b ~~ @a) {
say "b smartmatches in a";
}
else {
say "a and b don't smartmatch each other at all";
}
如果你要设置$b[3] = 4
,那么它不再报告“a 和 b 是彼此的深层副本”,而是报告"b 智能匹配在 a 中"
。这是因为@a
中对应的位置包含一个数组,该数组(最终)包含一个 4。
将一个哈希与另一个哈希智能匹配,报告两者是否包含相同的键,不多不少。这可以用来查看两个记录是否具有相同的字段名,而不关心这些字段可能具有什么值。例如
use v5.10.1;
sub make_dogtag {
state $REQUIRED_FIELDS = { name=>1, rank=>1, serial_num=>1 };
my ($class, $init_fields) = @_;
die "Must supply (only) name, rank, and serial number"
unless $init_fields ~~ $REQUIRED_FIELDS;
...
}
但是,只有当 $init_fields
确实是哈希引用时,这才能达到你的预期。条件 $init_fields ~~ $REQUIRED_FIELDS
也允许字符串 "name"
、"rank"
、"serial_num"
以及任何包含 "name"
或 "rank"
或 "serial_num"
的数组引用通过。
智能匹配运算符最常被用作 when
子句的隐式运算符。请参阅 perlsyn 中的“Switch 语句”部分。
为了避免依赖对象的底层表示,如果智能匹配的右操作数是一个没有重载 ~~
的对象,它会抛出异常“智能匹配未重载的对象会破坏封装
”。这是因为不应该去探查对象内部以查看某个东西是否“在”对象中。以下操作对没有 ~~
重载的对象都是非法的
%hash ~~ $object
42 ~~ $object
"fred" ~~ $object
但是,你可以通过重载 ~~
运算符来改变对象的智能匹配方式。这允许扩展通常的智能匹配语义。对于具有 ~~
重载的对象,请参阅 overload。
使用对象作为左操作数是允许的,尽管不太有用。智能匹配规则优先于重载,因此即使左操作数中的对象具有智能匹配重载,也会被忽略。作为左操作数的未重载对象将回退到对 ref
运算符返回的值进行字符串或数字比较。这意味着
$object ~~ X
不会以 X
作为参数调用重载方法。相反,上面的表格会像往常一样被查询,并且根据 X
的类型,可能会或可能不会调用重载。对于简单的字符串或数字,“in”等同于
$object ~~ $number ref($object) == $number
$object ~~ $string ref($object) eq $string
例如,这报告句柄闻起来像 IOish(但请不要真的这样做!)
use IO::Handle;
my $fh = IO::Handle->new();
if ($fh ~~ /\bIO\b/) {
say "handle smells IOish";
}
这是因为它将 $fh
视为一个字符串,例如 "IO::Handle=GLOB(0x8039e0)"
,然后根据它进行模式匹配。
二进制 "&"
返回其操作数按位与的结果。虽然目前没有发出警告,但当对不是数字(参见 "整数运算")或位字符串(参见 "位字符串运算符")的操作数执行此操作时,结果没有明确定义。
请注意,"&"
的优先级低于关系运算符,因此例如在像这样的测试中括号是必不可少的
print "Even\n" if ($x & 1) == 0;
如果通过 use feature 'bitwise'
或 use v5.28
启用了 "bitwise" 功能,则此运算符始终将其操作数视为数字。在 Perl 5.28 之前,此功能会在 "experimental::bitwise"
类别中产生警告。
二进制 "|"
返回其操作数按位或运算的结果。
二进制 "^"
返回其操作数按位异或运算的结果。
虽然目前没有发出警告,但当对不是数字(参见 "整数运算")或位串(参见 "位串运算符")的操作数执行这些运算时,结果是不确定的。
请注意,"|"
和 "^"
的优先级低于关系运算符,因此例如在像这样的测试中,括号是必不可少的
print "false\n" if (8 | 2) != 10;
如果通过 use feature 'bitwise'
或 use v5.28
启用了 "bitwise" 功能,则此运算符始终将其操作数视为数字。在 Perl 5.28 之前,此功能会在 "experimental::bitwise"
类别中产生警告。
二进制 "&&"
执行短路逻辑与运算。也就是说,如果左操作数为假,则不会评估右操作数。如果评估右操作数,标量或列表上下文将向下传播到右操作数。
二进制 "||"
执行短路逻辑或运算。也就是说,如果左操作数为真,则不会评估右操作数。如果评估右操作数,标量或列表上下文将向下传播到右操作数。
虽然在 C 中没有直接等效项,但 Perl 的 //
运算符与 C 样式的 "或" 运算符相关。实际上,它与 ||
完全相同,只是它测试的是左侧的定义性而不是其真值。因此,EXPR1 // EXPR2
如果 EXPR1
已定义,则返回 EXPR1
的值,否则返回 EXPR2
的值。(EXPR1
在标量上下文中被评估,EXPR2
在 //
本身上下文中被评估)。通常,这与 defined(EXPR1) ? EXPR1 : EXPR2
的结果相同(除了三元运算符形式可以用作左值,而 EXPR1 // EXPR2
不能)。这对于为变量提供默认值非常有用。如果您实际上要测试 $x
和 $y
中至少有一个是否已定义,请使用 defined($x // $y)
。
||
、//
和 &&
运算符返回最后评估的值(与 C 语言的 ||
和 &&
不同,它们返回 0 或 1)。因此,一个相当便携的方式来找出主目录可能是
$home = $ENV{HOME}
// $ENV{LOGDIR}
// (getpwuid($<))[7]
// die "You're homeless!\n";
特别是,这意味着你不应该将它用于选择两个聚合进行赋值
@a = @b || @c; # This doesn't do the right thing
@a = scalar(@b) || @c; # because it really means this.
@a = @b ? @b : @c; # This works fine, though.
作为 &&
和 ||
在用于控制流时的替代方案,Perl 提供了 and
和 or
运算符(见下文)。短路行为是相同的。然而,"and"
和 "or"
的优先级要低得多,因此你可以安全地在列表运算符之后使用它们,而无需使用括号
unlink "alpha", "beta", "gamma"
or gripe(), next LINE;
使用 C 语言风格的运算符,它们将这样写
unlink("alpha", "beta", "gamma")
|| (gripe(), next LINE);
用这种方式写会更易读
unless(unlink("alpha", "beta", "gamma")) {
gripe();
next LINE;
}
使用 "or"
进行赋值不太可能达到你想要的效果;见下文。
二元 ".."
是范围运算符,它实际上是两个不同的运算符,具体取决于上下文。在列表上下文中,它返回一个从左值到右值(以 1 为步长)计数的值列表。如果左值大于右值,则返回空列表。范围运算符对于编写 foreach (1..10)
循环和对数组进行切片操作很有用。在当前实现中,当范围运算符用作 foreach
循环中的表达式时,不会创建临时数组,但旧版本的 Perl 在你编写类似这样的代码时可能会消耗大量内存
for (1 .. 1_000_000) {
# code
}
范围运算符也适用于字符串,使用神奇的自动递增,见下文。
在标量上下文中,".."
返回一个布尔值。该运算符是双稳态的,就像一个触发器,并模拟了 sed、awk 和各种编辑器的行范围(逗号)运算符。每个 ".."
运算符都维护自己的布尔状态,即使跨越对包含它的子例程的调用也是如此。只要它的左操作数为假,它就为假。一旦左操作数为真,范围运算符就保持为真,直到右操作数为真,之后范围运算符再次变为假。它不会在下次评估范围运算符之前变为假。它可以测试右操作数并在它变为真的同一个评估中变为假(如 awk 中),但它仍然返回真。如果你不想在下次评估之前测试右操作数,如 sed 中,只需使用三个点 ("..."
) 而不是两个点。在所有其他方面,"..."
的行为与 ".."
相同。
当运算符处于“false”状态时,右操作数不会被计算,当运算符处于“true”状态时,左操作数不会被计算。优先级略低于 || 和 &&。返回值要么是表示 false 的空字符串,要么是表示 true 的序列号(从 1 开始)。每个遇到的范围都会重置序列号。范围内的最后一个序列号会附加字符串 "E0"
,这不会影响其数值,但可以让你在想要排除端点时搜索它。你可以通过等待序列号大于 1 来排除起始点。
如果标量 ".."
的两个操作数之一是常量表达式,则如果该操作数等于(==
)当前输入行号($.
变量),则该操作数被视为 true。
严格地说,比较实际上是 int(EXPR) == int(EXPR)
,但这只有在你使用浮点表达式时才会出现问题;当如上一段所述隐式使用 $.
时,比较是 int(EXPR) == int($.)
,这只有在 $.
设置为浮点值且你没有从文件读取时才会出现问题。此外,"span" .. "spat"
或 2.18 .. 3.14
在标量上下文中不会按你想要的方式工作,因为每个操作数都是使用它们的整数表示来计算的。
示例
作为标量运算符
if (101 .. 200) { print; } # print 2nd hundred lines, short for
# if ($. == 101 .. $. == 200) { print; }
next LINE if (1 .. /^$/); # skip header lines, short for
# next LINE if ($. == 1 .. /^$/);
# (typically in a loop labeled LINE)
s/^/> / if (/^$/ .. eof()); # quote body
# parse mail messages
while (<>) {
$in_header = 1 .. /^$/;
$in_body = /^$/ .. eof;
if ($in_header) {
# do something
} else { # in body
# do something else
}
} continue {
close ARGV if eof; # reset $. each file
}
这是一个简单的示例,说明了两个范围运算符之间的区别
@lines = (" - Foo",
"01 - Bar",
"1 - Baz",
" - Quux");
foreach (@lines) {
if (/0/ .. /1/) {
print "$_\n";
}
}
此程序将只打印包含“Bar”的行。如果将范围运算符更改为 ...
,它还会打印“Baz”行。
现在是一些作为列表运算符的示例
for (101 .. 200) { print } # print $_ 100 times
@foo = @foo[0 .. $#foo]; # an expensive no-op
@foo = @foo[$#foo-4 .. $#foo]; # slice last 5 items
因为每个操作数都是以整数形式计算的,所以 2.18 .. 3.14
将在列表上下文中返回两个元素。
@list = (2.18 .. 3.14); # same as @list = (2 .. 3);
如果两个操作数都是字符串,则列表上下文中的范围运算符可以使用神奇的自动递增算法,但要遵守以下规则
除了一个例外(如下),如果两个字符串看起来像 Perl 中的数字,则不会应用神奇的递增,字符串将被视为数字(更准确地说,是整数)。
例如,"-2".."2"
与 -2..2
相同,"2.18".."3.14"
生成 2, 3
。
上述规则的例外情况是,当左侧字符串以0
开头且长度大于一个字符时,在这种情况下,即使像"01"
这样的字符串在 Perl 中通常看起来像一个数字,也会应用神奇的增量。
例如,"01".."04"
生成 "01", "02", "03", "04"
,而 "00".."-1"
生成 "00"
到 "99"
- 这可能看起来令人惊讶,但请参见以下规则以了解其工作原理。要获取带有前导零的日期,您可以说
@z2 = ("01" .. "31");
print $z2[$mday];
如果您想强制将字符串解释为数字,您可以说
@numbers = ( 0+$first .. 0+$last );
注意:在 Perl 5.30 及更低版本中,左侧任何以"0"
开头的字符串,包括字符串"0"
本身,都会导致神奇的字符串增量行为。这意味着在这些 Perl 版本中,"0".."-1"
将生成 "0"
到 "99"
,这与 0..-1
不一致,后者生成空列表。这也意味着 "0".."9"
现在生成一个整数列表,而不是一个字符串列表。
如果指定的初始值不是神奇增量序列的一部分(即,不匹配/^[a-zA-Z]*[0-9]*\z/
的非空字符串),则只返回初始值。
例如,"ax".."az"
生成 "ax", "ay", "az"
,但 "*x".."az"
只生成 "*x"
。
对于其他遵循神奇增量规则的字符串初始值,将返回相应的序列。
例如,您可以说
@alphabet = ("A" .. "Z");
以获取英语字母表的所有普通字母,或者
$hexdigit = (0 .. 9, "a" .. "f")[$num & 15];
以获取十六进制数字。
如果指定的最终值不在神奇增量将生成的序列中,则序列将一直持续到下一个值比指定的最终值更长。如果最终字符串的长度小于第一个字符串,则返回空列表。
例如,"a".."--"
与 "a".."zz"
相同,"0".."xx"
生成 "0"
到 "99"
,而 "aaa".."--"
返回空列表。
从 Perl 5.26 开始,字符串上的列表上下文范围运算符在"use feature 'unicode_strings"
的范围内按预期工作。在之前的版本中,以及在该特性范围之外,它表现出"The "Unicode Bug"" in perlunicode:它的行为取决于范围端点的内部编码。
由于神奇的增量只对匹配/^[a-zA-Z]*[0-9]*\z/
的非空字符串有效,因此以下将只返回一个字母
use charnames "greek";
my @greek_small = ("\N{alpha}" .. "\N{omega}");
要获得 25 个传统的希腊字母小写字母,包括两个西格玛,你可以使用这个代替
use charnames "greek";
my @greek_small = map { chr } ( ord("\N{alpha}")
..
ord("\N{omega}")
);
但是,因为除了这些之外还有很多其他希腊字母小写字母,为了在正则表达式中匹配希腊字母小写字母,你可以使用模式 /(?:(?=\p{Greek})\p{Lower})+/
(或者使用实验性特性 /(?[ \p{Greek} & \p{Lower} ])+/
)。
三元 "?:"
是条件运算符,就像在 C 中一样。它的工作原理很像 if-then-else。如果 ?
之前的参数为真,则返回 :
之前的参数,否则返回 :
之后的参数。例如
printf "I have %d dog%s.\n", $n,
($n == 1) ? "" : "s";
标量或列表上下文会向下传播到第二个或第三个参数,无论哪个被选中。
$x = $ok ? $y : $z; # get a scalar
@x = $ok ? @y : @z; # get an array
$x = $ok ? @y : @z; # oops, that's just a count!
如果第二个和第三个参数都是合法的左值(意味着你可以对它们进行赋值),则该运算符可以被赋值。
($x_or_y ? $x : $y) = $z;
因为这个运算符会产生一个可赋值的结果,所以使用没有括号的赋值会让你陷入困境。例如,这个
$x % 2 ? $x += 10 : $x += 2
实际上意味着这个
(($x % 2) ? ($x += 10) : $x) += 2
而不是这个
($x % 2) ? ($x += 10) : ($x += 2)
这可能应该更简单地写成
$x += ($x % 2) ? 10 : 2;
"="
是普通的赋值运算符。
赋值运算符的工作原理与 C 中相同。也就是说,
$x += 2;
等同于
$x = $x + 2;
尽管没有复制解除对左值引用可能触发的任何副作用,例如来自 tie()
的副作用。其他赋值运算符的工作原理类似。以下是被识别的
**= += *= &= &.= <<= &&=
-= /= |= |.= >>= ||=
.= %= ^= ^.= //=
x=
虽然这些按家族分组,但它们都具有赋值的优先级。这些组合赋值运算符只能对标量进行操作,而普通的赋值运算符可以对数组、哈希、列表甚至引用进行赋值。(参见"上下文" 和"列表值构造器" 在 perldata 中,以及"对引用进行赋值" 在 perlref 中。)
与 C 不同的是,标量赋值运算符会产生一个有效的左值。修改赋值等同于进行赋值,然后修改被赋值的变量。这对于修改某物的副本很有用,例如
($tmp = $global) =~ tr/13579/24680/;
虽然从 5.14 开始,也可以通过这种方式完成
use v5.14;
$tmp = ($global =~ tr/13579/24680/r);
同样地,
($x += 2) *= 3;
等同于
$x += 2;
$x *= 3;
类似地,列表上下文中的列表赋值会生成被赋值的左值列表,而标量上下文中的列表赋值会返回赋值右侧表达式生成的元素数量。
三个点状位运算符赋值运算符(&.=
|.=
^.=
)是 Perl 5.22 中的新增内容。请参阅 "位运算符字符串"。
二元 ","
是逗号运算符。在标量上下文中,它会评估其左侧参数,丢弃该值,然后评估其右侧参数并返回该值。这与 C 语言中的逗号运算符相同。
在列表上下文中,它只是列表参数分隔符,并将它的两个参数都插入到列表中。这些参数也从左到右进行评估。
=>
运算符(有时发音为“胖逗号”)是逗号的同义词,只是它会导致其左侧的单词被解释为字符串,如果它以字母或下划线开头,并且仅由字母、数字和下划线组成。这包括可能被解释为运算符、常量、单个数字 v 字符串或函数调用的操作数。如果有疑问,可以显式地引用左侧操作数。
否则,=>
运算符的行为与逗号运算符或列表参数分隔符完全相同,具体取决于上下文。
例如
use constant FOO => "something";
my %h = ( FOO => 23 );
等同于
my %h = ("FOO", 23);
它不是
my %h = ("something", 23);
=>
运算符有助于记录哈希中键和值之间的对应关系,以及列表中其他成对的元素。
%hash = ( $key => $value );
login( $username => $password );
特殊的引用行为会忽略优先级,因此可能适用于左侧操作数的部分
print time.shift => "bbb";
该示例打印类似于 "1314363215shiftbbb"
的内容,因为 =>
隐式地引用了其左侧的 shift
,忽略了 time.shift
是整个左侧操作数的事实。
在列表运算符的右侧,逗号的优先级非常低,因此它控制着那里找到的所有逗号分隔的表达式。唯一优先级更低的运算符是逻辑运算符 "and"
、"or"
和 "not"
,它们可用于评估对列表运算符的调用,而无需使用括号
open HANDLE, "< :encoding(UTF-8)", "filename"
or die "Can't open: $!\n";
但是,有些人发现代码比用括号编写更难阅读
open(HANDLE, "< :encoding(UTF-8)", "filename")
or die "Can't open: $!\n";
在这种情况下,您也可以使用更常见的 "||"
运算符
open(HANDLE, "< :encoding(UTF-8)", "filename")
|| die "Can't open: $!\n";
另请参阅 "术语和列表运算符(向左)" 中对列表运算符的讨论。
一元运算符 "not"
返回其右侧表达式的逻辑否定。它等同于 "!"
,但优先级非常低。
二元运算符 "and"
返回两个周围表达式之间的逻辑合取。它等同于 &&
,但优先级非常低。这意味着它会短路:只有当左侧表达式为真时才会计算右侧表达式。
二元运算符 "or"
返回两个周围表达式之间的逻辑析取。它等同于 ||
,但优先级非常低。这使得它在控制流中非常有用。
print FH $data or die "Can't write to FH: $!";
这意味着它会短路:只有当左侧表达式为假时才会计算右侧表达式。由于其优先级,您必须小心避免将其用作 ||
运算符的替代品。它通常在流程控制中比在赋值中效果更好。
$x = $y or $z; # bug: this is wrong
($x = $y) or $z; # really means this
$x = $y || $z; # better written this way
但是,当它是一个列表上下文赋值,并且您试图使用 ||
进行流程控制时,您可能需要使用 "or"
,以便赋值具有更高的优先级。
@info = stat($file) || die; # oops, scalar sense of stat!
@info = stat($file) or die; # better, now @info gets its due
当然,您也可以始终使用括号。
二元运算符 "xor"
返回两个周围表达式的异或。它不能短路(当然)。
没有用于定义或的低优先级运算符。
以下是 C 中存在而 Perl 中不存在的运算符。
虽然我们通常认为引号是字面值,但在 Perl 中它们充当运算符,提供各种插值和模式匹配功能。Perl 为这些行为提供了常用的引号字符,但也提供了一种方法,让您为它们中的任何一个选择自己的引号字符。在下表中,{}
代表您选择的任何一对分隔符。
Customary Generic Meaning Interpolates
'' q{} Literal no
"" qq{} Literal yes
`` qx{} Command yes*
qw{} Word list no
// m{} Pattern match yes*
qr{} Pattern yes*
s{}{} Substitution yes*
tr{}{} Transliteration no (but see below)
y{}{} Transliteration no (but see below)
<<EOF here-doc yes*
* unless the delimiter is ''.
非括号分隔符使用相同的字符作为前缀和后缀,但四种类型的 ASCII 括号(圆括号、尖括号、方括号、花括号)都是嵌套的,这意味着
q{foo{bar}baz}
与
'foo{bar}baz'
相同。但是,请注意,这并不总是适用于引用 Perl 代码。
$s = q{ if($x eq "}") ... }; # WRONG
是一个语法错误。Text::Balanced
模块(从 v5.8 开始是标准的,之前来自 CPAN)能够正确地做到这一点。
在运算符和引号字符之间可以(在某些情况下必须)有空格,但当使用 #
作为引号字符时除外。q#foo#
被解析为字符串 foo
,而 q #foo#
是运算符 q
后跟一个注释。它的参数将从下一行获取。这允许您编写
s {foo} # Replace foo
{bar} # with bar.
必须使用空格的情况是,当引号字符是单词字符(意味着它匹配 /\w/
)时。
q XfooX # Works: means the string 'foo'
qXfooX # WRONG!
以下转义序列在插值结构中可用,以及在分隔符不是单引号("'"
)的转译中可用。在所有带大括号的转义序列中,允许(并忽略)任何数量的空格和/或制表符与大括号相邻和在大括号内。
Sequence Note Description
\t tab (HT, TAB)
\n newline (NL)
\r return (CR)
\f form feed (FF)
\b backspace (BS)
\a alarm (bell) (BEL)
\e escape (ESC)
\x{263A} [1,8] hex char (example shown: SMILEY)
\x{ 263A } Same, but shows optional blanks inside and
adjoining the braces
\x1b [2,8] restricted range hex char (example: ESC)
\N{name} [3] named Unicode character or character sequence
\N{U+263D} [4,8] Unicode character (example: FIRST QUARTER MOON)
\c[ [5] control char (example: chr(27))
\o{23072} [6,8] octal char (example: SMILEY)
\033 [7,8] restricted range octal char (example: ESC)
请注意,在插值结构中使用大括号的任何转义序列可能具有可选的空格(制表符或空格字符)与大括号相邻以及在大括号内,如上面的第二个 \x{ }
示例所示。
结果是大括号之间十六进制数字指定的字符。有关哪个字符的详细信息,请参见下面的 "[8]"。
空格(制表符或空格字符)可以将数字与大括号中的一个或两个分隔开。
否则,大括号之间只有十六进制数字有效。如果遇到无效字符,将发出警告,并且大括号内的无效字符和所有后续字符(有效或无效)将被丢弃。
如果大括号之间没有有效数字,则生成的字符是 NULL 字符(\x{00}
)。但是,显式空大括号(\x{}
)不会导致警告(目前)。
结果是范围 0x00 到 0xFF 中的十六进制数字指定的字符。有关哪个字符的详细信息,请参见下面的 "[8]"。
\x
后面只有十六进制数字有效。当 \x
后面跟少于两个有效数字时,任何有效数字都将用零填充。这意味着 \x7
将被解释为 \x07
,而单独的 "\x"
将被解释为 \x00
。除了字符串的末尾,少于两个有效数字将导致警告。请注意,虽然警告说非法字符被忽略,但它只作为转义的一部分被忽略,并且仍将用作字符串中的后续字符。例如
Original Result Warns?
"\x7" "\x07" no
"\x" "\x00" no
"\x7q" "\x07q" yes
"\xq" "\x00q" yes
结果是 name 给出的 Unicode 字符或字符序列。请参见 charnames。
\N{U+十六进制数字}
表示 Unicode 码点为 十六进制数字 的 Unicode 字符。
\c
后面的字符映射到表中所示的其他字符。
Sequence Value
\c@ chr(0)
\cA chr(1)
\ca chr(1)
\cB chr(2)
\cb chr(2)
...
\cZ chr(26)
\cz chr(26)
\c[ chr(27)
# See below for chr(28)
\c] chr(29)
\c^ chr(30)
\c_ chr(31)
\c? chr(127) # (on ASCII platforms; see below for link to
# EBCDIC discussion)
换句话说,它是代码点与其大写字母进行异或运算后得到 64 的字符。\c?
在 ASCII 平台上是 DELETE,因为 ord("?") ^ 64
是 127,而 \c@
是 NULL,因为 "@"
的 ord 是 64,所以与 64 本身进行异或运算会产生 0。
此外,对于任何 X,\c\X
会产生 chr(28) . "X"
,但不能出现在字符串的末尾,因为反斜杠会被解析为转义结束引号。
在 ASCII 平台上,上面列表中产生的字符是完整的 ASCII 控制字符集。在 EBCDIC 平台上并非如此;有关 ASCII 与 EBCDIC 平台之间差异的完整讨论,请参阅 "perlebcdic 中的 OPERATOR DIFFERENCES"。
不建议在 "c"
后面使用上面列出的字符以外的任何其他字符,从 Perl v5.20 开始,实际上只允许使用可打印的 ASCII 字符,减去左大括号 "{"
。对于任何其他允许的字符,其值是通过与第七位(即 64)进行异或运算得到的,如果启用了警告,则会发出警告。使用不允许的字符会导致致命错误。
要获得平台无关的控制字符,可以使用 \N{...}
。
结果是花括号之间八进制数字指定的字符。有关哪个字符的详细信息,请参阅下面的 "[8]"。
空格(制表符或空格字符)可以将数字与大括号中的一个或两个分隔开。
否则,如果遇到不是八进制数字的字符,则会发出警告,并且该值基于它之前的八进制数字,丢弃它以及所有后续字符,直到关闭花括号。如果根本没有八进制数字,则会发生致命错误。
结果是范围为 000 到 777 的三位八进制数字指定的字符(但最好不要使用超过 077,请参阅下一段)。有关哪个字符的详细信息,请参阅下面的 "[8]"。
某些上下文允许使用 2 位甚至 1 位数字,但任何不使用三位数字(第一个数字为零)的使用方式都可能产生意外结果。(例如,在正则表达式中,它可能会与反向引用混淆;请参阅 "perlrebackslash 中的 Octal escapes"。)从 Perl 5.14 开始,您可以使用 \o{}
,它可以避免所有这些问题。否则,最好仅将此结构用于序数 \077
及以下,记住用零填充左侧以形成三位数字。对于更大的序数,可以使用 \o{}
,或者转换为其他内容,例如转换为十六进制并使用 \N{U+}
(它在具有不同字符集的平台之间可移植)或 \x{}
。
以上几个结构通过数字指定一个字符。该数字表示字符在字符集编码中的位置(从 0 开始索引)。这被称为它的序数、代码位置或代码点。Perl 在目前使用 ASCII/Latin1 或 EBCDIC 作为本地编码的平台上运行,每种编码都允许指定 256 个字符。一般来说,如果数字是 255(0xFF,0377)或更小,Perl 会在平台的本地编码中解释它。如果数字是 256(0x100,0400)或更大,Perl 会将其解释为 Unicode 代码点,结果是相应的 Unicode 字符。例如,\x{50}
和 \o{120}
在十进制中都是 80,小于 256,因此该数字在本地字符集编码中被解释。在 ASCII 中,第 80 位(从 0 开始索引)的字符是字母 "P"
,而在 EBCDIC 中,它是符号 "&"
。\x{100}
和 \o{400}
在十进制中都是 256,因此无论本地编码是什么,该数字都被解释为 Unicode 代码点。Unicode 中第 256 位(从 0 开始索引)的字符名称是 LATIN CAPITAL LETTER A WITH MACRON
。
上述规则的一个例外是 \N{U+十六进制数字}
始终被解释为 Unicode 代码点,因此 \N{U+0050}
在 EBCDIC 平台上也是 "P"
。
注意:与 C 和其他语言不同,Perl 没有 \v
转义序列用于垂直制表符(VT,在 ASCII 和 EBCDIC 中都是 11),但您可以使用 \N{VT}
、\ck
、\N{U+0b}
或 \x0b
。(\v
在 Perl 的正则表达式模式中确实有意义,请参阅 perlre。)
以下转义序列在插值的结构中可用,但在转写中不可用。
\l lowercase next character only
\u titlecase (not uppercase!) next character only
\L lowercase all characters till \E or end of string
\U uppercase all characters till \E or end of string
\F foldcase all characters till \E or end of string
\Q quote (disable) pattern metacharacters till \E or
end of string
\E end either case modification or quoted section
(whichever was last seen)
有关 \Q
引用的字符的精确定义,请参阅 "perlfunc 中的 quotemeta"。
\L
、\U
、\F
和 \Q
可以堆叠,在这种情况下,您需要一个 \E
来对应每个。例如
say "This \Qquoting \ubusiness \Uhere isn't quite\E done yet,\E is it?";
This quoting\ Business\ HERE\ ISN\'T\ QUITE\ done\ yet\, is it?
如果包含 LC_CTYPE
的 use locale
表单生效(参见 perllocale),则 \l
、\L
、\u
和 \U
使用的字母大小写映射来自当前区域设置。如果使用 Unicode(例如,\N{}
或 0x100 或更大的代码点),则 \l
、\L
、\u
和 \U
使用的字母大小写映射由 Unicode 定义。这意味着对单个字符进行大小写映射有时会产生多个字符的序列。在 use locale
下,\F
对所有区域设置(除了 UTF-8 区域设置)都产生与 \L
相同的结果,而在 UTF-8 区域设置中,它使用 Unicode 定义。
所有系统都使用虚拟 "\n"
来表示行终止符,称为“换行符”。没有不变的物理换行符。这只是一个错觉,操作系统、设备驱动程序、C 库和 Perl 都共同维护着它。并非所有系统都将 "\r"
视为 ASCII CR,将 "\n"
视为 ASCII LF。例如,在过去的老式 Mac(MacOS X 之前)上,它们是相反的,而在没有行终止符的系统上,打印 "\n"
可能不会发出任何实际数据。通常,当您需要系统中的“换行符”时,请使用 "\n"
,但当您需要确切的字符时,请使用文字 ASCII。例如,大多数网络协议期望并更喜欢 CR+LF("\015\012"
或 "\cM\cJ"
)作为行终止符,尽管它们通常接受 "\012"
,但很少容忍 "\015"
。如果您习惯在网络中使用 "\n"
,您可能会在某一天被烧伤。
对于进行插值的结构,以“$
”或“@
”开头的变量将被插值。带下标的变量,如 $a[3]
或 $href->{key}[0]
,也会被插值,数组和哈希切片也是如此。但方法调用,如 $obj->meth
,则不会。
插值数组或切片会按顺序插值元素,并以 $"
的值为分隔符,因此等效于插值 join $", @array
。“标点符号”数组,如 @*
,通常只有在名称用大括号 @{*}
括起来时才会被插值,但数组 @_
、@+
和 @-
即使没有大括号也会被插值。
对于双引号字符串,\Q
的引用将在插值和转义处理之后应用。
"abc\Qfoo\tbar$s\Exyz"
等同于
"abc" . quotemeta("foo\tbar$s") . "xyz"
对于正则表达式运算符(qr//
、m//
和 s///
)的模式,\Q
的引用在插值处理之后、转义处理之前应用。这允许模式进行文字匹配(除了 $
和 @
)。例如,以下匹配
'\s\t' =~ /\Q\s\t/
因为 $
或 @
会触发插值,所以您需要使用类似 /\Quser\E\@\Qhost/
的东西来进行文字匹配。
模式会经过额外的级别解释,作为正则表达式。这作为第二遍进行,在变量插值之后,以便可以从变量中将正则表达式合并到模式中。如果您不希望这样做,请使用 \Q
来插值文字变量。
除了上面描述的行为之外,Perl 不会扩展多级插值。特别是,与 shell 程序员的预期相反,反引号在双引号内 *不会* 进行插值,单引号也不会阻止在双引号内使用时对变量的求值。
以下是应用于模式匹配和相关活动的类似引用运算符。
qr/STRING/msixpodualn
此运算符将 STRING 引用(并可能编译)为正则表达式。STRING 的插值方式与 m/PATTERN/
中的 PATTERN 相同。如果使用 "'"
作为分隔符,则不会进行变量插值。返回一个 Perl 值,该值可用于代替相应的 /STRING/msixpodualn
表达式。返回值是原始模式的规范化版本。它神奇地与包含相同字符的字符串不同:ref(qr/x/)
返回 "Regexp";但是,对其进行解引用没有明确定义(目前您将获得原始模式的规范化版本,但这可能会改变)。
例如,
$rex = qr/my.STRING/is;
print $rex; # prints (?si-xm:my.STRING)
s/$rex/foo/;
等同于
s/my.STRING/foo/is;
结果可用作匹配中的子模式
$re = qr/$pattern/;
$string =~ /foo${re}bar/; # can be interpolated in other
# patterns
$string =~ $re; # or used standalone
$string =~ /$re/; # or this way
由于 Perl 可能会在执行 qr()
运算符时编译模式,因此在某些情况下使用 qr()
可能会具有速度优势,尤其是在 qr()
的结果独立使用时
sub match {
my $patterns = shift;
my @compiled = map qr/$_/i, @$patterns;
grep {
my $success = 0;
foreach my $pat (@compiled) {
$success = 1, last if /$pat/;
}
$success;
} @_;
}
在 qr()
时将模式预编译为内部表示,避免了每次尝试匹配 /$pat/
时都需要重新编译模式。(Perl 有许多其他内部优化,但如果我们不使用 qr()
运算符,则在上面的示例中不会触发任何优化。)
选项(由以下修饰符指定)为
m Treat string as multiple lines.
s Treat string as single line. (Make . match a newline)
i Do case-insensitive pattern matching.
x Use extended regular expressions; specifying two
x's means \t and the SPACE character are ignored within
square-bracketed character classes
p When matching preserve a copy of the matched string so
that ${^PREMATCH}, ${^MATCH}, ${^POSTMATCH} will be
defined (ignored starting in v5.20 as these are always
defined starting in that release)
o Compile pattern only once.
a ASCII-restrict: Use ASCII for \d, \s, \w and [[:posix:]]
character classes; specifying two a's adds the further
restriction that no ASCII character will match a
non-ASCII one under /i.
l Use the current run-time locale's rules.
u Use Unicode rules.
d Use Unicode or native charset, as in 5.12 and earlier.
n Non-capture mode. Don't let () fill in $1, $2, etc...
如果预编译模式嵌入在更大的模式中,则 "msixpluadn"
的效果将被适当地传播。/o
修饰符的效果不会传播,它仅限于明确使用它的那些模式。
/a
、/d
、/l
和 /u
修饰符(在 Perl 5.14 中添加)控制字符集规则,但 /a
是您可能想要明确指定的唯一一个;其他三个由各种 pragma 自动选择。
有关 STRING 的有效语法以及对正则表达式语义的详细说明,请参阅 perlre。特别是,除了大部分已过时的 /o
之外的所有修饰符都在 "perlre 中的修饰符" 中进行了进一步说明。/o
在下一节中描述。
m/PATTERN/msixpodualngc
/PATTERN/msixpodualngc
搜索字符串以查找模式匹配,并在标量上下文中返回 true(如果成功)或 false(如果失败)。如果未通过 =~
或 !~
运算符指定字符串,则搜索 $_
字符串。(使用 =~
指定的字符串不必是左值——它可能是表达式求值的結果,但请记住 =~
的绑定非常紧密。)另请参阅 perlre。
选项如上面 qr//
中所述;此外,还提供以下匹配过程修饰符
g Match globally, i.e., find all occurrences.
c Do not reset search position on a failed match when /g is
in effect.
如果 "/"
是分隔符,则初始 m
是可选的。使用 m
,您可以使用任何一对非空格(ASCII)字符作为分隔符。这对于匹配包含 "/"
的路径名特别有用,以避免 LTS(leaning toothpick syndrome)。如果 "?"
是分隔符,则应用仅匹配一次规则,如 m?PATTERN?
中所述。如果 "'"
(单引号)是分隔符,则不会对 PATTERN 执行变量插值。当使用标识符中有效的分隔符字符时,m
后面需要空格。
PATTERN 可能包含变量,这些变量将在每次评估模式搜索时进行插值,除非分隔符是单引号。(注意,$(
、$)
和 $|
不会被插值,因为它们看起来像字符串结尾测试。)除非包含的插值变量发生变化,否则 Perl 不会重新编译模式。您可以通过在尾部分隔符后添加 /o
(代表“once”)来强制 Perl 跳过测试,永远不会重新编译。曾经,Perl 会不必要地重新编译正则表达式,而这个修饰符很有用,可以告诉它不要这样做,以提高速度。但现在,使用 /o
的唯一原因是
变量有数千个字符长,并且您知道它们不会改变,并且您需要通过让 Perl 跳过对它们的测试来榨取最后一点速度。(这样做会带来维护成本,因为提到 /o
就意味着您承诺不会更改模式中的变量。如果您更改了它们,Perl 甚至不会注意到。)
您希望模式使用变量的初始值,无论它们是否改变。(但有比使用 /o
更明智的方法来实现这一点。)
如果模式包含嵌入代码,例如
use re 'eval';
$code = 'foo(?{ $x })';
/$code/
那么 perl 将每次重新编译,即使模式字符串没有改变,以确保每次都看到 $x
的当前值。如果您想避免这种情况,请使用 /o
。
底线是使用 /o
几乎从来都不是一个好主意。
//
如果PATTERN 评估为空字符串,则使用上次成功匹配的正则表达式。在这种情况下,只有空模式上的 g
和 c
标志会被使用;其他标志取自原始模式。如果之前没有匹配成功,则(静默地)将其视为真正的空模式(它将始终匹配)。使用用户提供的字符串作为模式存在风险,如果字符串为空,则会触发“上次成功匹配”行为,这可能会非常令人困惑。在这种情况下,建议您将 m/$pattern/
替换为 m/(?:$pattern)/
以避免此行为。
可以通过变量 ${^LAST_SUCCESSFUL_PATTERN}
访问上次成功的模式。与它匹配或与空模式匹配应该具有相同的效果,区别在于当没有上次成功的模式时,空模式将静默匹配,而使用 ${^LAST_SUCCESSFUL_PATTERN}
变量将产生未定义的警告(如果启用了警告)。您可以检查 defined(${^LAST_SUCCESSFUL_PATTERN})
以测试当前作用域中是否存在“上次成功匹配”。
请注意,可能会使 Perl 误以为 //
(空正则表达式)实际上是 //
(定义或运算符)。Perl 通常在这方面做得很好,但一些病态情况可能会触发这种情况,例如 $x///
(是 ($x) / (//)
还是 $x // /
?)和 print $fh //
(print $fh(//
还是 print($fh //
?)。在所有这些示例中,Perl 将假设您指的是定义或运算符。如果您指的是空正则表达式,只需使用括号或空格来消除歧义,甚至在空正则表达式前面加上 m
(因此 //
变为 m//
)。
如果未使用 /g
选项,则列表上下文中的 m//
返回一个列表,该列表包含模式中括号匹配的子表达式,即 ($1
, $2
, $3
...)(请注意,这里 $1
等也被设置)。当模式中没有括号时,返回值是列表 (1)
,表示成功。无论是否有括号,在失败时都会返回空列表。
示例
open(TTY, "+</dev/tty")
|| die "can't access /dev/tty: $!";
<TTY> =~ /^y/i && foo(); # do foo if desired
if (/Version: *([0-9.]*)/) { $version = $1; }
next if m#^/usr/spool/uucp#;
# poor man's grep
$arg = shift;
while (<>) {
print if /$arg/o; # compile only once (no longer needed!)
}
if (($F1, $F2, $Etc) = ($foo =~ /^(\S+)\s+(\S+)\s*(.*)/))
最后一个示例将 $foo
分割成前两个单词和该行的其余部分,并将这三个字段分配给 $F1
、$F2
和 $Etc
。如果分配了任何变量,则条件为真;也就是说,如果模式匹配。
/g
修饰符指定全局模式匹配,即在字符串中尽可能多次匹配。它的行为取决于上下文。在列表上下文中,它返回一个列表,其中包含正则表达式中任何捕获括号匹配的子字符串。如果没有括号,它将返回一个包含所有匹配字符串的列表,就好像整个模式周围有括号一样。
在标量上下文中,每次执行 m//g
都会找到下一个匹配项,如果匹配则返回 true,如果没有进一步匹配则返回 false。可以使用 pos()
函数读取或设置上次匹配后的位置;请参阅 "perlfunc 中的 pos"。失败的匹配通常会将搜索位置重置为字符串的开头,但您可以通过添加 /c
修饰符(例如,m//gc
)来避免这种情况。修改目标字符串也会重置搜索位置。
\G 断言
您可以将 m//g
匹配与 m/\G.../g
交织在一起,其中 \G
是一个零宽度断言,它匹配先前 m//g
(如果有)停止的精确位置。如果没有 /g
修饰符,\G
断言仍然锚定在 pos()
上,因为它是在操作开始时(请参阅 "perlfunc 中的 pos"),但匹配当然只尝试一次。在没有对目标字符串应用 /g
匹配的情况下使用 \G
与使用 \A
断言匹配字符串的开头相同。另请注意,目前,\G
仅在锚定在模式的最开头时才得到正确支持。
示例
# list context
($one,$five,$fifteen) = (`uptime` =~ /(\d+\.\d+)/g);
# scalar context
local $/ = "";
while ($paragraph = <>) {
while ($paragraph =~ /\p{Ll}['")]*[.!?]+['")]*\s/g) {
$sentences++;
}
}
say $sentences;
这是另一种检查段落中句子的方法
my $sentence_rx = qr{
(?: (?<= ^ ) | (?<= \s ) ) # after start-of-string or
# whitespace
\p{Lu} # capital letter
.*? # a bunch of anything
(?<= \S ) # that ends in non-
# whitespace
(?<! \b [DMS]r ) # but isn't a common abbr.
(?<! \b Mrs )
(?<! \b Sra )
(?<! \b St )
[.?!] # followed by a sentence
# ender
(?= $ | \s ) # in front of end-of-string
# or whitespace
}sx;
local $/ = "";
while (my $paragraph = <>) {
say "NEW PARAGRAPH";
my $count = 0;
while ($paragraph =~ /($sentence_rx)/g) {
printf "\tgot sentence %d: <%s>\n", ++$count, $1;
}
}
以下是使用 m//gc
和 \G
的方法
$_ = "ppooqppqq";
while ($i++ < 2) {
print "1: '";
print $1 while /(o)/gc; print "', pos=", pos, "\n";
print "2: '";
print $1 if /\G(q)/gc; print "', pos=", pos, "\n";
print "3: '";
print $1 while /(p)/gc; print "', pos=", pos, "\n";
}
print "Final: '$1', pos=",pos,"\n" if /\G(.)/;
最后一个示例应该打印
1: 'oo', pos=4
2: 'q', pos=5
3: 'pp', pos=7
1: '', pos=7
2: 'q', pos=8
3: '', pos=8
Final: 'q', pos=8
请注意,最终匹配的是 q
而不是 p
,而没有 \G
锚点的匹配会这样做。还要注意,最终匹配没有更新 pos
。pos
仅在 /g
匹配时更新。如果最终匹配确实匹配了 p
,那么很有可能您正在运行一个古老的(5.6.0 之前的)Perl 版本。
/\G.../gc
是 lex
类扫描仪的一个有用习惯用法。您可以将多个正则表达式组合起来,以逐部分处理字符串,根据匹配的正则表达式执行不同的操作。每个正则表达式都试图从上一个正则表达式停止的地方开始匹配。
$_ = <<'EOL';
$url = URI::URL->new( "http://example.com/" );
die if $url eq "xXx";
EOL
LOOP: {
print(" digits"), redo LOOP if /\G\d+\b[,.;]?\s*/gc;
print(" lowercase"), redo LOOP
if /\G\p{Ll}+\b[,.;]?\s*/gc;
print(" UPPERCASE"), redo LOOP
if /\G\p{Lu}+\b[,.;]?\s*/gc;
print(" Capitalized"), redo LOOP
if /\G\p{Lu}\p{Ll}+\b[,.;]?\s*/gc;
print(" MiXeD"), redo LOOP if /\G\pL+\b[,.;]?\s*/gc;
print(" alphanumeric"), redo LOOP
if /\G[\p{Alpha}\pN]+\b[,.;]?\s*/gc;
print(" line-noise"), redo LOOP if /\G\W+/gc;
print ". That's all!\n";
}
以下是输出(分成几行)
line-noise lowercase line-noise UPPERCASE line-noise UPPERCASE
line-noise lowercase line-noise lowercase line-noise lowercase
lowercase line-noise lowercase lowercase line-noise lowercase
lowercase line-noise MiXeD line-noise. That's all!
m?PATTERN?msixpodualngc
这与 m/PATTERN/
搜索类似,只是它在调用 reset()
运算符之间只匹配一次。当您只想查看一组文件中每个文件中的某个内容的第一次出现时,这是一个有用的优化。只有当前包中本地的 m??
模式会被重置。
while (<>) {
if (m?^$?) {
# blank line between header and body
}
} continue {
reset if eof; # clear m?? status for next file
}
另一个示例将 pod 文件中找到的第一个“latin1”编码切换为“utf8”
s//utf8/ if m? ^ =encoding \h+ \K latin1 ?x;
匹配一次的行为由匹配分隔符 ?
控制;使用任何其他分隔符,这将是正常的 m//
运算符。
过去,m?PATTERN?
中的引导 m
是可选的,但省略它会产生弃用警告。从 v5.22.0 开始,省略它会产生语法错误。如果您在旧代码中遇到此结构,只需添加 m
。
s/PATTERN/REPLACEMENT/msixpodualngcer
在字符串中搜索模式,如果找到,则将该模式替换为替换文本并返回所做替换的次数。否则返回 false(一个既是空字符串(""
)又是数字零(0
)的值,如 "关系运算符" 中所述)。
如果使用 /r
(非破坏性)选项,则它会在字符串的副本上运行替换,并且不会返回替换次数,而是返回副本,无论是否发生了替换。当使用 /r
时,原始字符串永远不会改变。即使输入是对象或绑定变量,副本也将始终是普通字符串。
如果没有通过 =~
或 !~
运算符指定字符串,则搜索和修改 $_
变量。除非使用 /r
选项,否则指定的字符串必须是标量变量、数组元素、哈希元素或对其中之一的赋值;也就是说,某种标量左值。
如果选择的分隔符是单引号,则不会对 PATTERN 或 REPLACEMENT 进行任何变量插值。否则,如果 PATTERN 包含一个看起来像变量而不是字符串结束测试的 $
,则该变量将在运行时插值到模式中。如果您希望模式在第一次插值变量时只编译一次,请使用 /o
选项。如果模式计算结果为空字符串,则使用最后成功执行的正则表达式。有关这些的进一步解释,请参见 perlre。
选项与 m//
相同,并增加了以下特定于替换的选项
e Evaluate the right side as an expression.
ee Evaluate the right side as a string then eval the
result.
r Return substitution and leave the original string
untouched.
任何非空白分隔符都可以替换斜杠。在使用标识符中允许的字符时,在 s
后添加空格。如果使用单引号,则不会对替换字符串进行解释(但是,/e
修饰符会覆盖此操作)。请注意,Perl 将反引号视为普通分隔符;替换文本不会被评估为命令。如果 PATTERN 由括号引号分隔,则 REPLACEMENT 具有自己的引号对,这些引号可能是也可能不是括号引号,例如,s(foo)(bar)
或 s<foo>/bar/
。/e
将导致替换部分被视为完整的 Perl 表达式,并在当时立即进行评估。但是,它在编译时会进行语法检查。第二个 e
修饰符将导致替换部分在作为 Perl 表达式运行之前被 eval
。
示例
s/\bgreen\b/mauve/g; # don't change wintergreen
$path =~ s|/usr/bin|/usr/local/bin|;
s/Login: $foo/Login: $bar/; # run-time pattern
($foo = $bar) =~ s/this/that/; # copy first, then
# change
($foo = "$bar") =~ s/this/that/; # convert to string,
# copy, then change
$foo = $bar =~ s/this/that/r; # Same as above using /r
$foo = $bar =~ s/this/that/r
=~ s/that/the other/r; # Chained substitutes
# using /r
@foo = map { s/this/that/r } @bar # /r is very useful in
# maps
$count = ($paragraph =~ s/Mister\b/Mr./g); # get change-cnt
$_ = 'abc123xyz';
s/\d+/$&*2/e; # yields 'abc246xyz'
s/\d+/sprintf("%5d",$&)/e; # yields 'abc 246xyz'
s/\w/$& x 2/eg; # yields 'aabbcc 224466xxyyzz'
s/%(.)/$percent{$1}/g; # change percent escapes; no /e
s/%(.)/$percent{$1} || $&/ge; # expr now, so /e
s/^=(\w+)/pod($1)/ge; # use function call
$_ = 'abc123xyz';
$x = s/abc/def/r; # $x is 'def123xyz' and
# $_ remains 'abc123xyz'.
# expand variables in $_, but dynamics only, using
# symbolic dereferencing
s/\$(\w+)/${$1}/g;
# Add one to the value of any numbers in the string
s/(\d+)/1 + $1/eg;
# Titlecase words in the last 30 characters only (presuming
# that the substring doesn't start in the middle of a word)
substr($str, -30) =~ s/\b(\p{Alpha})(\p{Alpha}*)\b/\u$1\L$2/g;
# This will expand any embedded scalar variable
# (including lexicals) in $_ : First $1 is interpolated
# to the variable name, and then evaluated
s/(\$\w+)/$1/eeg;
# Delete (most) C comments.
$program =~ s {
/\* # Match the opening delimiter.
.*? # Match a minimal number of characters.
\*/ # Match the closing delimiter.
} []gsx;
s/^\s*(.*?)\s*$/$1/; # trim whitespace in $_,
# expensively
for ($variable) { # trim whitespace in $variable,
# cheap
s/^\s+//;
s/\s+$//;
}
s/([^ ]*) *([^ ]*)/$2 $1/; # reverse 1st two fields
$foo !~ s/A/a/g; # Lowercase all A's in $foo; return
# 0 if any were found and changed;
# otherwise return 1
请注意在最后一个示例中使用 $
而不是 \
。与 sed 不同,我们只在左侧使用 \<digit>
形式。在其他任何地方都是 $<digit>
。
有时,您无法仅使用/g
来获得您可能想要的所有更改。以下列出了两种常见情况。
# put commas in the right places in an integer
1 while s/(\d)(\d\d\d)(?!\d)/$1,$2/g;
# expand tabs to 8-column spacing
1 while s/\t+/' ' x (length($&)*8 - length($`)%8)/e;
虽然s///
接受/c
标志,但除了在启用警告时产生警告之外,它没有任何效果。
q/STRING/
'STRING'
单引号的字面字符串。反斜杠表示反斜杠,除非它后面跟着分隔符或另一个反斜杠,在这种情况下,分隔符或反斜杠将被插值。
$foo = q!I said, "You said, 'She said it.'"!;
$bar = q('This is it.');
$baz = '\n'; # a two-character string
qq/STRING/
"STRING"
双引号的插值字符串。
$_ .= qq
(*** The previous line contains the naughty word "$1".\n)
if /\b(tcl|java|python)\b/i; # :-)
$baz = "\n"; # a one-character string
qx/STRING/
`STRING`
一个字符串,它可能被插值,然后作为系统命令执行,通过/bin/sh或其等效项(如果需要)。shell 通配符、管道和重定向将被识别。类似于system
,如果字符串不包含 shell 元字符,则它将直接执行。返回命令的收集的标准输出;标准错误不受影响。在标量上下文中,它将作为单个(可能包含多行)字符串返回,或者如果 shell(或命令)无法启动,则返回undef
。在列表上下文中,返回一个行列表(无论您如何使用$/
或$INPUT_RECORD_SEPARATOR
定义行),或者如果 shell(或命令)无法启动,则返回一个空列表。
因为反引号不会影响标准错误,所以如果您关心解决这个问题,请使用 shell 文件描述符语法(假设 shell 支持此语法)。要捕获命令的 STDERR 和 STDOUT
$output = `cmd 2>&1`;
要捕获命令的 STDOUT 但丢弃其 STDERR
$output = `cmd 2>/dev/null`;
要捕获命令的 STDERR 但丢弃其 STDOUT(这里顺序很重要)
$output = `cmd 2>&1 1>/dev/null`;
要交换命令的 STDOUT 和 STDERR 以捕获 STDERR 但让其 STDOUT 输出到旧的 STDERR
$output = `cmd 3>&1 1>&2 2>&3 3>&-`;
要分别读取命令的 STDOUT 和 STDERR,最简单的方法是将它们分别重定向到文件,然后在程序完成后从这些文件读取
system("program args 1>program.stdout 2>program.stderr");
命令使用的 STDIN 文件句柄从 Perl 的 STDIN 继承。例如
open(SPLAT, "stuff") || die "can't open stuff: $!";
open(STDIN, "<&SPLAT") || die "can't dupe SPLAT: $!";
print STDOUT `sort`;
将打印名为“stuff”的文件的排序内容。
使用单引号作为分隔符可以保护命令免受 Perl 的双引号插值的影响,而是将其传递给 shell。
$perl_info = qx(ps $$); # that's Perl's $$
$shell_info = qx'ps $$'; # that's the new shell's $$
该字符串的评估方式完全取决于您系统上的命令解释器。在大多数平台上,如果您希望将 shell 元字符按字面意思处理,则必须对其进行保护。在实践中,这很难做到,因为不清楚如何转义哪些字符。有关手动fork()
和exec()
的安全示例,请参阅perlsec,以安全地模拟反引号。
在某些平台(特别是类似 DOS 的平台)上,shell 可能无法处理多行命令,因此在字符串中放置换行符可能无法获得您想要的结果。如果您想在单行中评估多个命令,您可以使用命令分隔符字符将它们分隔开,如果您的 shell 支持该字符(例如,许多 Unix shell 上的;
和 Windows NT cmd
shell 上的&
)。
Perl 将尝试在启动子进程之前刷新所有打开以供输出的文件,但这在某些平台上可能不受支持(请参阅perlport)。为了安全起见,您可能需要设置$|
(在English
中为$AUTOFLUSH
)或在任何打开的句柄上调用IO::Handle
的autoflush()
方法。
请注意,某些命令 shell 可能会对命令行的长度设置限制。您必须确保您的字符串在经过任何必要的插值后不会超过此限制。有关您特定环境的更多详细信息,请参阅特定于平台的发行说明。
使用此运算符会导致难以移植的程序,因为调用的 shell 命令因系统而异,实际上可能根本不存在。例如,POSIX shell 下的type
命令与 DOS 下的type
命令非常不同。但这并不意味着您应该尽力避免在反引号是完成某件事的正确方法时使用反引号。Perl 被设计为一种粘合语言,它粘合在一起的东西之一就是命令。只要了解您在做什么。
与system
一样,反引号将子进程退出代码放在$?
中。如果您想手动检查失败,您可以通过如下检查$?
来检查所有可能的失败模式
if ($? == -1) {
print "failed to execute: $!\n";
}
elsif ($? & 127) {
printf "child died with signal %d, %s coredump\n",
($? & 127), ($? & 128) ? 'with' : 'without';
}
else {
printf "child exited with value %d\n", $? >> 8;
}
使用open pragma 来控制在读取命令输出时使用的 I/O 层,例如
use open IN => ":encoding(UTF-8)";
my $x = `cmd-producing-utf-8`;
qx//
也可以像函数一样调用,使用"perlfunc 中的 readpipe"。
有关更多讨论,请参阅"I/O 运算符"。
qw/STRING/
评估为从STRING中提取的单词列表,使用嵌入的空格作为单词分隔符。它可以理解为大致等同于
split(" ", q/STRING/);
区别在于它只在 ASCII 空格处分割,在编译时生成一个真正的列表,并且在标量上下文中返回列表中的最后一个元素。所以这个表达式
qw(foo bar baz)
在语义上等同于列表
"foo", "bar", "baz"
一些常见的例子
use POSIX qw( setlocale localeconv )
@EXPORT = qw( foo bar baz );
一个常见的错误是试图用逗号分隔单词或在多行 qw
字符串中添加注释。出于这个原因,use warnings
编译指示和 -w 开关(即 $^W
变量)会在 STRING 包含 ","
或 "#"
字符时产生警告。
tr/SEARCHLIST/REPLACEMENTLIST/cdsr
y/SEARCHLIST/REPLACEMENTLIST/cdsr
将搜索列表中找到的(如果指定了 /c
修饰符,则为未找到的)所有字符用替换列表中位置对应的字符进行音译,可能删除一些字符,具体取决于指定的修饰符。它返回被替换或删除的字符数。如果没有通过 =~
或 !~
运算符指定字符串,则对 $_
字符串进行音译。
对于 sed 爱好者,y
是 tr
的同义词。
如果存在 /r
(非破坏性)选项,则会创建一个字符串的新副本并对其字符进行音译,无论该副本是否被修改,都会返回该副本:原始字符串始终保持不变。新副本始终是普通字符串,即使输入字符串是对象或绑定变量。
除非使用 /r
选项,否则使用 =~
指定的字符串必须是标量变量、数组元素、哈希元素或对其中之一的赋值;换句话说,必须是左值。
分隔 SEARCHLIST 和 REPLACEMENTLIST 的字符可以是任何可打印字符,而不仅仅是正斜杠。如果它们是单引号(tr'SEARCHLIST'REPLACEMENTLIST'
),则唯一的插值是删除 \\
对中的 \
;因此,连字符被解释为字面意思,而不是指定字符范围。
否则,可以使用连字符指定字符范围,因此tr/A-J/0-9/
与tr/ACEGIBDFHJ/0246813579/
执行相同的替换。
如果SEARCHLIST用括号引号分隔,则REPLACEMENTLIST必须有它自己的引号对,这些引号可以是括号引号,也可以不是;例如,tr(aeiouy)(yuoiea)
或tr[+\-*/]"ABCD"
。最后一个示例展示了一种方法,可以直观地阐明对更熟悉正则表达式模式而不是tr
的人来说发生了什么,这些人可能认为正斜杠分隔符意味着tr
更像正则表达式模式,而不是它实际的样子。(另一个选择可能是使用tr[...][...]
。)
tr
并不完全像括号字符类,只是(显著地)比它更像字符类,而不是完整的模式。例如,在两个列表中多次出现的字符在这里的行为与模式中的行为不同,并且tr
列表不允许使用反斜杠字符类,例如\d
或\pL
,也不允许变量插值,因此"$"
和"@"
始终被视为字面量。
允许的元素是字面量加上\'
(表示单引号)。如果分隔符不是单引号,则还允许在双引号字符串中接受的任何转义序列。转义序列的详细信息在本节开头附近的表格中。
开头或结尾的连字符,或前面有反斜杠的连字符,也始终被视为字面量。在分隔符字符前面加上反斜杠以允许它。
tr
运算符不等于tr(1)
实用程序。tr[a-z][A-Z]
将把 26 个字母“a”到“z”大写,但对于不限于 ASCII 的大小写转换,请使用lc
、uc
、lcfirst
、ucfirst
(所有这些都在perlfunc中记录),或替换运算符s/PATTERN/REPLACEMENT/
(在REPLACEMENT部分使用\U
、\u
、\L
和\l
字符串插值转义)。
大多数范围在字符集之间不可移植,但某些范围会向 Perl 发出信号,使其进行特殊处理以使其可移植。有两种可移植范围类别。第一类是A-Z
、a-z
和0-9
范围的任何子集,当以字面字符表示时。
tr/h-k/H-K/
将字母"h"
、"i"
、"j"
和 "k"
转换为大写,其他字符保持不变,无论平台的字符集是什么。相比之下,所有
tr/\x68-\x6B/\x48-\x4B/
tr/h-\x6B/H-\x4B/
tr/\x68-k/\x48-K/
在 ASCII 平台上运行时,执行与上一个示例相同的转换,但在 EBCDIC 平台上则完全不同。
当范围的两个端点之一或两者都用 \N{...}
表示时,就会调用第二类可移植范围。
$string =~ tr/\N{U+20}-\N{U+7E}//d;
从 $string
中删除所有与 Unicode U+0020、U+0021、... U+007D、U+007E 相等的平台字符。这是一个可移植范围,在所有运行它的平台上都有相同的效果。在这个例子中,这些是 ASCII 可打印字符。因此,在运行完此操作后,$string
中只包含控制字符和没有 ASCII 等效字符的字符。
但是,即使对于可移植范围,如果没有查阅手册,通常也不清楚其中包含什么。一个合理的原则是在范围的起始和结束位置都使用 ASCII 字母(b-e
、B-E
)或数字(1-4
)。其他任何情况都不清楚(除非使用 \N{...}
)。如果有疑问,请完整地列出字符集。
选项
c Complement the SEARCHLIST.
d Delete found but unreplaced characters.
r Return the modified string and leave the original string
untouched.
s Squash duplicate replaced characters.
如果指定了 /d
修饰符,则删除 SEARCHLIST 中未在 REPLACEMENTLIST 中找到的任何字符。(请注意,这比某些 tr 程序的行为更灵活,后者会删除在 SEARCHLIST 中找到的任何内容。)
如果指定了 /s
修饰符,则将所有连续的字符序列,这些序列都被转写为同一个字符,压缩为该字符的单个实例。
my $a = "aaabbbca";
$a =~ tr/ab/dd/s; # $a now is "dcd"
如果使用 /d
修饰符,则始终按指定的方式解释 REPLACEMENTLIST。否则,如果 REPLACEMENTLIST 比 SEARCHLIST 短,则重复最后一个字符(如果有)直到其长度足够。如果且仅当 REPLACEMENTLIST 为空时,才不会有最后一个字符,在这种情况下,REPLACEMENTLIST 将从 SEARCHLIST 复制。空 REPLACEMENTLIST 用于计算类中的字符数量或压缩类中的字符序列。
tr/abcd// tr/abcd/abcd/
tr/abcd/AB/ tr/abcd/ABBB/
tr/abcd//d s/[abcd]//g
tr/abcd/AB/d (tr/ab/AB/ + s/[cd]//g) - but run together
如果指定了/c
修饰符,则要音译的字符是SEARCHLIST中**没有**的字符,也就是说,它是被补充的。如果也指定了/d
和/或/s
,它们将应用于补充后的SEARCHLIST。请记住,如果REPLACEMENTLIST为空(在/d
下除外),则使用SEARCHLIST的副本代替。该副本是在/c
下补充后创建的。SEARCHLIST在补充后按代码点顺序排序,并且任何REPLACEMENTLIST都应用于该排序结果。这意味着在/c
下,SEARCHLIST中指定的字符的顺序无关紧要。如果REPLACEMENTLIST包含多个字符,这可能会导致 EBCDIC 系统上的结果不同,因此通常不建议将/c
与这样的REPLACEMENTLIST一起使用。
描述操作的另一种方法是:如果指定了/c
,则SEARCHLIST按代码点顺序排序,然后进行补充。如果REPLACEMENTLIST为空且未指定/d
,则REPLACEMENTLIST将被SEARCHLIST的副本(如在/c
下修改的那样)替换,并且这些可能修改后的列表将用作后续操作的基础。目标字符串中不在SEARCHLIST中的任何字符都将原样通过。目标字符串中的所有其他字符都将被REPLACEMENTLIST中位置上与其在SEARCHLIST中的对应字符相对应的字符替换,但如果指定了/s
,则在一系列连续的字符中,所有字符都翻译成同一个字符,则将挤出第二个及后续字符。如果SEARCHLIST比REPLACEMENTLIST长,则目标字符串中与SEARCHLIST中没有对应字符的字符相匹配的字符将从目标字符串中删除(如果指定了/d
);或者如果未指定/d
,则用REPLACEMENTLIST中的最后一个字符替换。
一些例子
$ARGV[1] =~ tr/A-Z/a-z/; # canonicalize to lower case ASCII
$cnt = tr/*/*/; # count the stars in $_
$cnt = tr/*//; # same thing
$cnt = $sky =~ tr/*/*/; # count the stars in $sky
$cnt = $sky =~ tr/*//; # same thing
$cnt = $sky =~ tr/*//c; # count all the non-stars in $sky
$cnt = $sky =~ tr/*/*/c; # same, but transliterate each non-star
# into a star, leaving the already-stars
# alone. Afterwards, everything in $sky
# is a star.
$cnt = tr/0-9//; # count the ASCII digits in $_
tr/a-zA-Z//s; # bookkeeper -> bokeper
tr/o/o/s; # bookkeeper -> bokkeeper
tr/oe/oe/s; # bookkeeper -> bokkeper
tr/oe//s; # bookkeeper -> bokkeper
tr/oe/o/s; # bookkeeper -> bokkopor
($HOST = $host) =~ tr/a-z/A-Z/;
$HOST = $host =~ tr/a-z/A-Z/r; # same thing
$HOST = $host =~ tr/a-z/A-Z/r # chained with s///r
=~ s/:/ -p/r;
tr/a-zA-Z/ /cs; # change non-alphas to single space
@stripped = map tr/a-zA-Z/ /csr, @original;
# /r with map
tr [\200-\377]
[\000-\177]; # wickedly delete 8th bit
$foo !~ tr/A/a/ # transliterate all the A's in $foo to 'a',
# return 0 if any were found and changed.
# Otherwise return 1
如果为一个字符给出多个音译,则只使用第一个音译
tr/AAA/XYZ/
将任何 A 音译为 X。
由于转写表是在编译时构建的,因此SEARCHLIST和REPLACEMENTLIST都不会进行双引号插值。这意味着如果您想使用变量,则必须使用eval()
eval "tr/$oldlist/$newlist/";
die $@ if $@;
eval "tr/$oldlist/$newlist/, 1" or die $@;
<<EOF
一种面向行的引用形式基于 shell 的“here-document”语法。在<<
之后,您指定一个字符串来终止引用的内容,并且从当前行到终止字符串的所有行都是该项的值。
在终止字符串前加上~
表示您想使用"缩进的 Here-docs"(见下文)。
终止字符串可以是标识符(一个词)或一些引用的文本。未引用的标识符的作用类似于双引号。<<
和标识符之间不能有空格,除非标识符被显式引用。终止字符串必须单独出现(未引用且周围没有空格)在终止行上。
如果终止字符串被引用,则使用的引号类型决定文本的处理方式。
双引号表示文本将使用与普通双引号字符串完全相同的规则进行插值。
print <<EOF;
The price is $Price.
EOF
print << "EOF"; # same as above
The price is $Price.
EOF
单引号表示文本将被视为字面量,不进行内容插值。这类似于单引号字符串,除了反斜杠没有特殊含义,\\
被视为两个反斜杠,而不是像其他所有引用结构那样视为一个反斜杠。
就像在 shell 中一样,<<
后面的反斜杠裸词与单引号字符串具有相同的含义
$cost = <<'VISTA'; # hasta la ...
That'll be $10 please, ma'am.
VISTA
$cost = <<\VISTA; # Same thing!
That'll be $10 please, ma'am.
VISTA
这是 perl 中唯一一种不需要担心转义内容的引用形式,代码生成器可以并且确实很好地利用这一点。
here-doc 的内容将与字符串嵌入反引号中时一样对待。因此,内容将被插值,就好像它被双引号引用然后通过 shell 执行一样,执行结果将被返回。
print << `EOC`; # execute command and get results
echo hi there
EOC
here-doc 修饰符~
允许您缩进 here-docs,使代码更易读
if ($some_var) {
print <<~EOF;
This is a here-doc
EOF
}
这将打印...
This is a here-doc
...没有前导空格。
包含标记 here-doc 结束的定界符的行决定了整个内容的缩进模板。如果 here-doc 中的任何非空行没有以终止行的精确缩进开头,编译就会报错。(空行仅包含单个字符“\n”。)例如,假设终止行以一个制表符后跟 4 个空格字符开头。here-doc 中的每个非空行都必须以一个制表符后跟 4 个空格开头。它们将从每行中剥离,并且任何一行上剩余的前导空格将作为该行的缩进。目前,只有 TAB 和 SPACE 字符被视为此目的的空格。制表符和空格可以混合使用,但必须完全匹配;制表符保持制表符,不会扩展。
额外的开头空格(超出定界符之前的空格)将被保留
print <<~EOF;
This text is not indented
This text is indented with two spaces
This text is indented with two tabs
EOF
最后,修饰符可以与上面提到的所有形式一起使用
<<~\EOF;
<<~'EOF'
<<~"EOF"
<<~`EOF`
并且可以在 ~
和引号定界符之间使用空格
<<~ 'EOF'; # ... "EOF", `EOF`
可以将多个 here-doc 叠加在一起
print <<"foo", <<"bar"; # you can stack them
I said foo.
foo
I said bar.
bar
myfunc(<< "THIS", 23, <<'THAT');
Here's a line
or two.
THIS
and here's another.
THAT
只是不要忘记在末尾加上分号以完成语句,因为 Perl 不知道你不会尝试这样做
print <<ABC
179231
ABC
+ 20;
如果你想从你的 here-doc 中删除行终止符,使用 chomp()
。
chomp($string = <<'END');
This is a string.
END
如果你想让你的 here-doc 与代码的其余部分缩进,使用 <<~FOO
结构,如 "缩进的 Here-doc" 中所述
$quote = <<~'FINIS';
The Road goes ever on and on,
down from the door where it began.
FINIS
如果你在分隔的结构中使用 here-doc,例如在 s///eg
中,引用的材料仍然必须出现在 <<FOO
标记后的行上,这意味着它可能在分隔的结构中
s/this/<<E . 'that'
the other
E
. 'more '/eg;
它在 Perl 5.18 之后一直这样工作。从历史上看,它是不一致的,你必须写
s/this/<<E . 'that'
. 'more '/eg;
the other
E
在字符串求值之外。
此外,字符串结束标识符的引用规则与 Perl 的引用规则无关。q()
、qq()
等不支持代替 ''
和 ""
,并且唯一的插值是用于反斜杠转义引号字符
print << "abc\"def";
testing...
abc"def
最后,引用的字符串不能跨越多行。一般规则是标识符必须是字符串字面量。坚持这一点,你应该安全。
当遇到可能有多种解释的语句时,Perl 会使用 **DWIM**(即“Do What I Mean”)原则来选择最可能的解释。这种策略非常成功,以至于 Perl 程序员通常不会怀疑他们所写内容的歧义性。但有时,Perl 的理解与作者的本意会有很大差异。
本节旨在阐明 Perl 如何处理引号构造。虽然学习这方面知识最常见的原因是为了解开复杂的正则表达式,但由于解析的初始步骤对于所有引号运算符都是相同的,因此我们将一起讨论它们。
Perl 最重要的解析规则是下面讨论的第一个规则:在处理引号构造时,Perl 首先找到该构造的结尾,然后解释其内容。如果你理解了这条规则,你可以在第一次阅读时跳过本节的其余部分。其他规则与用户期望产生矛盾的频率可能远低于这条规则。
下面讨论的一些步骤是同时执行的,但由于它们的结果相同,因此我们分别考虑它们。对于不同的引号构造,Perl 执行的步骤数量不同,从一个到四个不等,但这些步骤始终按相同的顺序执行。
第一步是查找引号构造的结尾。这将导致将文本(在起始和结束分隔符之间)的副本保存到安全位置,并根据需要进行规范化,以避免需要知道原始分隔符是什么。
如果构造是 here-doc,则结束分隔符是包含终止字符串的行。因此,<<EOF
由紧随其后的 EOF
和 "\n"
终止,并且从终止行的第一列开始。在搜索 here-doc 的终止行时,不会跳过任何内容。换句话说,here-doc 语法之后的行将逐行与终止字符串进行比较。
对于除 here-docs 之外的构造,单个字符用作起始和结束分隔符。如果起始分隔符是开括号(即 (
、[
、{
或 <
),则结束分隔符是相应的闭括号(即 )
、]
、}
或 >
)。如果起始分隔符是未配对的字符,如 /
或闭括号,则结束分隔符与起始分隔符相同。因此,/
终止 qq//
构造,而 ]
终止 qq[]
和 qq]]
构造。
在搜索单字符分隔符时,转义的分隔符和\\
会被跳过。例如,在搜索终止符/
时,\\
和\/
的组合会被跳过。如果分隔符是括号,嵌套的括号对也会被跳过。例如,在搜索与起始符[
配对的结束符]
时,\\
、\]
和\[
的组合都会被跳过,嵌套的[
和]
也会被跳过。但是,当反斜杠用作分隔符(如qq\\
和tr\\\
)时,不会跳过任何内容。在搜索结束符的过程中,用于转义分隔符或其他反斜杠的反斜杠会被移除(准确地说,它们不会被复制到安全位置)。
对于具有三部分分隔符的结构(s///
、y///
和tr///
),搜索会重复一次。如果第一个分隔符不是一个起始标点符号,则三个分隔符必须相同,例如s!!!
和tr)))
,在这种情况下,第二个分隔符会同时终止左侧部分并开始右侧部分。如果左侧部分由括号标点符号分隔(即()
、[]
、{}
或<>
),则右侧部分需要另一对分隔符,例如s(){}
和tr[]//
。在这些情况下,两个部分之间允许使用空格和注释,但注释必须至少跟随一个空格字符;否则,作为注释开始的字符可能会被视为右侧部分的起始分隔符。
在搜索过程中,不会关注结构的语义。因此
"$hash{"$foo/$bar"}"
或
m/
bar # NOT a comment, this slash / terminated m//!
/x
不会形成合法的引号表达式。引号部分在第一个"
和/
处结束,其余部分恰好是一个语法错误。因为终止m//
的斜杠后面跟着一个SPACE
,所以上面的例子不是m//x
,而是没有/x
修饰符的m//
。因此,嵌入的#
被解释为一个字面#
。
同样,在搜索过程中也不会关注\c\
(多字符控制字符语法)。因此,qq/\c\/
中的第二个\
被解释为\/
的一部分,而接下来的/
不被识别为分隔符。相反,在引号结构的末尾使用\034
或\x1c
。
下一步是在获得的文本中进行插值,现在它与分隔符无关。有多种情况。
<<'EOF'
不执行插值。请注意,\\
组合保持不变,因为转义的分隔符不适用于here-docs。
m''
,s'''
的模式在此阶段不执行任何插值。任何包含\\
的反斜杠序列将在"解析正则表达式"阶段处理。
''
, q//
, tr'''
, y'''
,s'''
的替换唯一的插值是将\\
对中的\
移除。因此,tr'''
和y'''
中的"-"
被视为字面上的连字符,没有字符范围可用。s'''
替换中的\1
不作为$1
工作。
tr///
, y///
不会发生变量插值。用于大小写和引用的字符串修改组合,例如\Q
、\U
和\E
,不被识别。其他转义序列,例如\200
和\t
,以及反斜杠字符,例如\\
和\-
,将转换为相应的字面量。字符"-"
被特殊对待,因此\-
被视为字面量"-"
。
""
, ``
, qq//
, qx//
, <file*glob>
, <<"EOF"
\Q
、\U
、\u
、\L
、\l
、\F
(可能与\E
配对)将转换为相应的 Perl 结构。因此,"$foo\Qbaz$bar"
在内部转换为$foo . (quotemeta("baz" . $bar))
。其他转义序列,例如\200
和\t
,以及反斜杠字符,例如\\
和\-
,将被替换为相应的扩展。
需要强调的是,任何位于\Q
和\E
之间的内容都将以通常的方式进行插值。类似于"\Q\\E"
这样的内容在内部没有\E
。相反,它包含\Q
、\\
和E
,因此结果与"\\\\E"
相同。作为一般规则,\Q
和\E
之间的反斜杠可能会导致违反直觉的结果。因此,"\Q\t\E"
将转换为quotemeta("\t")
,这与"\\\t"
相同(因为TAB不是字母数字)。还要注意,
$str = '\t';
return "\Q$str";
可能更接近于"\Q\t\E"
编写者所推测的意图。
插值的标量和数组在内部转换为join
和"."
连接操作。因此,"$foo XXX '@arr'"
将变为
$foo . " XXX '" . (join $", @arr) . "'";
以上所有操作都是同时执行的,从左到右。
由于"\Q STRING \E"
的结果对所有元字符进行了引用,因此无法在\Q\E
对内插入字面$
或@
。如果由\
保护,$
将被引用为"\\\$"
;如果没有,它将被解释为插值标量的开始。
还要注意,插值代码需要对插值标量结束的位置做出决定。例如,"a $x -> {c}"
是否真的意味着
"a " . $x . " -> {c}";
或
"a " . $x -> {c};
大多数情况下,最长的可能文本不包含组件之间的空格,并且包含匹配的括号或方括号。由于结果可能是通过基于启发式估计器的投票来确定的,因此结果并非严格可预测。幸运的是,对于模棱两可的情况,它通常是正确的。
s///
的替换\Q
、\U
、\u
、\L
、\l
、\F
和插值的处理方式与qq//
构造相同。
正是在这一步,\1
在s///
的替换文本中不情愿地转换为$1
,以纠正那些尚未掌握更明智的习语的顽固的sed黑客。如果设置了use warnings
pragma或-w命令行标志(即$^W
变量),则会发出警告。
RE
在m?RE?
、/RE/
、m/RE/
、s/RE/foo/
中,\Q
、\U
、\u
、\L
、\l
、\F
、\E
和插值的处理方式(几乎)与qq//
构造相同。
\N{...}
的处理也在此完成,并被编译成正则表达式编译器的中间形式。(这是因为,如下所述,正则表达式编译可能在执行时完成,而 \N{...}
是一个编译时构造。)
但是,任何其他 \
后跟字符的组合都不会被替换,而只是被跳过,以便在后续步骤中将它们解析为正则表达式。由于 \c
在此步骤中被跳过,因此 RE 中 \c@
的 @
可能被视为数组符号(例如 @foo
),即使 qq//
中的相同文本会对 \c@
进行插值。
代码块,例如 (?{BLOCK})
,通过将控制权临时传递回 Perl 解析器来处理,类似于插值数组下标表达式,例如 "foo$array[1+f("[xyz")]bar"
的处理方式。
此外,在 (?{BLOCK})
、(?# comment )
和 /x
正则表达式中的 #
注释中,不会执行任何处理。这是 /x
修饰符的存在相关的第一个步骤。
模式中的插值有一些怪癖:$|
、$(
、$)
、@+
和 @-
不会被插值,并且构造 $var[SOMETHING]
被多个不同的估算器投票认为是数组元素或 $var
后跟 RE 备选方案。这就是 ${arr[$bar]}
符号派上用场的地方:/${arr[0-9]}/
被解释为数组元素 -9
,而不是来自变量 $arr
的正则表达式后跟一个数字,这将是 /$arr[0-9]/
的解释。由于可能发生不同估算器之间的投票,因此结果不可预测。
对 \\
的处理缺失会对后处理文本造成特定限制。如果分隔符是 /
,则无法将组合 \/
作为此步骤的结果。/
将结束正则表达式,\/
将在之前的步骤中被剥离为 /
,而 \\/
将保持原样。因为 /
在正则表达式内部等效于 \/
,所以这无关紧要,除非分隔符恰好是 RE 引擎的特殊字符,例如在 s*foo*bar*
、m[foo]
或 m?foo?
中;或者是一个字母数字字符,例如在
m m ^ a \s* b mmx;
上面的 RE 中,为了说明目的,故意进行了混淆,分隔符是 m
,修饰符是 mx
,并且在删除分隔符后,RE 与 m/ ^ a \s* b /mx
相同。鼓励您将分隔符限制为非字母数字、非空格选择的原因不止一个。
此步骤是除正则表达式之外所有构造的最后一个步骤,正则表达式将被进一步处理。
之前的步骤是在编译 Perl 代码时执行的,但这一步是在运行时发生的,尽管如果合适,它可能会被优化为在编译时计算。在完成上述预处理后,以及可能在评估连接、联接、大小写转换或元引用后,生成的字符串将传递给 RE 引擎进行编译。
RE 引擎中发生的事情可能在 perlre 中有更好的讨论,但为了连续性,我们将在本文中进行讨论。
这是另一个与 /x
修饰符的存在相关的步骤。RE 引擎从左到右扫描字符串,并将其转换为有限自动机。
反斜杠字符要么被替换为相应的文字字符串(如 \{
),要么在有限自动机中生成特殊节点(如 \b
)。RE 引擎的特殊字符(如 |
)会生成相应的节点或节点组。(?#...)
注释将被忽略。所有其他内容要么被转换为要匹配的文字字符串,要么被忽略(如果存在 /x
,则空格和 #
样式的注释也是如此)。
对括号字符类构造 [...]
的解析与用于模式其余部分的规则有所不同。此构造的终止符的查找规则与查找 {}
分隔构造的终止符的规则相同,唯一的例外是紧随 [
之后的 ]
被视为在前面有反斜杠。
运行时 (?{...})
的终止符的查找是通过暂时将控制权切换到 perl 解析器来完成的,解析器应该在找到逻辑上平衡的终止 }
的位置停止。
可以检查传递给 RE 引擎的字符串和生成的有限自动机。请参阅 use re
pragma 中的 debug
/debugcolor
参数,以及 Perl 的 -Dr 命令行开关,该开关在 "perlrun 中的命令行开关" 中有说明。
此步骤仅出于完整性而列出。由于它不改变语义,因此此步骤的细节没有记录,并且可能会在未经通知的情况下更改。此步骤是在上一步生成的有限自动机上执行的。
在这个阶段,split()
会默默地优化 /^/
,使其表示 /^/m
。
有一些 I/O 运算符你需要了解。
用反引号(重音符)括起来的字符串首先会进行双引号插值。然后它被解释为外部命令,该命令的输出是反引号字符串的值,就像在 shell 中一样。在标量上下文中,返回一个包含所有输出的单个字符串。在列表上下文中,返回一个值列表,每行输出一个值。(你可以设置 $/
来使用不同的行终止符。)每次评估伪字面量时都会执行该命令。命令的状态值在 $?
中返回(有关 $?
的解释,请参见 perlvar)。与 csh 不同,返回数据不会进行任何转换——换行符仍然是换行符。与任何 shell 不同,单引号不会隐藏命令中变量名的解释。要将一个字面美元符号传递给 shell,你需要用反斜杠将其隐藏起来。反引号的通用形式是 qx//
,或者你可以调用 "perlfunc 中的 readpipe" 函数。(因为反引号总是会进行 shell 展开,所以请参见 perlsec 以了解安全问题。)
在标量上下文中,评估尖括号中的文件句柄会产生该文件中的下一行(包括换行符,如果有),或者在文件末尾或出错时产生 undef
。当 $/
设置为 undef
(有时称为文件吸入模式)并且文件为空时,它第一次返回 ''
,之后返回 undef
。
通常你必须将返回值赋给一个变量,但有一种情况会发生自动赋值。当且仅当输入符号是 while
语句条件中唯一的元素(即使它被伪装成 for(;;)
循环)时,该值会自动赋给全局变量 $_
,销毁之前存在的所有内容。(这对你来说可能看起来很奇怪,但你将在你编写的几乎所有 Perl 脚本中使用这种结构。)$_
变量不会被隐式局部化。如果你想让它发生,你必须在循环之前放置一个 local $_;
。此外,如果输入符号或对输入符号的显式赋值被用作 while
/for
条件,那么条件实际上测试的是表达式的值的定义性,而不是其常规的真值。
因此,以下几行是等效的
while (defined($_ = <STDIN>)) { print; }
while ($_ = <STDIN>) { print; }
while (<STDIN>) { print; }
for (;<STDIN>;) { print; }
print while defined($_ = <STDIN>);
print while ($_ = <STDIN>);
print while <STDIN>;
这也有类似的行为,但将值分配给词法变量而不是 $_
while (my $line = <STDIN>) { print $line }
在这些循环结构中,分配的值(无论是自动分配还是显式分配)都会被测试以查看它是否已定义。定义测试避免了行具有字符串值(Perl 会将其视为假)的情况,例如 "" 或 "0"
且没有尾随换行符。如果您确实希望此类值终止循环,则应显式测试它们
while (($_ = <STDIN>) ne '0') { ... }
while (<STDIN>) { last unless $_; ... }
在其他布尔上下文中,<FILEHANDLE>
在没有显式 defined
测试或比较的情况下,如果 use warnings
pragma 或 -w 命令行开关($^W
变量)生效,则会引发警告。
文件句柄 STDIN、STDOUT 和 STDERR 是预定义的。(文件句柄 stdin
、stdout
和 stderr
也将起作用,除非在包中,它们将被解释为局部标识符而不是全局标识符。)可以使用 open()
函数创建其他文件句柄,以及其他函数。有关详细信息,请参阅 perlopentut 和 "open" in perlfunc。
如果 <FILEHANDLE>
用于需要列表的上下文中,则将返回一个包含所有输入行的列表,每个列表元素对应一行。这种方式很容易增长到相当大的数据空间,因此请谨慎使用。
<FILEHANDLE>
也可以写成 readline(*FILEHANDLE)
。请参阅 "readline" in perlfunc。
空文件句柄 <>
(有时称为菱形运算符)是特殊的:它可以用来模拟 sed 和 awk 的行为,以及任何其他接受文件名列表的 Unix 过滤程序,对所有文件名中的每一行输入执行相同的操作。来自 <>
的输入来自标准输入,或者来自命令行中列出的每个文件。以下是它的工作原理:<>
第一次被评估时,会检查 @ARGV
数组,如果它为空,则 $ARGV[0]
被设置为 "-"
,当打开时,它会给你标准输入。然后 @ARGV
数组被处理为一个文件名列表。循环
while (<>) {
... # code for each line
}
等效于以下类似 Perl 的伪代码
unshift(@ARGV, '-') unless @ARGV;
while ($ARGV = shift) {
open(ARGV, $ARGV);
while (<ARGV>) {
... # code for each line
}
}
除了它没有那么繁琐,而且实际上可以工作。它确实会移位 @ARGV
数组并将当前文件名放入 $ARGV
变量中。它还在内部使用文件句柄 ARGV。<>
只是 <ARGV>
的同义词,它是神奇的。(上面的伪代码不起作用,因为它将 <ARGV>
视为非神奇的。)
由于空文件句柄使用 "open" in perlfunc 的两个参数形式,因此它会解释特殊字符,因此如果您有这样的脚本
while (<>) {
print;
}
并用 perl dangerous.pl 'rm -rfv *|'
调用它,它实际上会打开一个管道,执行 rm
命令并从该管道读取 rm
的输出。如果您希望 @ARGV
中的所有项目都被解释为文件名,您可以使用 CPAN 中的 ARGV::readonly
模块,或者使用双菱形括号
while (<<>>) {
print;
}
在 while 中使用双尖括号会导致 open 使用三个参数形式(第二个参数为 <
),因此 ARGV
中的所有参数都被视为文字文件名(包括 "-"
)。(请注意,为了方便起见,如果您使用 <<>>
并且 @ARGV
为空,它仍然会从标准输入读取。)
您可以在第一个<>
之前修改@ARGV
,只要数组最终包含您真正想要的的文件名列表。行号 ($.
) 继续像输入是一个大而快乐的文件一样。有关如何在每个文件上重置行号的示例,请参见 "eof" in perlfunc。
如果您想将@ARGV
设置为您自己的文件列表,请继续。这将@ARGV
设置为所有纯文本文件(如果未给出@ARGV
)。
@ARGV = grep { -f && -T } glob('*') unless @ARGV;
您甚至可以将它们设置为管道命令。例如,这会自动通过 gzip 过滤压缩参数
@ARGV = map { /\.(gz|Z)$/ ? "gzip -dc < $_ |" : $_ } @ARGV;
如果您想将开关传递到您的脚本中,您可以使用其中一个Getopts
模块或在前面添加一个循环,如下所示
while ($_ = $ARGV[0], /^-/) {
shift;
last if /^--$/;
if (/^-D(.*)/) { $debug = $1 }
if (/^-v/) { $verbose++ }
# ... # other switches
}
while (<>) {
# ... # code for each line
}
<>
符号只会在文件结束时返回undef
。如果您在此之后再次调用它,它将假定您正在处理另一个@ARGV
列表,如果您没有设置@ARGV
,它将从 STDIN 读取输入。
如果尖括号中包含的是一个简单的标量变量(例如,$foo
),那么该变量包含要从中输入的文件句柄的名称,或其类型全局变量,或对同一变量的引用。例如
$fh = \*STDIN;
$line = <$fh>;
如果尖括号中的内容既不是文件句柄,也不是包含文件句柄名称、类型全局变量或类型全局变量引用的简单标量变量,则它将被解释为要进行 glob 的文件名模式,并且将返回文件名列表或列表中的下一个文件名,具体取决于上下文。这种区别仅在语法基础上确定。这意味着<$x>
始终是从间接句柄进行的readline()
,但<$hash{key}>
始终是glob()
。这是因为$x
是一个简单的标量变量,而$hash{key}
不是——它是一个哈希元素。即使<$x >
(注意额外的空格)也被视为glob("$x ")
,而不是readline($x)
。
首先进行一层双引号解释,但您不能说<$foo>
,因为正如上一段所述,这是一个间接文件句柄。(在较旧版本的 Perl 中,程序员会插入花括号以强制解释为文件名 glob:<${foo}>
。如今,直接调用内部函数glob($foo)
被认为更简洁,这可能一开始就是正确的方法。)例如
while (<*.c>) {
chmod 0644, $_;
}
大致等同于
open(FOO, "echo *.c | tr -s ' \t\r\f' '\\012\\012\\012\\012'|");
while (<FOO>) {
chomp;
chmod 0644, $_;
}
除了 glob 实际上是使用标准File::Glob
扩展在内部完成的。当然,执行上述操作的最短方法是
chmod 0644, <*.c>;
(文件)glob 仅在开始新列表时才评估其(嵌入)参数。必须读取所有值,然后它才会重新开始。在列表上下文中,这并不重要,因为您会自动获得所有值。但是,在标量上下文中,该运算符每次被调用时都会返回下一个值,或者在列表用完时返回undef
。与文件句柄读取一样,当 glob 出现在while
的测试部分时,会自动生成defined
,因为合法的 glob 返回值(例如,名为0 的文件)否则将终止循环。同样,undef
只返回一次。因此,如果您期望从 glob 中获得单个值,最好说
($file) = <blurch*>;
比
$file = <blurch*>;
因为后者将在返回文件名和返回 false 之间交替。
如果你想进行变量插值,使用 glob()
函数绝对更好,因为旧的符号会导致人们将它与间接文件句柄符号混淆。
@files = glob("$dir/*.[ch]");
@files = glob($files[$i]);
如果一个基于尖括号的通配符表达式用作 while
或 for
循环的条件,那么它将被隐式地赋值给 $_
。如果一个通配符表达式或一个显式地将通配符表达式赋值给标量的表达式用作 while
/for
条件,那么条件实际上测试的是表达式的值的定义性,而不是它的常规真值。
与 C 语言类似,Perl 在编译时会对表达式进行一定程度的求值,只要它确定操作符的所有参数都是静态的并且没有副作用。特别是,字符串连接在编译时发生在不进行变量替换的字面量之间。反斜杠插值也发生在编译时。你可以说
'Now is the time for all'
. "\n"
. 'good men to come to.'
所有这些在内部都简化为一个字符串。同样,如果你说
foreach $file (@filenames) {
if (-s $file > 5 + 100 * 2**16) { }
}
编译器会预先计算该表达式表示的数字,这样解释器就不必再计算了。
Perl 并没有正式的无操作符,但裸常量 0
和 1
是特殊情况,不会在空上下文产生警告,因此你可以安全地执行以下操作
1 while foo();
任何大小的位串都可以通过按位运算符 (~ | & ^
) 进行操作。
如果二元按位运算符的操作数是不同大小的字符串,则 | 和 ^ 运算符的行为就好像较短的操作数在右侧有额外的零位一样,而 & 运算符的行为就好像较长的操作数被截断为较短的操作数的长度一样。这种扩展或截断的粒度是一个或多个字节。
# ASCII-based examples
print "j p \n" ^ " a h"; # prints "JAPH\n"
print "JA" | " ph\n"; # prints "japh\n"
print "japh\nJunk" & '_____'; # prints "JAPH\n";
print 'p N$' ^ " E<H\n"; # prints "Perl\n";
如果你打算操作位串,请确保你提供的是位串:如果操作数是一个数字,那么将意味着一个 数值 按位运算。你可以通过使用 ""
或 0+
来明确地显示你打算执行哪种类型的操作,如下面的示例所示。
$foo = 150 | 105; # yields 255 (0x96 | 0x69 is 0xFF)
$foo = '150' | 105; # yields 255
$foo = 150 | '105'; # yields 255
$foo = '150' | '105'; # yields string '155' (under ASCII)
$baz = 0+$foo & 0+$bar; # both ops explicitly numeric
$biz = "$foo" ^ "$bar"; # both ops explicitly stringy
这种有些不可预测的行为可以通过 Perl 5.22 中的新功能“按位”来避免。你可以通过 use feature 'bitwise'
或 use v5.28
来启用它。在 Perl 5.28 之前,它会在 "experimental::bitwise"
类别中发出警告。在这个功能下,四个标准按位运算符 (~ | & ^
) 始终是数值。在每个运算符后添加一个点 (~. |. &. ^.
) 会强制它将它的操作数视为字符串
use feature "bitwise";
$foo = 150 | 105; # yields 255 (0x96 | 0x69 is 0xFF)
$foo = '150' | 105; # yields 255
$foo = 150 | '105'; # yields 255
$foo = '150' | '105'; # yields 255
$foo = 150 |. 105; # yields string '155'
$foo = '150' |. 105; # yields string '155'
$foo = 150 |.'105'; # yields string '155'
$foo = '150' |.'105'; # yields string '155'
$baz = $foo & $bar; # both operands numeric
$biz = $foo ^. $bar; # both operands stringy
这些运算符的赋值变体 (&= |= ^= &.= |.= ^.=
) 在该功能下也具有类似的行为。
如果操作数包含一个序数值大于 0xFF 的字符,则这是一个致命错误,因此除了 UTF-8 外无法表达。该操作在其他以 UTF-8 编码的操作数的非 UTF-8 副本上执行。请参阅 "perlunicode 中的字节和字符语义"。
有关如何在位向量中操作单个位的详细信息,请参阅 "perlfunc 中的 vec"。
默认情况下,Perl 假设它必须在浮点数中执行大部分算术运算。但通过说
use integer;
您可以告诉编译器从这里到封闭 BLOCK 的末尾使用整数运算(有关详细说明,请参阅 integer)。内部 BLOCK 可以通过说
no integer;
来反驳这一点,该语句持续到该 BLOCK 的末尾。请注意,这并不意味着所有内容都是整数,而仅仅意味着 Perl 将对算术运算、比较运算和按位运算符使用整数运算。例如,即使在 use integer
下,如果您取 sqrt(2)
,您仍然会得到 1.4142135623731
左右。
用于数字的按位运算符(&
|
^
~
<<
>>
)始终产生整数值。(但另请参阅 "按位字符串运算符"。)但是,use integer
对它们仍然有意义。默认情况下,它们的结果被解释为无符号整数,但如果 use integer
生效,它们的结果被解释为有符号整数。例如,~0
通常计算为一个很大的整数值。但是,use integer; ~0
在二进制补码机器上是 -1
。
虽然 use integer
提供仅整数的算术运算,但没有类似的机制来提供自动舍入或截断到某个小数位数。对于舍入到某个位数,sprintf()
或 printf()
通常是最简单的途径。请参阅 perlfaq4。
浮点数只是对数学家所说的实数的近似值。实数的数量远远超过浮点数,因此必须做出一些妥协。例如
printf "%.20g\n", 123456789123456789;
# produces 123456789123456784
测试浮点数的精确相等或不相等不是一个好主意。以下是一个(相对昂贵的)解决方法,用于比较两个浮点数是否等于特定的小数位数。有关此主题的更稳健的处理,请参阅 Knuth,第二卷。
sub fp_equal {
my ($X, $Y, $POINTS) = @_;
my ($tX, $tY);
$tX = sprintf("%.${POINTS}g", $X);
$tY = sprintf("%.${POINTS}g", $Y);
return $tX eq $tY;
}
POSIX 模块(标准 perl 发行版的一部分)实现了 ceil()
、floor()
以及其他数学和三角函数。Math::Complex
模块(标准 perl 发行版的一部分)定义了对实数和虚数都有效的数学函数。Math::Complex
不如 POSIX 效率高,但 POSIX 无法处理复数。
在金融应用中,舍入可能会产生严重的影响,因此应精确指定所使用的舍入方法。在这种情况下,最好不要依赖 Perl 使用的任何系统舍入,而是自己实现所需的舍入函数。
标准的 Math::BigInt
、Math::BigRat
和 Math::BigFloat
模块,以及 bignum
、bigint
和 bigrat
编译指示,提供了可变精度的算术运算和重载运算符,尽管它们目前速度相当慢。以牺牲一些空间和相当的速度为代价,它们避免了与有限精度表示相关的常见陷阱。
use 5.010;
use bigint; # easy interface to Math::BigInt
$x = 123456789123456789;
say $x * $x;
+15241578780673678515622620750190521
或者使用有理数
use 5.010;
use bigrat;
$x = 3/22;
$y = 4/6;
say "x/y is ", $x/$y;
say "x*y is ", $x*$y;
x/y is 9/44
x*y is 1/11
几个模块允许您使用无限或固定精度进行计算(仅受内存和 CPU 时间限制)。还有一些非标准模块通过外部 C 库提供更快的实现。
这是一个简短但并不完整的总结
Math::String treat string sequences like numbers
Math::FixedPrecision calculate with a fixed precision
Math::Currency for currency calculations
Bit::Vector manipulate bit vectors fast (uses C)
Math::BigIntFast Bit::Vector wrapper for big numbers
Math::Pari provides access to the Pari C library
Math::Cephes uses the external Cephes C library (no
big numbers)
Math::Cephes::Fraction fractions via the Cephes library
Math::GMP another one using an external C library
Math::GMPz an alternative interface to libgmp's big ints
Math::GMPq an interface to libgmp's fraction numbers
Math::GMPf an interface to libgmp's floating point numbers
明智地选择。