本帖最后由 PerlMonk 于 2017-4-18 12:43 编辑
列表操作
",", sort,reverse,push, pop, shift, unshift, grep, map
使用 grep 过滤列表
grep 的使用可以分为表达式形式和 block 形式
筛选大于10的数并保存到另一个数组:
| my @input_numbers = (1, 2, 4, 8, 16, 32, 64); | | my @bigger_than_10 = grep $_ > 10, @input_numbers;COPY |
结果为 16, 32, 64
通过隐式引用来筛选末尾含有4的数字:my @end_in_4 = grep /4$/, @input_numbers;COPY 如果测试表达式较为复杂,可以写在一个子例程中,然后通过 grep 调用。
在一组数字中,提取个位十位... 相加 %2 余 1 的项: | my @odd_digit_sum = grep digit_sum_is_odd($_), @input_numbers; | | | | sub digit_sum_is_odd { | | my $input = shift; | | my @digits = split //, $input; | | my $sum; | | $sum += $_ for @digits; | | return $sum % 2; | | }COPY |
块形式(相比调用子例程的形式,少了 return。在这里使用 return 将退出 grep ): | my @odd_digit_sum = grep { | | my $sum; | | $sum += $_ for split //; | | $sum % 2; | | } @input_numbers;COPY |
使用 map 转换列表
| my @input_numbers = (1, 2, 4, 8, 16, 32, 64); | | my @result = map $_ + 100, @input_numbers;COPY |
以及 map 没有规定对于每一项只返回一个值my @result = map { $_, 3 * $_ } @input_numbers;COPY 借此可以快速从一个列表生成一组哈希映射,以便于做字典判断 | my %hash = map { $_, 1 } @castaways; | | | | my $person = 'Gilligan'; | | if( $hash{$person} ) { | | print "$person is a castaway.\n"; | | }COPY |
eval
| my $average = $total / $count; | | print "okay\n" unless /$match/; | | | | open MINNOW, '>', 'ship.txt' | | or die "Can't create 'ship.txt': $!"; | | | | implement($_) foreach @rescue_scheme; COPY |
其中每一行都有可能出错导致程序崩溃,但在实际应用中并不意味着应该结束整个程序,Perl 通过 eval 实现错误捕获: | eval { $average = $total / $count } ; | | print "Continuing after error: $@" if $@;COPY |
当 eval 代码块运行出错时,错误信息保存到 $@,eval 之后的代码继续运行。注意 eval 不是结构语句,末尾必须加分号。
eval 语句块也可以像函数一样 return,如果出错,返回空值(在标量环境返回 undef,在数组环境返回空列表)。现在可以安全地处理零除错误:my $average = eval { $total / $count };COPY $average 要么是"商"要么是"undef"。
注意
Perl 允许 eval 镶嵌使用。
eval 可以捕获一般的错误,但无法处理结束进程、内存溢出等情况
Try::Tiny | use Try::Tiny; | | my $average = try { $total / $count } catch { "NaN" };COPY |
执行动态生成的代码
eval 除了代码块的形式,还有一种字符串形式。在运行时编译运行某段字符串内的代码。这将带来一定风险,或许会执行带有攻击性的代码。
一个简短的示例:
| eval '$sum = 2 + 2'; | | print "The sum is $sum\n";COPY |
因为 eval 能够返回最后一句代码的结果,所以不必将赋值放在待执行的字符串中 | foreach my $operator ( qw(+ ? * /) ) { | | my $result = eval "2 $operator 2"; | | print "2 $operator 2 is $result\n"; | | }COPY |
和代码块形式一样,如果执行错误,有关信息将保留到 $@: | print 'The quotient is ', eval '5 /', "\n"; | | warn $@ if $@;COPY |
>The quotient is
>syntax error at (eval 1) line 2, at EOF
提醒
请谨慎使用 eval 执行字符串代码的形式,尽可能使用其他方法达到目的。在11章将介绍如何加载外部文件代码并执行,并使用更好的方法。
do
do 是 Perl 语言中一个强有力但容易被忽视的工具,用于将多个表达式组织到一个代码块中,
并像子例程一样返回最后执行的结果。
使用 do 语句块简化赋值判断
假设要为 $bowler 赋值,但是分为三种条件,需要写出多个 $bowler:
| my $bowler; | | if( ...some condition... ) { | | $bowler = 'Mary Ann'; | | } | | elsif( ... some condition ... ) { | | $bowler = 'Ginger'; | | } | | else { | | $bowler = 'The Professor'; | | }COPY |
如果改为 do 语句块的形式,只需要一个 $bowler | my $bowler = do { | | if( ... some condition ... ) { 'Mary Ann' } | | elsif( ... some condition ... ) { 'Ginger' } | | else { 'The Professor' } | | };COPY |
一次读取文件
do 能够创建一个包围作用域,当我们要一次读取整个文件内容到变量中,可以通过 do block 私有作用域,为 $/ 和 @ARGV 创建私有副本,从而能够使用 <> 句柄读取 $filename 参数的文件内容
| $filename = __FILE__; | | | | my $file_contents = do { | | local $/; | | local @ARGV = ( $filename ); | | <>; | | }; | | | | print $file_contents;COPY |
加载运行其他脚本代码
类似 eval,do 也有将字符串作为参数的形式,当传递的是字符串而非代码块时,do 假设传入的是文件,并从文件中读取代码编译执行:
do "slurp.pl";COPY 类似于 `eval "type slurp.pl";` 区别参考 perldoc -f do
缺点在于:即使执行的代码发生错误,程序仍会继续运行。并且即使是加载运行过的文件,仍会再次执行(对比 require)。基于这些原因,很少人使用 do 语句
我们知道,使用 use 加载模块,以及 use 语句在编译时运行。其实还可以通过 require 在运行时加载模块:require List::Util;COPY use List::Util 的实质是在 BEGIN 块中执行 require 以及 该模块的 import() 方法; | BEGIN { | | require List::Util; | | List::Util->import( ... ); | | }COPY |
通常 use 使用模块名的形式,而 require 还可以用文件名作为参数,导入文件:require $filename;COPY require 能够记住已经加载过的文件,对于重复的加载将不会再执行 ( 对比 do )。更多内容参考 12 章 - Creating Your Own Perl Distribution
[Finished in 0.1s] |