在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
我的评价: 本书是perl的经典入门书籍,介绍了perl中最基本的概念和语法,是perl入门的好书,我 向所有想了解或学习perl语言的朋友推荐本书。书中穿插有perl语法形成的历史和原因, 使你能充分感受到perl语言的文化,这也是perl流行的原动力。本书行文流畅,各知识点 介绍到位,令人很容易明白,达到入门点睛的效果。但本书的作用也就是入门而已,目的很 明确,它没有深入到perl脚本语言的高级部份。如果你想了解perl的高级功能或使用perl 来更好地完成你的日常工作,还需进一步学习,《perl语言编程》应该是你最好的选择。 第一章 简介 第二章 标量数据 什么是村量数据? 标量(scalar)是perl中最基本的元素。大多数标量要么是一个数字,要么是一个字符串。 perl基本上把它们视为可相互替换的东西。 数字 所有数字的内部格式都一样 在内部,perl一律把整数,浮点数按双精度浮点数进行计算。perl内部没有整数值----程序中 的一个整数常量被当作等值的浮点数。 直接量(literal)指的是在perl源代码中表示值的方式, 浮点直接量 1.33,233.5,-3.9 整数据直接量 0,89,-66,61_383_883_897_363(一个大数,用下划线以示清晰) 非十进制整数直接量 八进制直接量以0开头 十六进制直接量以0x开头 二进制以0b开头 从5.6版本开始,perl允许在直接量中加下划线以示清晰。0x50_65_7c 数值操作符 加 + 2 + 3 减 - 3 - 2 乘 * 2 * 3 除 / 2/3 模 % 10%3 指数 ** 2**3 字符串 单引号字符串直接量 在引号间除了单引号或反斜杠以外的任何字符(包括换行符,如果该 字符串连续占几行)在字符串中表示该字符本身。要想得到一个斜杠,需要连续写两个斜杠, 要得到一个单引号,需要加一个斜杠。 双引号字符串直接量 在双引号内的反斜杠开始发挥它的作用,可以用来指定特定的控制字 符。可以在双引号里面内插变量。 双引号内的转义字符完整列表 \n 换行 \r 回车 \t 制表符 \f 换页 \b 退格 \a 响铃 \e Esc(ascii的转义字符) \007 任意ASCII码的八进制值(这里007=响铃) \x7f 任意ASCII码的十六进制值(这里07f=删除) \cC 任意Ctrl键组合字符(这里是Ctrl-C) \\ 反斜杠 \" 双引号 \l 下一个字母小写 \L 所有后面的字母小写,直到\E \u 下一个字母大写 \U 所有后面的字母大写,直到\E \Q 添加反斜杠引用后面的非单词字符,直到\E \E 结束\L,\U,\Q 字符串操作符 . 连接两个字符串 "hello" . "world" = "helloworld" x 串重复操作符 "yang" x 3="yangyangyang" 次数使用前截成一个整数。4.8=4,小 于1的拷贝次数会得到一个空串。 数字与字符串的自动转换 依赖于作用在标量值上的操作符。如果是+则是数据,如果是.则是字符串。 "z".6*7="z42" ,"12"*"3"=36 perl的内置警告 可以要求perl在发现你程序有异常时给你一个警告。使用 -w 选项。 #!/usr/bin/perl -w 标量变量 变量(variable)是一个容器的名字,用以存放一个或多个值,变量的名字在程序中保护不 变,但它所包含的值在执行过程中一般要不停地改变。在perl中用美元符号标识。$a,$test。 选择好的变量名 适当加一些下划线可让变量更易读,更易理解。 标量赋值(assignment) 操作符用等号,它的左边是变量名,右边是值。 二元赋值操作符 += -= *= .= **= and so on 用print输出 print "hello world\n"; print "the answer",6*6,".\n"; 在字符串中替换标量变量 $meal = "brontosaurus steak"; $barney = "fred ate a $meal"; 另一种写法 $barney = 'fred ate a'.$meal; 如果标量变量从未被赋过值,就用一个空串替换。 操作符优先级和结合性 善用小括号 perlk中的操作符优先级和结合性与C一样 比较操作符 相等 == eq 不相等 != ne 小于 < lt 大于 > gt 小于等于 <= le 大于等于 >= ge if控制结构 if () { ...; } else { ...; } 布尔值 perl中没有单独的的布尔数据类型,不过,它使用几条简单的规则。 1、特殊值undef是假。 2、0是假,所有其它的数字是真。 3、空串''是假,所有其它的字符串一般是真。 4、一个例外,因为数字和字符是等价的,所以0的字符形式'0',和它的数值形式有同样的值: 假。 ! 是取反的意思,可以在真值前加,结果就变成了假。 取得用户输入 <STDIN>从键盘得到一个值。一般以\n字符结束。所以可利用该字符做条件控制。 chomp操作符 它作用于一个变量,此变量必须存放一个字符串,如果这个字符串以换行符结尾,chomp 就把它去掉。 $text = "a line of text\n"; chomp ($text); 去掉换行符 chomp($text =<STDIN>); 最常用的方法,读文本,不带换行符 chomp是一个函数,所有它有返回值,即去掉的字符个数。 使用chomp时可以带或不带小括号,这是perl的另一个基本规则,除非去掉括号会改变意 思,否则括号总是可有可无的。 while控制结构 重复执行一个代码块,只要条件为真。 $count = 0; while ($count < 10 ) { $count +=1; print "count is now $count\n"; 得到从1到10的值。 } undef值 变量在第一次赋值之前有特别的undef值,代表什么也没有。当把它作为字符串时,其功能 相当于一个空串。当把它作为数字时,其功能相当于0。perl程序员经常这样使用。 很多操作符在操作数超出范围或没有意义时会返回undef值,这样一般不是什么问题,但如 果打开perl的警告,则会导至一个警告。 defined函数 测试一个值是undef还是非空字符串,可以使用这个函数。它对undef返回假,其它所有情 况则返回真: $madon = <STDIN>; if (defined($madon) { print "the input was $madon"; } else { print "no input available!\n"; } 如果你想生成自已的undef值,可以用古怪的undef操作符 $madon = undef; 好像它从来没有被动过 第三章 列表和数组 在perl中,如果“单数”是标量,那么“复数”则由列表和数组来表示。 列表(list)是一个有序的标量集合,数组(array)是一个包含列表的变量。精确地说,列表是数 据,而数组是变量,可以有一个不在数组的列表,但每个数组变量都包含一个列表。数组和 列表可以放任意多的元素,最小的是没有元素,而最大的可以把所有内存耗尽。这符合perl 的“没有不必要的限制”哲学。 访问数组的元素 数组元素用连续的整数编号,从0开始,然后按1递增。 $fred[0] = "a"; $fred[1] = "b"; $fred[2] = "c"; 如果下标所指的元素超出了数组的区间,那么相应的值就是undef。 特殊的数组索引 如果你试图存储一个超出数组区间的数组元素,这个数组就会自动按需扩展,对它的长度没 有限制,只要有足够的内存供perl使用。 $rocks[0] = "a"; $rocks[1] = "b"; $rocks[2] = "c"; $rocks[10] = "end"; 现在就有6个undef元素。 访问最后一个元素的索引是$#rocks。 负的数组索引从数组尾部算起,-1代表最后个元素。 列表直接量 列表直接量(list literal)是小括号中的一列由逗号分隔的值。例如: (1,2,3,4) (“fred”,43.4) () (1..100) (0..$#rocks) “..”是区间操作符,能生成一个顺序列表。如上例中的从1到100。列表可包含表达式或 变量。 qw快捷方式 qw表示“被括引的单词”(quoted words)或“用空白括住”(quoted by whitespace),perl 按单引号字符串处理它们,所以你不能像在双引号字符串中那样在qw列表中用\n或$fred)。 qw /a b c d/ 等同于 ("a","b","c","d") qw !a b c d! 等同于 ("a","b","c","d") qw {a b c d} 等同于 ("a","b","c","d") 定界符可以选择任意的标点。 列表赋值 ($fred,$barney,$dino) = ("a","b","c") ($fred,$barney) = ($barney,$fred) 交换它们的值,比c等语言方便很多。 如果变量的个数小于值的个数,则多余的值会被无声地忽略。 如果变量的个数多于值的个数,则多的变量会得到undef值。 at符号@可以一次指定整个数组。这样@rocks代表“所有的rocks”。@rocks = qw / a b c/; @copy = @quarry 从一个数组拷贝一个列表到另一个数组 pop and push操作符 正真的perl程序员不使用索引访问数组,这样发挥不了perl的强项。我们经常把数组当信 息栈用。总是从列表的右侧加入新值或删除。 pop操作取出一个数组的最后一个元素 @array = 5..9; $fred = pop(@array); $fred得到9,@array现在有(5,6,7,8) $barney = pop(@array); $barney得到8,@array现在有(5,6,7) 如果数组是空的,pop就不管它,因为没有元素可删除,只返回undef。 push操作与pop对应,它顺数组的最后添加一个元素或一个元素列表。 push(@array,0); @array现在有(5,6,7,0) @others = qw /1 2 3/; push @array,@others @array现在有(5,6,7,0,1,2,3) 注意:push的第一个参数或pop的唯一参数必须是一个数组变量,进栈和出栈对直接量列表 来说是没有意义的。 shift and unshift 操作 类似于push and pop,shift and unshift对数组的头部进行相应的操作。 在字符串中替换数组 与标量一样,数组的值也可以被替换到双引号字符串中。 print "quartz @rocks limestone\n"; 打印所有岩石,用空格分开。 注意:不要把电子邮件地址放到双引号字符串中。 foreach控制结构 foreach循环遍历列表中的所有值,对每个值执行一个迭代(执行一次循环体) foreach $rocks ( qw /a b c/) { print "one rock is $rocks.\n"; 打印a b c。 } Perl最喜欢的缺省变量:$_ 如果你在foreach循环的开始忽略了控制变量,perl就会使用$_。 foreach (1..10) { 缺省使用$_ print "I can count to $_!\n"; } $_ = "a"; print; 缺省打印$_ reverse操作符 reverse 取一个列表的值,然后返回相反顺序的列表。记住它只返回反序列表,并不影响它 的参数,如果返回值不被赋给别的变量,它是不保存。 sort操作符 sort取一个列表的值,然后按照内部字符序进行排序。 标量和列表上下文 一个给定的表达式在不同的地方,可能会有不同的含义。 5 + something something必须是个标量 sort something something必须是个列表 在标量上下文中使用列表生成表达式 提供标量上下文的表达式: $fred = something; $fred[3] = something; 123 + something something +654; if (something) {...} while (something) {...} $fred[something] = something; 提供列表上下文的表达式: @fred = something; ($fred,$barney) = something; ($fred) = something; push @fred,something; foreach $fred (something) {...} sort something; reverse something; print something; 在列表上下文中使用标量生成表达式 如果一个表达式在正常情况下不生成一个列表值,那么自然它就会生成标量值,即一个元素 列表: @fred = 6*7 得到一个单元素列表(42) 注意,因为undef是个村标量值,所以给一个数组赋undef并不能清空数组,清空的更好方 法是赋一个空列表()。 强制使用标量上下文 可以使用scalar假函数,它告诉perl提供一个标量上下文。 @rocks = qw /a b c d/; print "i have",@rocks,"rocks!\n"; 错,打印了石头的名字 print "i have",scalar @rocks,"rocks!\n"; 正确,打印石头的数量 列表上下文中的<STDIN> chomp (@lines = <STDIN>); 读入那些行,不带换行符 第四章 子例程 系统与用户函数 perl的子例程可以让我们在一个程序中重复利用一块代码,子例程名字是在前面加一个可有 可无的&符号,有一条规则规定什么时候可以省略。 定义一个子例程 使用关键字sub和子例程名定义。可以放在程序的任何位置。 sub marine { $n += 1; print "hello ,sailor number $n!\n"; } 注意:n 为全局变量 调用子例程 $marine; 输出hello,sailor number 1! $marine; 输出hello,sailor number 2! 返回值 每个子例程在运行过程中,计算值是它一系列动作的一部份。在子例程中得到的最后一个计 算值自动成为返回值。因此注意在向子例程增加代码时要确保最后一个表达式的结果是你希 望的返回值。“最后一个表达式”是指真正的、被最后计算的表达式,而不是正文的最后一 行。 参数 参数列表在子例程运行期间被自动地赋给一个特别的数组变量@_。子例程可以访问这个变 量以确定参数个数和参数的值。也就是说,第一个参数被存在@_[0],第二个被存在@_[1]中, 其它依次类推。@_变量是子例程的局部变量,如果@_中有一个全局变量,它就会在子例程 调用前被保存起来,而在子例程返回时恢复原值。 子例程中的私有变量 my操作符生成被称为词法变量(lexical variable)的私有变量。 local操作符 local的真正功能是把给定的变量的一个拷贝保存在一个秘密的地方(栈)。这个值在保存后 不能被访问、修改、删除,读出、检查、打印等。在perl中没有办法以接近被保存的值。 接着local把该变量设为空值(对标量是undef,对数组是空表),或设为任何赋给它的值。 当perl从子例程中返回时,会自动将变量恢复为原先的值。从效果上来看,这个变量被暂 时借用了片刻。 local 和 my 的区别 local是全局变量,可以想成“save”(在子例中调用时),在所有新代码中只用my,my比 local快。 可变长参数列表 在perl中,经常传递给子例程任意长度的参数列表。子例程可以查看@_数组,从而轻松地 判断参数的个数。但在实际的perl编程中,这类检查几乎没有用过,最好是让子例程适应 参数。 一个允许任意参数的例程 $maximum = &max(3,5,10,4,6); sub max { my ($max_so_far) = shift @_; foreach (@_) { if ( $_ > $max_so_far) { $max_so_far = $_; } } $max_so_far; } 这段代码使用了被称作“高水位线(high-water mark)的算法。在一次洪水之后,当水最后 一次涨潮和退潮时,高水位线显示了曾经达到的最高水位。 空的参数列表 返回一个undef。 词法(my)变量的说明 my可以在任何块中使用,而不仅仅在子例程中。例如可以在if,while or foreach中。 use strict pragma perl是一种特别宽松的语言,但也许你想让perl把规则加强一些,这可以用user strict pragma (编译指示)来安排。pragma中给编译器的提示,告诉它关代码的一些信息,这里,use strict pragma是告诉perl编译器,它应该在本块或源文件的其余部份强制使用一些好的程序设计 规则。 return操作符 该操作符立即从子例程中返回一个值。 省略与字符(&) 原则在除与内置函数名一致,其它子例程都可以省略与字符。 第五章 散列 什么是散列? 散列(hash)是一个数据结构,与数组相同的是它可以含有任意数目的值并随意读取它们。 但与数组中由数字对值进行索引不同,散列用名字(name)查找值。也就是说,索引不再 是数字,而是任意的惟一字符串,称之为键(key)。它是一桶数据,不存在第一项,它是一 团糟的,随意的,没有固定的顺序。键总是被转成字符串,如用数值50/20做键,它就会被 变成“2.5”。 散列可以任意大小,从空散列直以填满内存。 在perl中巨大的散列并不可怕,从三个和三百万个键值对中取出一个数的速度差不了多少。 为什么要用散列 你有一组数据与另外一组数据相关。 如 名 姓 主机名 ip地址 ip地址 主机名 单词 单词出现的次数 用户名 用户使用的硬盘块数 驾照号码 名字 如果你的任务描述中包含“找到重复项”,“唯一的”,“交叉引用”,或“查表”之类的词语, 那么散列就会在实现中很有用处。 散列元素访问 使用如下语法: $hash{$some_key} $a{"home"} = "first"; $a("hotel"} = "second"; 当你在一个已存在的散列中存入东西时,会覆盖以前的值。 访问散列之外的元素会得到undef: $a = $b{"test"}; 这里没有test,得到undef。 作为一个整体的散列 要访问整个散列,就使用%号做前缀。 为方便起见,可以将散列转换为一个列表,并转换回来,给散列赋值是一个列表上下文,这 个表由键-值对组成 %hash = ("aa",33,"bay",11,2.5,"hello","cc","bb\n"); 展开散列(unwind),把散列转换回键-值对应列表。次序乱了,但键-值还是成对出现的。 @array = %hash print "@array"; bay,11,2.5,hello,cc,bb(一个换行符),aa,33 散列赋值 %new_hash = %old_hash perl将%old_hash展开成一个键-值对列表,并赋值给 %new_hash。 %inverse_hash = reverse %any_hash 生成逆散列,键变值,值变键。前提是原散列值要唯一。 大箭头 用大箭头把散列中的键-值对组织起来。 my %hash = ( "aa" => "test1", "bb" => "test2", "cc" => "test3", 最后额外的逗号是无害的 ); 散列函数 keys函数得到一个散列中所有当前键构成的一个列表,values函数得到一个相应的值。 my %hash = ("a" => "test1,"b" => "test2","c" =>"test3"); my @k = keys %hash 得到"a","b","c"。 my @v = values %hash 得到"test1","test2","test3"。 在一人标量上下文中,这些函数给出散列的元素个数。 my $count = keys %hash 得到3,即有三个键-值对。 each函数 该函数可以遍历一个完整的散列。每次返回一个键-值对作为一个二元元素列表。最后返回 一个空列表。 while ( ($key,$value) = each %hash ) { print "$key => $value\n"; } exists函数 查看某键是否在散列中。存在就返回真,不存在就返回假。 delete函数 从散列中删除指定的键(和相应的值)。如无此键,它的任务就结束。此时没有警告或出错 信息给出来。 第六章 I/O基础 从标准输入进行输入 while (defined($line = <STDIN>)) { print "I saw $line"; }; 因为行输入操作符在你到达文件末尾时会返回undef,所以可以用它方便地跳出循环。 从钻石操作符进行输入 “<>”是一种特殊的行输入操作符,它可以是也可以不是来自键盘的输入。 while (defined($line = <>)) { chomp($line); print "It was $line that I saw!\n"; }; 如果用a,b,c三个参数调用该程序,将打印三个文件的内容。使用钻石操作符,就好像输入 文件被合并到一个大文件中。上面程序可用快捷方式写成: while (<>) { chomp; print "It was $_ that I saw!\n"; }; 大多数linux标准工具中,短横-代表标准输入流。 通常在一个程序中只用一个钻石操作符,当初学者在程序中放第二个钻石时,其实他们一般 是想用$_。记住,钻石操作符读取输入,但输入本身是在$_中。 调用参数 钻石操作符并不是直接从字面上读取调用参数,它实际上读取@ARGV数组。它被perl解 释器预设为调用参数的列表。在程序中可以对该数组进行赋值等操作。 @ARGV = qw# a b c #; 强制读取这三个文件 while (<>) { chomp; print "It was $_ that I saw!\n"; }; 向标准输出进行输出 print @array; aabbcc print "@array"; aa bb cc print <>; cat 的源代码 print sort <>; sort 的源代码 用printf进行格式化输出,和c类似。 数组与printf 可动态形成格式字符串。 my @items = qw ( a b c ); my $format = "the items are:\n".("%10s\n" x @items); 在标量上下文中使用@items得到它的长 度 printf $format,@items 在列表上下文中使用@items得到它的内容 上下文太重要了。要好好感受。 第七章 正则表达式的概念 正则表达式(regular expression),在perl中经常被称为模式(pattern),是与一个给定字符 串匹配或不匹配的模版。 不要把正则表达式和被称为glod的shell的文件名匹配模式混淆。比如*.pm匹配以.pm结尾 的文件名。 使用简单的模式 要比较一个模式和$_的内容,只需把模式放在一对斜杠之间,如下: $_ = "aabbkdkdk"; if ( /aabb/ ) { print "it matched!\n"; }; 关于元字符 在正则表达式中有一组具有特殊意义的字符,叫元字符,如:.号匹配任意单个字符(但不 匹配换行符),加反斜杠会使它不再特殊。一对反斜杠配置一个真正的反斜杠。 简单的数量符 在模式中重复一些东西。*号匹配前面的条目0次或多次。如:/foo\t*test/匹配在foo和test 间任意数目的制表符。 .* 匹配任意字符、任意次数。 + 匹配前面的条目一次或多次。 ? 匹配前面的条目是可选的,只能发生一次或0次(即没有)。 模式中的分组 可以用()括号分组,所以小括号也是元字符。如: /abc+/ 匹配abccccccccccccccccc /(abc)+/ 匹配abcabcabcabcabc /(abc)*/ 匹配任意字符串,甚至是空串。 选择 竖线 | 表示要么是左侧匹配,要么是右侧匹配。此时读做“或”。 /aa|bb|cc|/ 匹配一个含有aa或bb或cc的字符串。 /aa( |\t)+bb/ 匹配aa和bb中间被空格、制表符或两者的混合串分隔 /aa( +|\t+)bb/ 匹配aa和bb中间必须全是空格,或全是制表符 /aa(and|or)bb/ 匹配aa and bb 和aa or bb 一个模式测试程序 下面这个程序有助于在一些字符串上测试一个模式,看看它匹配了什么,在哪里匹配的。 #!/usr/bin/perl while (<>) { chomp; if (/your_pattern_goes_here/) { print "Matched : |$`<$&>$'|\n"; } else { print "No match.\n"; } }; 第八章 正则表达式提高 字符类 字符类(character class)即在一对中括号中列出的所有字符,可以匹配类中的任何单个字符。 例如:[abcdefg]可以匹配这七个字符中的任何一个。可用“-”指定一个范围。如[a-h],[1-9] 等。[\001-\177]匹配任何7比特ASCII码。中括号中的“^”号是取反的意思,如[^abc]匹配 除abc外的任何单个字符。 字符类快捷方式 有些字符类的使用特别频繁,所以就有了快捷方式。如:所有数字的字符类[0-9]可以缩写成 \d,[A-Za-z0-9_]缩写成\w。\s匹配空白,它和[\f\t\n\r ]等同,即等同一个含五种空白字符的 字符类,它们是换页符,制表符,换行符,回车符和空格字符自已。\s只匹配类中的一个字 符,所以一般用\s*匹配任意数量的空白(包括没有空白),或用\s+匹配一个或多个空白字符。 以上快捷方式的反置写法是用大写形式表示,\D,\W,\S。 /[\dA-Fa-f]/匹配十六进制数字。 /[\d\D]/匹配任何数字或任何非数字,也就是任何字符,包括换行符。“.”匹配除换行符外的 所有字符。 /[^\d\D]/表示什么都不匹配。 通用数量符 前面我们见过三个数量符*,+,?。但如果这三个不能满足你的需要,也可以用大括号{}中的一 对由逗号隔开的数字来指定重复的最少和最多次数。如/a{5,15}/匹配重复5次到15次的字 母a。如果省略第二个数(但包含逗号),那么匹配的次数就没有上限。如/a{3,}/就匹配一行 中连续出现的3个或多个a,它没有上限。如果连逗号也没有了,那么给出的数字就是一个 准确的数字。如/a{3}/匹配3个a。 * 等价 {0,} + 等价 {1,} ? 等价 {0,1} 锚位符 锚位符(anchor)可以用来为模式指定字符串的特定位置。“^”标志字符串的开头,“$”标 志字符串的结尾。如/^a/匹配处于字符头的abc,不能匹配dda,/b$/匹配处于字符尾的aab, 不能匹配abc。 /^s*$/匹配一个空行 /^abc$/匹配abc,又匹配abc\n 单词锚位符 \b可以匹配一个单词的两端,可以用/\babc\b/来匹配单词abc。 可以用一个单词锚位符,如,/\bth/可以匹配this,these,但不匹配method。/er\b/匹配 hander,woner,但不匹配gerenic,lery. 非单词边界锚位符是\B,它匹配任何\b不匹配的地方。如/\bsearch\B/会匹配searches,searching and searched.但不能匹配search or researching。 记忆的小括号 ()可以用来把模式的一些部份组合起来,它还有第二个功能,它们要求正则表达式引擎记住 与小括号中的模式匹配的那部份子串。 反向引用 反向引用(backreference)就是回头引用在当前模式处理过程中保存的记忆。用一个么斜杠 来构成,如\1包含第一个正则表达式记忆。即被第一对小括号匹配的字符串部份。 反向引用被用来匹配与模式在前面匹配的字符串完全一样的字符串。所以/(.)\1/匹配任意单 个字符,在记忆1中记住它,然后再与记忆1匹配。换句话说,它匹配任意字符,后跟同一 个字符。这样,这个模式会匹配双字母式的字符串。如bamm-bamm和betty。它和/../不一 样,/../匹配任意字符,后跟任意字符,它们两个可以一样,也可以不一样。 记忆变量 正则表达式记忆的内容在模式匹配结束后仍可通过特殊变量$1得到。 优先级 分四个级别 1、最上面的是小括号。 2、是数量符,*,+,?,{1,2},{1,},{1} 3、是锚位符和序列,^,$,\b,\B。 4、是坚线 | 。 优先级例子 /^aaa|bbb$/可能不程序员的意思,它只匹配字符串aaa的开头或字符串bbb的未尾。程序员 更可能想要的是 /^(aaa|bbb)$/,它匹配一行中没有其它东西,除了aaa或bbb以外。 第九章 使用正则表达式 使用m//进行匹配 一对斜杠实际上是m//(模式匹配)操作符的一个快捷方式。如我们在qw//中所中,你可以选 择任何定界符对把内容括住。如m<aaa>,m(aaa),m{aaa},m[aaa],m!aaa!等。如果选择了反斜 杠,就可以省略m。 选项修饰符 用/i进行不区分大小写的匹配。 用/s进行任何字符的匹配,包括换行符。它把模式中的每个点变成和字符类[\d\D]一样,匹 配任何字符,包括换行符。 可组合使用修饰符/is,顺序并不重要。 绑定操作符=~ my $some_other = "I dream of betty rubble"; if ($some_other =~ /\brub/) { print "Aye,there's the rub.\n"; } 看起来像个赋值语句,但它不是的。它是说“这个模式缺省时匹配$_中的东西---但现在让它 匹配左侧的字符串”。如果没有绑定操作符,表达式就缺省使用$_。 匹配变量 可以用$1,$2,$3,$4引用正则表达式记忆的第一到第四个记忆。匹配变量是正则表达式强大功 能的一个重要部份,它能让我们取出一个字符串的一部份。 $_ = "hello there,neighbor"; if (/\s(w+),/) { 记住空格和逗号之间的单词 print "the word was $1\n."; $1 就是 there } 匹配变量可以是空串。 记忆的持久性 匹配变量一般保留到下一次模式匹配成功。也就是除非匹配成功,否则你不应该使用这些匹 配变量。 自动匹配变量 $& 实际与模式匹配的那部份字符串就保存在这里。 if ("hello there, neighbor" =~ /\s(w+),/) { print "that actually matched '$&'.\n"; } 整个匹配部份是" there,"(一个空格,there,一个逗号),$1中是there,而$&中是整个匹配部 份。 匹配部份之前的东西被存在$`,之后的东西被存在$'。也就是说,$`含有正则表达式引擎在 找到匹配之前需跳过的部份,而$'则含有模式没有到达的字符串的剩余部份。如果把这三个 字符串按顺序连在一起,那么你总会得到原字符串。第七章的模式测试程序就是使用了这三 个神秘代码。print "match:|$`<$&>$'|\n" 使用自动匹配变量的代价是,会使用其它正则表达式的运行会变得慢一些。所以很多perl 程序员都尽量避免使用这些自动匹配变量。相反,他们会采用一些方法,例如,如果你只需 要$&,那就在整个模式的周围加一对括号,然后使用$1。 用s///进行查换并替换 $_ = "he's out bowling with barney tonight."; s/barney/killer; 用killer替换barney,如果匹配失败,什么也不会发生。 print "$_\n"; s///有一个返回值,替换成功,则为真,否则为假。 用/g进行全局替换 s///只替换一处,/g修饰符告诉s///进行所有可能的无交迭替换。 全局替换的一个相当常见的使用是压缩空白,把任意数量的空白变成一个空格。 s/\s+/ /g; 压缩空白 s/^\s+//; 把前面的空白删除 s/\s+$//; 把结尾的空白删除 s///也可用不同的定界符,如#,!号等,但如果使用成对的字符,因为它有左右之分,所以 必须用两对,一结放模式,一对放替换串,如s[aaa][bbb],s(aaa)(bbb),甚至也可以用s<aaa>(bbb) 这样不成对的定界符。 s///也和m//一样,有/i,/s修饰符和=~绑定操作符。 $file_name =~ s/^.*///s; 在$file_name中,去掉所有unix风格的路径。 大小写转换 \U 强制后面的字符都用大写 s/(aaa|bbb)/\U$1/gi AAA BBB \L 强制后面的字符都用小写 s/(aaa|BBB)/\L$1/gi aaa bbb 用\E关闭,当写成小写形式时,\u,\l就只影响下一个字符。 \u\L或\L\u 代表首字符大写,与顺序无关。 split操作符 它把一个字符串按照分割子(separator)分开。 @fields = split /:/,"abc:def::a:b"; 得到("abc","def","","a","b")。 @fields = split /:/,":::a:b:c:::"; 得到("","","","a","b","c"),结尾空字段被丢弃。 在空白处分割也是常见的,使用/\s+/模式。 split的缺省行为是在空白处分割$_。如 my @fields = split; 等同于split /\s+/, $_; join函数 在某种意义上,join完成split的相反过程。它把一组片断粘合起来形成一个字符串。 my $a = join ":",1,2,3,4,5; 则$a 是"1:2:3:4:5" join可以和split配合使用,把一个字符串分割后用不同的定界符恢复它。如可以把1,2,3 变 成 1-2-3. 第十章 更多的控制结构 unless控制结构 if是表达式为真时执行,如果希望表达式为假时执行可用unless(除非)。表示除非表达式 为真,否则运行这个代码。它就像一个具有相反条件的if语句,也可以理解成一个独立的 else子句。 unless ($aa = ~/^[A-Z_]\w*$/i) { print "the value of \$aa doesn't look like a perl identifier name.\n"; } 等同于 if ($aa =~ /^[A-Z_]\w*$/i) { } else { print "the value of \$aa doesn't look like a perl identifier name.\n"; } 等同于 if (!$aa =~ /^[A-Z_]\w*$/i) { print "the value of \$aa doesn't look like a perl identifier name.\n"; } 以上语句都被编译成相同的内部字节码,但unless最自然。 unless的else子句 unless ($aa =~/^(bb)/) { print "this value like bb.\n"; } else { print "do you see what's going on here?\n"; } 等同于 if ($aa =~/^(bb)/) { print "do you see what's going on here?\n"; } else { print "this value like bb.\n"; } until控制结构 while的反置结构。 until ($a > $b) { $a *= 2; } 这个循环一直执行,直到条件表达式返回真为止。 表达式修饰符 print "$n is a negative number.\n" if $n < 0; 等同于 if ($n < 0) { print "$n is a negative number.\n"; } 前一种写法更紧凑。读起来很像自然英语。还有: print " ",($n +=2) while $n < 10; $i *= 2 until $i >$j; &greet($_) foreach @person; 裸块控制块 所谓的“裸(naked)块”,指的是没有关键字或条件的块。如: while (condition) { body; body; body; } 现在把while关键字和条件去掉,就得到一个裸块。 { body; body; body; } 它只执行一次,然后就结束, 其中一个作用是提供一个临时词法变量的作用域。一条通用的原则,所有变量就应该在最小 的作用域中声明。如果你需要一个只用在几行代码中的变量,那么你可以把这些行放在裸块 中,并在该块中声明这个变量。 elsif子句 如果你需要检查一组条件,一个接一个,看看哪个条件为真,就可以用elsif子句(注意不 是elseif)。perl会逐个测试条件表达式,当一个条件成功就执行相应的代码。但如果测试项 太多,就不要使用这种方式,应该用类“case or switch”的语句。 自递增和自递减 ++ $a++; $a值不变。 ++$a; 把$a增1,存到$a里。 -- $a--; $a值不变。 --$a; 把$a减1,存在$a里。 for控制结构 for ($i =1 ;$i <=10;$i++) { print "I can count to $i!.\n"; } for ($_ = "aaabbbccc";s/(.)//; ) { 当s///成功时执行循环。 print " one character is :$1\n." } 每次迭代时都会去掉一个字母。当字符串为空时,替换失败,循环结束。 以下用for实现的无限循环 for (;;) { print "this is an infinite loop.\n"; } 以下为用while实现的无限循环,一种更具perl特色的写法。 while (1) { print "this is an infinite loop.\n"; } foreach和for之间的秘密联系 在perl内部,foreach 和 for 完全等价。 for (1..100) { 实现是一个从1到100的foreach循环 print "I can count to $_.\n"; } 在perl中,foreach总是被写成for,因为可节省4个字符的输入,因为懒惰是perl程序中的 经典品质。 循环控制 last操作符 立即终止一个循环的执行(与c中的break相似)。作用于当前运行的最内层 循环块。 next操作符 控制从循环的下一个迭代继续(与c中的continue相似) redo操作符 回到当前循环的开头,但不测试条件表达式,进入下一次迭代。 next 和 redo 最大的区别在于next会进入到下一次迭代,而redo则重新执行当前的迭代。 带标签的块 很少使用,也就是命令一个循环,以便从内层循环中直接跳出。 LINK: while (<>) { foreach (split) { last LINK if /__END__/; 跳出LINE循环。 ...; } } 逻辑操作符 && 相当于and || 相当于or 它们被称为短路操作符。 三元操作符 ? 和c的一样,它就像一个if-then-else测试。 expression ? if_true_expr : if_false_expr 一个利用三元操作符写的多路分支程序 my $size = ($width < 10) ? "small" : ($width < 20) ? "medium" : ($width < 50) ? "large" : "extra-large"; 缺省值。 使用部份计算操作符的控制结构 &&,||,?:都有一个共有的属性,依赖于左侧值的真假,它们可能计算可不计算一个表达式, 因此叫部份计算 (partial-evaluation)操作符。因此它天生就是一种控制结构。 第十章 文件句柄和文件测试 什么是文件句柄? 文件句柄(filehandle)是Perl程序中的一个名字,表示你的Perl进程与外面世界的i/o连接。 它是一个连接的名字,并不是一个文件的名字。 文件句柄的命名方式与其它perl标识符一样,建议用大写字母。 Perl为了自已使用,已经有六个特殊的文件句柄名:STDIN,STDOUT,STDERR,DATA,ARGV AND ARGVOUT。 打开一个文件句柄 open CONFIG,"test"; 打开test文件,它所包括的东西通过名为CONFIG的文件句柄 为我们的程序所使用。 open CONFIG,"<test"; 打开test文件,显式说明这个文件名用于输入。 open CONFIG,">test"; 打开test文件,显式说明这个文件名用于输出。为了输出打开文 件句柄CONFIG到新文件test。 open CONFIG,">>logtest"; 打开logtest文件,用于附加。如果文件不存在则生成它。 关闭一个文件句柄 close CONFIG; 退出程序时文件句柄会自动关闭,但建议最好在完成一个文件句柄的使用后不久就关闭它。 坏文件句柄 系统中会存在坏文件句柄,如果你试图向一个坏文件句柄写入,那么数据会被无声地丢弃。 在编写脚本时用perl -w会打开警告显示。 用die表明致命错误 unless (open LOG,">>logtest") { die "Cannot create logtest:$!"; } 或者用另外一种更好的写法 open LOG, ">>logtest" or die "Cannot create logtest:$!"; 使用or操作符。如果open成功,返 回真,or结束,如果open失败,返回假,or会继进行到右侧代码。伴随一条消息死去。你 可以用语言去读它,“打开这个文件,或死去”。 $!是系统给出的出错提示信息,如果die表明的错误并非来自一个系统请示失败,请不要包 含$!。 die "Not enouht arguments.\n" if @ARGV < 2; 命令参数不够两个时,程序退出。 使用warn发出警告信息 与die类似,但它不退出程序。 使用文件句柄 一旦打开一个文件句柄,你就可以读入行。像使用STDIN从标准输入读取一样。例如从unix 的passwd文件中读取行: open PASSWD, "/etc/passwd" or die ”How did yo get loged in?($!)"; 一个为写入或附加打开的文件句柄可以和print or printf一起使用,紧跟在其后但在参数列表 之前: print LOG "filehandle test.\n" 输出到LOG 改变缺省的输出文件句柄 缺省情况下,如果没有给print指定一个文件句柄,输出就会发送到STDOUT,但这个行为 可以用select操作符改变。 select LOG; print "this message send to LOG.\n"; 一旦选择一个文件句柄作为缺省的输出,它会一直保留,这样会把后面的程序搞糊涂,所以 要在完成后及时设回STDOUT。 select STDOUT; 重新打开一个标准文件句柄 如果三个系统句柄(STDIN,STDOUT,STDERR)的任何一个不能打开,Perl会友好地恢复原来 的那个,也就是说perl只有在看到新的连接打开成功时帮把原来的关掉。 文件测试 在perl中有一组完整的测试,你可以用来了解文件的信息。 -e 测试文件是否存在 die "ooo!my gods,a file called "$file"already exists.\n" if -e $file; -M 检查一个文件是否最新 warn "config file is looking pretty old!\n" if -M CONFIG > 28; 文件测试和它们的含义 -r 文件或目录对该(有效)用户或组可读 -w 文件或目录对该(有效)用户或组可写 -x 文件或目录对该(有效)用户或组可执行 -o 文件或目录被该(有效)用户或组所有 -R 文件或目录对该实际用户或组可读 -W 文件或目录对该实际用户或组可写 -X 文件或目录对该实际用户或组可执行 -O 文件或目录被该实际用户或组所有 -e 文件或目录名存在 -z 文件存在,大小为零,对目录总为假 -s 文件或目录存在,大小非零,单位为字节 -f 条目是个普通文件 -d 条目的个目录 -l 条目是个符号链接 -S 条目是个套接字 -p 条目是个命名管道(一个fifo) -b 条目是个块特殊(block-special)文件(如一个可装载磁盘) -c 条目是个字符特殊(character-special)文件(如一个i/o设备) -u 文件或目录是setuid -g 文件或目录是setgid -k 文件或目录的粘着位sticky bit被设置 -t 文件句柄是个TTY(可以由isatty()函数返回,文件名不能由本测试来测试) -T 文件像个“文本”文件 -B 文件像个“二进制”文件 -M 更改年龄(单位为天) -A 访问年龄(单位为天) -C Inode更改年龄(单位为天) stat和lstat函数 stat返回unix的stat系统调用返回的所有信息。它的操作数是一个文件句柄或是一个文件名。 返回值可能是一个空列表,表示stat失败(通常是文件不存在),或者是一个13个元素的数 字列表。可用以下标量变量列表描述出来。 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); $dev 文件设置号 $ino 文件inode号 $mode 文件权限位和一些特别位 $nlink 指向文件或目录的链接数 $uid 文件的用户id $gid 文件的组id $size 文件大小(以字节为单位) $atime $mtime $ctime 访问,修改,改变时间 $blksize 块大小 $blocks 块数 对符号链接使用stat将返回该链接所指的东西的信息,而不是符号链接本身,除非这个链接 碰巧没有指向任何目前可以访问的东西。如果你需要(基本上没用)符号链接本身的信息, 就使用lstat。如果操作数不是一个符号链接,lstat则返回与stat一样的东西。 localtime函数 把电脑时间转换成人可以看得明白的日期时间。 my $timestamp = 19809999393 my $date = localtime $timestamp 在列表上下文中,localtime返回一个数字列表。 my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp $mon 表示月份,从0-11。 $year 表示自1900年起的年数,要加上1900才是真正的年数。 按位操作符 & 按位与----得到哪些位在两个操作数中同时为真 | 按位或----得到哪些位在两个操作数中至少有一个为真 ^ 按位异或----得到哪些位在两个操作数中只有一上为真 << 按位左移----把左操作数的位移动由右操作数给定的次数,在低位补上0 >> 按位右移----把左操作数的位移动由右操作数给定的次数,低位会被丢弃 ~ 按位取反,也被称为一元位补----返回与操作数的每位相反的数 使用位串 如果一个按位操作符的任何操作数是一个字符串,perl就会进行位串操作。也就是说"\xAA" | "\x55" 会得到字符串"\xFF"。 使用特殊的下划线文件句柄 _ 特殊的文件句柄,使perl用上次文件测试、stat,lstat函数操作后留在内存中的信息。 第十二章 目录操作 改变目录树 chdir操作符可以改变工作目录,就像cd命令一样。 chdir "/etc" or dir "cannot cddir to /etc:$!"; glob shell通常会把每个命令中的文件名模式扩展为匹配的文件名,这就称为glod。 如ls *.txt perl中的类似的glob操作符。 my @all_files = glob "*"; 得到当前目录中的所有文件,不包含以句点开头的文件。 my @pm_files = glob ".pm"; 得到以.pm结尾的文件。 glob的另一种作法 在一些老程序中用<>代替glob操作符 my @all_files = <*>; 目录句柄 目录句柄(directory handle)和文件句柄在外表和操作上都很像,可以打开它(用opendir), 读取它(用readdir),关闭它(用closedir)。它读出的是目录的内容。 my $dir_to_process = "/etc"; opendir DH,$dir_to_process or die "cannot open $dir:$!"; foreach $file (readdir DH) { print "one file in $dir is $file\n"; } closedir DH; 与文件句柄类似,目录句柄会在程序结束时或该目录句柄在其它目录上重新打开时被自动关 闭。 如果我们只想要那些以pm结尾的文件,可以在循环中使用一个跳过函数 while ($name =readdir DIR) { next unless $name =~ /\.pm$/; ..... } 如果要非点文件可以用:next if $name =~ /^\./; 如果想要除. 和 ..之外的文件可用: next if $name eq "." or $name eq ".."; readdir操作符返回的文件没有路径名部份,仅仅是目录内的文件名。如passwd,而不是 /etc/passwd 名字补丁,加上路径名。 opendir SOMEDIR, $dirname or die "cannot open $dirname:$!"; while (my $name = readdir SOMEDIR) { next if $name =~ /^\./; 跳过点文件 $name = "$dirname/$name"; 补上路径名 next unless -f $name and -r $name 只要可读文件 ..... } 递归的目录列表 递归地访问目录可用File::Find库。以进行简洁的递归目录处理。不用自已写代码。 第十三章 处理文件和目录 删除文件 在perl中用unlink操作符删除文件,同shell的rm命令一样。 unlink "aa","bb","cc"; 把这三个文件删除。 与glob函数结合起来可以一次删除多个文件 unlink glob "*.o"; 删除当前目录下以.o结尾的文件,与rm *.o相似。 unlink的返回值告诉我们有多少文件被成功删除。 my $successful = unlink "aa","bb","cc"; print "I delete $successful file(s) just now.\n"; 如果想知道那个文件被删除,可用循环,一次删除一个文件。 foreach my $file (qw/aa,bb,cc/) { unlink $file or warn "failed on $file:$!"; } 一个很少人知道的有关unix的事实。如果你有一个文件,你对它不能读,不能写,不能执 行,甚至文件可能并不属于你,但你仍然可以删除它。这是因为unlink一个文件的权限不 依赖于文件本身的权限位,起作用的其实是包含这个文件的目录的权限位。只要目录是可写 的,就可以删除该目录中不属于自已的文件。在unix中可以通过设置sticky bit解这个问题, 以保护可写目录。 重命名文件 rename "old","new"; 类似于mv命令。rename失败时返回假,并在$!中设置操作系统的错误信息。因此可用or die 或or warn显示给用户。 一个把所有以.old结尾的东西rename为以.new结尾的perl程序。 foreach my $file (glob "*.old") { my $newfile = $file; $newfile =~ s/\.old$/.new/; 由于.new不是模式,所以点号不用加反斜杠。 if (-e $newfile) { warn "can't rename $file to $newfile:$newfile exists.\n"; } elsif ( rename $file, $newfile) { } else { warn "rename $file to $newfile failed:$!\n"; } } 链接和文件 每个文件都被存在一个编了号的inode中,每个inode都包含一个称为链接计数(link count) 的数字,当inode没有列在任何目录中时,链接计数总是0,也就是空,可以分配给文件。 当inode被加到一个目录中时,链接计数会递增;如果此列表项被删除,链接计数会递减。 目录包含.,也就是指向自已的inode,所以目录的链接计数应该总是至少为2。文件也可以 不止一个列表项,如链接文件。在perl中用link "aa","bb"建立一个指向aa的链接bb。类似 于在unix shell一执行"ln aa bb"。现在aa,bb都有相同的inode值,两个文件有相同的大小, 相同的内容。在aa中加入一行,也会在bb中加入一行。如果意外删除了aa,数据并不会丢 失,可以在bb中找回来。反之也一样。但如果两个文件都删除了,则数据就会丢失。 目录列表项中的链接规则 1、 一个给定的目录列表项中的inode号都指向同一个安装卷上的inode。这条规则保证,如 2、 果物理媒介被移到了另一台机器上,所有的目录仍和它们的文件呆在一起。这就是为什 3、 么可用rename把文件从一个目录移到另一个目录的原因,但两个目录必须在同一个文 4、 件系统(安装卷)中。 链接不能用于目录。 2、不能给目录起新的名字。因此目录不能用于链接。 以上讨论的是硬链接,还有一个符号链接,也叫软链接,能绕过这硬连接的限制。 symlink "aa","bb"; or warn "cannot symlink aa to bb:$!"; 这和unix shell 中的"ln -s aa bb" 类似。 要想知道符号链接指向哪里,可以使用readlin函数。如果不是符号链接,则返回undef。 两种链接都要以用unlink删除。 建立和删除目录 mkdir函数可以在一个已有的目录中建立一个目录。返回真时表示成功。 mkdir "aaa",0755 or warn "cannot make aaa directory:$!"; 第二个参数是新生成目录的权限位。以0开头,这个是一个八进制值。 oct函数强制对一个字符串按八进制解释,不论前面有没有0: 删除空目录,可用rmdir函数。 rmdir glob "aa/*"; 删除aa/下所有空目录。 rmdir操作符对非空目录操作会失败。所以要先用unlink删除文件,再删除目录。 修改权限 perl中有一个chmod函数,和unix shell中的chmod完成类似功能。 chmod 0755, "aa","bb"; perl中不接受符号权限表达式方式,如+x,go=u-w等。 改变所有者 chown函数可以改变一组文件的所有者和属组。 chown 1004,100,glob "*.o"; 可用getpwnam把用户名翻译成一个数字,用getgrnam函数把组名翻译成一个数字。 改变时间戳 utime函数可修改文件的访问时间和修改时间。 my $now = time; my $ago = $now -24*60*60; 每天的秒数 utime $now,$ago,glob "*"; 把访问时间设为现在,修改时间设为一天以前 第三个时间ctime的值在对文件做任何改变时,总被设为“现在”,因此没办法用utime函数 来设置它。因为在你设置完后它会立即被重置为“现在”,这是因为它的主要目的就是进行 增量备份:如果文件的ctime比备份磁带上的日期要新,就说明又需要备份了。 使用简单的模块 File::Basename模块 从文件名中抽取基名,取不包括路径的文件名。 通过use命令声明一个模块 use File::Basename; 这样,我们就有了一个basename函数。 my $name = "/usr/local/bin/perl"; my $basename = basename $name; 得到perl 该函数可用于多平台,如windows。 该模块中还有一个dirname函数,它把目录名从一个完整文件名中分离出来。 有选择地使用模块中的函数 当你不需要模块中的所有函数,或模块中的函数和你程序中子例程有冲突时,你可以在声明 模块时给模块一个引入列表,只包括需要的函数。 use File::Basename qw /basename/; 只要basename函数,不要其它函数。 use File::Basename qw //; 不要任何函数。 怎么会想要一个空列表呢?这是因为,有引入只是使得我们能使用短的简单的函数名, basename,dirname。即使不引入这些名字,我们仍可以使用,只是在没有引入时,我们要用 全名来调用它,如:File::Basename::dirname。 每个模块都有缺省的引入列表,查相关文档有介绍。 File::Spec模块 用来处理文件规范(file specification)。它是一个OO的模块。用小箭头而不是::来引用函数。 $newname = File::Spec->catfile($dirname,$basename); 第十四章 进程管理 通过perl直接启动其它程序。 system函数 system "date"; 启动unix系统的date命令。 子进程会运行date命令,它将继承perl的标准输入,标准输出和标准错误。 system 'ls -l $HOME'; 注意是用单引号,因为$HOME是shell变量,否则,shell就看不到 美元符号。表明要替换的符号。 system "long_time_command&"; 把长时间运行的程序放在后台。 system 'for i in *; do echo ==$1 ==; cat $i; done'; 可以写脚本 避免shell 调用system操作符时带多个参数,此时shell就不会卷入。如: system "tar","cvf",$aaa,@bbb; 第一个命令是tar,其余的参数会一个一个传递给它。 system的退出状态基于子进程的退出状态。在unix中0表示正常,非0表示出错。 unless (system "date") { 返回0表示成功 print "we gave you a date,ok!\n"; } exec函数 与system差不多,system会生成一个子进程,exec是让perl进程本身去处理所要求的动作。 一般用system就可以了。 环境变量 当你启动一个新进程时,环境变量就被继承下来了。在perl中,通过特殊的%ENV散列得 到环境变量,散列中每个键表示一个环境变量。在你的程序刚开始执行时,%ENV就从父 进程(通常是shell)继承而来。修改这个散列就改变了环境变量,它又会被新进程继承。 $ENV {'PATH'} = "/home/mydir/bin:$ENV{'PATH'}"; 设置新的环境变量,增加了一个 路径 delete $ENV{"IFS"}; 删除“IFS”这个路径 my $make_result = system "make"; 在新环境变量中执行程序 使用反引号捕获输出 当使用system and exec时,所启动命令的输出都被送到perl的标准输出上。有时我们需捕 获这些输出。 my $now = `date`; print "the time is now $now."; 已经有换行符,不用加\n。 与shell差不多。但它把行尾去掉,而perl的输出包含\n。所以要得到同样的效果,需加上 chomp操作。 在列表上下文中使用反引号 my $who_text = `who`; 标量上下文,得到一个长字符串。 my @who_lines = `who`; 列表上下文,得到一个按行分开的数据。 文件句柄形式进程 perl可以启动一个处理活动状态的子进程。启动一个并发子进程的语法是把命令当做“文件 名”用在open调用中,在命令之前或之后加一个竖线,这是一个“管道”字符,因些,这 通常被称为管道打开(piped open)。 open DATE, "date|" or die "cannot pipe from date:$!"; 竖线在右边,其标准输出与文件句柄DATE连接,就像shell中的date | your_program。 open MAIL, "|mail merlyn" or die "cannot pipe to mail:$!"; 竖线在左边,命令的标准输入文件句柄MAIL连接,就像shell中的your_program | mail。 命令启动后是个独立于perl的进程。 要读取一个为读而打开的文件句柄,我们只需进行普通的读: my $now = <DATE>; 要想给邮件进程发送数据,一人简单的“带文件句柄的打印”就可以了: print MAIL "the time is now $now."; 用fork进行深入和复杂的工作 用低级系统调用实现 system "date";命令。 defined (my $pid = fork ) or die "cannot fork:$!"; unless ($pid) { exec "date"; die "cannot exec date:$!"; } waitpid($pdi.0); 发送和接收信号 向4201发送一个SIGINT。 kill 2, 4201 or die "cannot signal 4201 with SIGINT:$!"; 你也可用“INT”替代这里的2,因为2号信号就是SIGINT。 信号0表示,看看我能不能发一个个信号,但我并不想现在发送。因此可用以进程探测。 unless (kill 0,$pid) { warn "$pid has gone away!"; } 第十五章 字符串与排序 用index寻找子字符串在大字符串中出现的位置。 $where = index($big,$small); 例子 my $where = index ("howdy world","wor") where 是 6 . index还有第三个参数,告诉index从后面某个指定的位置开始搜索,而不是从开头。 可用rindex函数找到子字符串最后出现的位置。 my $last_slash = rindex ("/etc/passwd","/"); 值是4 rindex也有可选的第三个参数,但此时给出的是允许的最大返回值。 用substr处理一个子字符串 substr操作符只作用于一个大字符串的一部分,它看起来如下: $part = substr($string,$initial_position,$length); 它取三个参数:字符串值、以零为基准的初始位置(与index的返回值类似)和子字符串的 长度。返回值是一个子字符串: my $mineral = substr ("hello world",6,5); 得到world my $rock = substr "hello world,6,10000"; 得到world,第三个参数可超过实现的字符串长度。 如果想确保到达字符串末尾,不论它多长或多短,则只须省略第三个参数。 始初位置可以是负值,意思是从字符串的末尾数起,即-1代表最后一个字符。 index and substr可很好地配合工作。如我们可以取出从字线l位置开始的一个子串: my $long = "a very very long string"; my $right = substr($long,index($long,"l")); 还可以使用绑定操作符(=~)以限制某个操作符只作用于字符串的一部份。 substr($string,-20) =~ s/aa/bb/g; 但在实现代码中不会需要这样的功能。 用substr and index能完成的工作多数也可以用正则表达式完成。不过substr and index一般 会快一些。 四个参数版本的substr,第四个参数就是做替换的子字符串。 my $previous_value = substr($string,0,5,"Goodbye"); 用sprintf格式化数据 sprintf 和 printf取一样的参数(除了可选的文件句柄之外),但它返回请求的字符串而不是 打印它。 my $date_tag = sprintf "%4d/%02d/%02d %2d:%02d",$yr,$mo,$da,$h,$m,$s; 在本例中,$date_tag得到的东西类似于"2004/01/01 3:00:00"。 使用sprintf处理“钱数” 显示2.50而不是2.5,可用“%.2f”格式完成。 my $money = sprintf "%.2f",2.499999"; 如果你的“钱数”的太大以至于需要逗号来显示它的大小,那么可以用以下例程实现。 sub money { my $number = sprintf "%.2f",shift @_; 每次通过空循环时加一个逗号 1 while $number =~ s/^(-?\d+)(\d\d\d)/$1,$2/; 在合适的地方加上美元符号 $number =~ s/^(-?)/$1\$/; $number; 第一行格式化第一个参数以获得在小数点后准确的两个数字。如果参数是数字12345678.9 那么我们的$number就是字符串"12345678.90"。 下一行使用一个while修饰符,表示只要替换返回真值(表示成功),循环体就被执行,但 循环体什么都不做。它可以有两种其它写法: while ($number =~ s/^(-?\d+)(\d\d\d)/$1,$2/) { 1; } 和 'keep looping' while $number =~ s/^(-?\d+)(\d\d\d)/$1,$2/; 这个替换做了什么呢?模式会匹配字符串的前面部份,不能匹配小数点后面的部份。记忆$1 会得到"12345",$2会得到"678",因此替换后会使得$number变成"12345,678.90"。如果替换 成功,则循环重新开始。这次,模式不能匹配逗号以后的部份,因此$number变成 "12,345,678.90"。这样,替换在每次通过空循环时添加一个逗号。接着再进入一次循环,但 这次模式不能匹配,所以循环就结束。在字符开头的一个负号作用是把美元符号放在正确的 位置。变样$number就是"$12,345,678.90"。 高级排序 内置的sort操作符按ASCII字母顺序排序。如果要对组数值,或大小写无关,或存储在散 列中的信息对一些条目进行排序。那就要告诉perl你想要什么样的顺序,方法就是写一个 排序定义子例程。如下一个数值排序子例程: sub by_number { if ($a < $b ) {-1} elsif ($a > $b) {1} else {0} } 如果$a应该$b之前,则返回-1,如果$b应该在$a之前,则返回1,如果$a 和$b的顺序无 关紧要,则返回0,如相等。 要使用排序子例程,只须把它的名字放在关键字sort操作符和要排序的列表之间就可以了。 my @result = sort by_number @some_number; 不需在子例程中声明$a $b,如果这样做了,子例程就无法工作。还有一种更简单的写法, 而且更有效率。采用<=>操用符。 sub by_number { $a <=> $b} cmp是比效字符串的操作符。 sub ascii {$a cmp $b) my @stings = sort ascii @any_string; 大小写无关的比较 sub case_insensitive {"\L$a" cmp "\L$b"} 用\L强制把参数变成小写 以“内联”的方式把排序子例程写进代码中: my @number = sort {$a <=> $b} @some_number; 如果按降序排序,可用reverse写成: my @number = reverse sort {$a <=> $b} @some_number; 也可以把参数互换达到反序的目的: my @number = sort {$b <=> $a} @some_number; 按值排序一个散列 my %score =("aa" => 195,"bb" => 201,"cc" => 40); my @winners = sort by_score keys %score; sub by_score { $score{$b} <=> $score{a} } 按照多个键排序 如果散列中有两个相同的值。那么可以按名字排序。 my @winners = sort by_score_and_name keys %score; sub by_score_name { $score{$b} <=> $score{a} 按数值分数排序 or 加一个低优先级的短路or操作符 $a cmp $b 按名字根据ASCII字母顺序排序 } 排序子例程不是只能使用两级排序,允许多级排序。如上例,多加几个or操作符就可以了。 第十六章 简单数据库 DBM文件和DBM散列 在每个有perl的系统都有一个已经可用的简单数据库,以DBM文件的形式存在。这可让你 的程序把数据存储在一个文件或一对文件中以便快速查询。当使用两个文件时,一个存放数 据,一个存放目录。 有些DBM的实现对文件中每个键和值的大小有一个1000字节的限制。但对文件中单个数 据项的数目没有限制,只要你有足够的硬盘空间。 打开和关闭DBM散列 要把一个DBM数据库和一个DBM散列关联起来,即打开数据库,可以使用dbmopen函数。 dbmopen (%DATA,"my_database",0644) or die "cannot create my_database:$!"; 第一个参数是散列的名字,如果这个散列已经有值了,那么在打开DBM文件后这些值都将 无法访问。 第二个参数是DBM数据库名,在硬盘上通常以一对扩展名为.dir and .pag的文件存储,但在 这里不需要打上扩展名。 第三个参数是权限值。被赋于打开的文件。 使用大写散列名只是个传统,和文件句柄一样。 DBM散列在程序运行的全过程中一直打开。当程序结束时,关联被终止。你也可以用 dbmclose关闭它 dbmclose (%DATA) 使用DBM散列 DBM散列与一般散列几乎一样工作。可以在散列中添加,删除,保存数据。只是并非存在 内存中,而是在硬盘上。 $DATA("aa") = "test"; 生成或更新一个元素 delete $DATA{"aa"}; 删除数据库中一个元素 while (my($key,$value) = each(%DATA)) { print "$key has value of $value\n"; } 访问一个由c程序维护的DBM文件,你就应该知道C程序通常会在字符的末尾加一个 NUL("\0")字符,原因是c使用NUL字节作为字符串尾标志。DBM库例程不需要这个NUL, 因此NUL会被当作数据的一部份被存储。如果要和C程序合作,就必须在你的键和值后面 加一个NUL字符,而把返回值末尾的NUL去掉从而使得数据变得有意义。例如在一个unix 系统上的sendmail别名数据库中搜索mymail。你可作以下操作: dbmopen(my %ALL,"/etc/mail/aliases",undef) or die "no aliases?"; my $value = $ALL{"mymail\0"}; 注意附加的NUL $value =~ s/\0$//; 删去结尾的NUL print "my mail is headed for "$value\n";显示结果 如果你DBM文件被多个进程并发访问,如通过WEB来更新,那么就需要一个附加的锁文 件。具体内容需查询相关资料。 在pack and unpack处理数据 pack函数取一个格式字符串和一组参数,然后把参数装配置到一起构成一个字符串,unpack 还原字符串。 my $buffer = pack ("c s l",31,1123,85858); 格式c,s,l代表char,short and logn。所以第一个数字装入一个字节,第二个数字装入两个字节, 第三个数字装入四个字节。格式字符可查询 perlfunc手册。 固定长度的随机访问数据库 固定长度不是说文件本身,而是指单个记录是固定长度的,就好像关系数据库中的定长字段。 假如有一组记录,用pack格式字符代表如下: 名字 40个字符 a40 年龄 单字节整数 C 分数 5个双字节整数 I5 日期 4字节整数 L 每个记录共55个字节。 perl支持使用此类磁盘文件。步骤如下: 1、为读和写打开一个磁盘文件。 用"+<"模式open一个文件,就对该文件有读写权限。“<”只是有读权限。“+>”生成一个 新文件,并对它有读写权限。 2、在这个文件中移动到任意位置。 seek函数能在文件中移动 seek (HEAD,55 * $n,0); 第一个参数是文件句柄。 第二个参数是距文件头的偏移量,以字节为单位。 第三个参数是0,是“从哪里开始”参数,如果你寻址到一个相对于当前位置的位置,或相 对于文件尾的位置,就可使用一个非0值,多数为0。 一旦文件指针用seek定了位,那么下次的输入输出操作将会从那个位置开始。 3、按长度取出数据,而不是直到下一个换行符。 使用read函数读取数据。 my $buf; 输入缓冲区变量 my $number_read = read(HEAD,$buf,55) 读出55个字节数据后,可用unpack拆装它们,以获得具体信息。 my ($name,$age,$score1,$score2,$score3,$score4,$score5,$when) = unpack"a40 C I5 L",$buf; 4、按固定长度写数据。 不是用write,而是用print。并用pack确保长度正确。以存入一个new_score和时间为例 print HEAD pack("a40 C I5 L",$name,$new_scroe,$score1,$score2,$score3,$score4,time); 可变长(文本)数据库 #!/usr/bin/perl -w use strict chomp(my $date = `date`); 一个更好的办法是使用localtime @ARGV = glob "aa.dat" or die "no files found"; $^I = ".bak"; 旧文件备份成.bak文件,如果是空串,则不生成备份,但不建议这样用。 while (<>) { s/^aaa:*/aaa:change value/; 修改值 s/^bbb:.*\n//; 删除 s/^ccc:.*/ccc:$date/; 更新日期 print; } 该程序生成一个修改后的新文件,文件名和旧文件一样,旧文件被备份成.bak文件。该程序 能在几秒钟内更新几百个文件,功能相当强大。 从命令行现场编辑 # perl -p -i.bak -w -e 's/aaa1/aaa/g' bbb*.dat -p 告诉perl为你写一个程序 -i.bak 同$^I设置,生成.bak文件 -w 打开警告 -e 表示后面是可执行代码 最后一个参数表明@ARGV将包含匹配这个glob文件名列表。一个完整的程序如下: #!/usr/bin/perl -w @ARGV = glob "bbb*.dat"; $^I=".bak"; while (<>) { s/aaa1/aaa/g'; print; } 如果代码少,只有几行,采用命令行选项更方便。 第十七章 一些高级Perl技术 用eval捕获错误 一种捕获致命错误的办 |
请发表评论