Skip to main content

perl 学习笔记完

第六章 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\ttest/匹配在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 /:/,"::🅰️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特色的写法。
```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 $newfilenewfile 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。这条规则保证,如果物理媒介被移到了另一台机器上,所有的目录仍和它们的文件呆在一起。这就是为什么可用rename把文件从一个目录移到另一个目录的原因,但两个目录必须在同一个文件系统(安装卷)中链接不能用于目录。

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 -246060; 每天的秒数

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;
}

如果代码少,只有几行,采用命令行选项更方便。

第十六章 简单数据库

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;
}

如果代码少,只有几行,采用命令行选项更方便。