第二十七章 Introduction to Perl Programming(中文版)

来源:互联网 发布:耳根算是网络写手吗? 编辑:程序博客网 时间:2024/05/16 01:46

Perl基础

脚本

Perl 是一种脚本语言,它在每次运行前都需要编译。在unix/linux下,通常perl脚本都是以 #!/usr/bin/perl 为开头的。这其实是perl(可执行文件)的全局路径,这个路径当然可以写成其他,只要能找到perl, 但有一点需要注意,perl的全局路径(/usr/bin/perl)不能超过32个字符。

Perl的内容和命令行

Perl路径给出后: #!/usr/bin/perl 接下来就该写perl脚本实际 内容了,你可以什么都不写,也可以写点注释语句,当然最应该写的就是命令行了。通常,注释行都是 以"#" 开头的,内容任意,反正在perl脚本中不会被运行。命 令行是以非空格字符开始并且以";"结束的。所以,你可以在把一条命令 写成多行,而直到你给出分号时结束。

直接命令行和子程序(函数)

普通命令行在perl脚本中是由上及下一行一行执行的。然而,子程序可以放到任何地方,只有被调用时才会被执行 。子程序是什么样子的呢?如果你看到一段代码以"sub"开头,并且格式是这样的:sub name {command;} , 那 么这就是子程序了。

其他特殊行

Perl 可以包含其他程序的代码,但需要这样的格式:require something 或者 use something.

引用

单引号: '' 或者: q// 
双引号: "" 或者: qq// 
引用执行结果:`` 或者: qx// 
引用几个词组: ('term1','term2','term3') 或者: qw/term1 term2 term3/ 
引用一个被引用的字符串: qq/"$name" is $name/; 
引用包含 "/": qq!/usr/bin/$file is readdy!;

标量上下文和列表上下文

在Perl中,标量上下文和列表上下文是有区别的,这是Perl脚本语言的一个亮点,这个特性在众多脚本语言当中是 独一无二的,也是非常实用的。 

在Perl的子程序中,不仅可以返回一个列表类似于C中的标量,而且还可以返回一个数组,这个数组不仅可 以给出标量上下文中元素的个数还可以给出列表上下文中元素本身。 

Perl该特性的巨大价值应该是显而易见的。


变量和运算符

概况

Perl的变量包含很多内容:标量变量、一维数组、二维数组以及关联数组。你可以通过特殊字符来声明Perl的变量 是标量还是数组,如$variable 表示普通的标量变量; @variable 表示数组;%variable 表示关联数组。 在Perl中,你不需要去区分变量中的字符串和数字,因为Perl可以自动识别它们。

标量

赋值变量: $price = 300; $name = "JOHN"; 变量的计算: $price *= 2; $price = $oldprice * 4; $count++; $worth--; 输出变量的值: print $price,"\n";

数组

赋值数组: $arr[0] = "Fred"; $arr[1] = "John"; 输出数组: print join(' ',@arr),"\n"; 
二维数组: $arr[0][0] = 5; $arr[0][1] = 7;

哈希(关联数组)

赋值单一元素的哈希: $hash{'fred'} = "USA"; $hash{'john'} = "CANADA"; 

赋值完整哈希:
%a = (    'r1',  'this is val of r1',    'r2',  'this is val of r2',    'r3',  'this is val of r3',);
或者:
%a = (    r1 => 'this is val of r1',    r2 => 'this is val of r2',    r3 => 'this is val of r3',);

赋值

"="可以把一个值赋给一个变量,或者其他和 "="组合的操作符在赋值的同时也可以做其他操作。 

$var = "string"; 把string赋予$var 
$var = 5; 把数字赋值给$var 

$var .= "string"; $var 后面加string 
$var += 5;$var加5 
$var *= 5; $var乘以5 
$var ||= 5; 如果 $var 为 0 则$var的值为 5 
$var x= 3; 使$var重复3次,如 $var 原来为a,则现在 为 aaa 

替换和赋值: 

($new = $old) =~ s/pattern/replacement/;

比较

字符串的比较: eq ne 如 in: $name eq "mary"
数字的比较: == != >= <= <=> 如: $price == 400.


And/Or/Not

评估成功或者失败的表达式: $yes or die; 意思是,如果$yes 没有定义那么脚本会 exit. 
AND 我们可以写成: && 或者 "and" 而OR 我们可以写成: || 或者"or". Not写成 "!"或者 "not". 

And, Or 和 Not 一般用于 if() 语句中: 
if($first && $second){....;} 
if($first || $second){....;} 
if($first && ! $second{....;} 这个表示,如果$first 为真,并且$second为假,才会执行{}内的语句。 
但大多时候,我们会用unless()语句去替代Not,这样看起来更容易懂: 
print if ! $noway; one uses: print unless $noway; 

.


分支

if

if(condition){    command;}elsif(condition){    command;}else{    command;}如果满足 condition, 那么会执行 command;

unless (和if相反)

unless(condition){    command1;}else{    command2;}只有满足了condition才会执行command2, 否则执行command1;

循环

while

while(condition){    command;}# 这里的next if表示,当condition2为假时,才会执行command2 while(condition1){    command1;    next if condition2;    command2;}# 当满足last condition时会退出循环while(condition){    command;    last if condition;}# 如果满足condition1 则会执行command1, 同时满足condition2时会进一步执行command2,每执行一次while循环都会执行command3while(condition1){    command1;    continue if condition2;    command2;}continue{    command3;}# 当满足condition1时,执行command,当同时满足condition1以及condition2时,再次执行commandwhile(condtion1){    command;    redo if condition2;}command while condition;

until (和while正好想反)

until(condition){    command;}until(condition){    command;    next if condition;    command;}until(condition){    command;    last if condition;}until(condition){    command;    continue if condition;    command;}continue{    command;}command until condtion;

for (=foreach)

# 迭代@data,输出每个 $_的值for(@data){    print $_,"\n";}# 把@data的值赋予$infofor $info (@data){    print $info,"\n";}#输出1到100中的奇数for $num (1..100){    next if $num % 2;    print $num,"\n";}#  (;;),表示为真。当$num>100时就停止循环for (;;){    $num++;    last if $num > 100;}

map

# 语法map (command,list);map {comm1;comm2;comm3;} list;
# 例map (rename($_,lc($_),<*>);
.

文件检测操作

用来检测文档的状态,如下:-f $file是否是普通文件-d $file是否是目录-r $file是否是可读-x $file是否可执行-w $file是否可写-o $file当前用户是否为该文件的所属主-l $file文件是否为链接-e $file文件是否存在-z $file文件存在,检测大小是否为0-s $file文件大小大于0-t FILEHANDLE该文件句柄是连接到某一个tty的-T $file文件是否是文本的文档-B $file文件是否是二进制文档-M $file距最后一次更改多长时间


正则表达式

什么是正则表达式

正则表达式是用某种模式去匹配字符串的一个公式。通常情况下,你可以通过搜索模式来找到匹配的字符串,也可 以把匹配的字符串给替换为你想要的。

模式

Perl的模式很多,可以是一个或多个字符,也可以是某个或者多个特殊字符,当然还可以是任意字符或者无任何字 符。总之,Perl的模式匹配是非常精准、抽象以及灵活的。模式匹配.任意一个字符.*任意个(包含0)任意字符a*the maximum of consecutive a'sa*?the minimum of consecutive a's.?一个或者无任意字符.+一个或者多个任意字符.{3,7}3到7个任意字符,尽量多.{3,7}?3到7个任意字符,尽量少.{3,}至少3个任意字符.{3}3次任意字符[ab]a或者b[^ab]不是a并且也不是b[a-z]任意一个小写字母^a 
\Aa
a开头a$ 
a\Z
a结尾A|bb|CCCa或者bb或者CCCtele(f|ph)onetelefone 或者 telephone\w大小写字母或下划线_\W除大小写字母和下划线的任意字符\d0-9 任意一个数字\D除数字之外的所有字符\s空格或者tab、换行以及其他空白字符\S除上面的以外\ttab\n换行\rcarridge return (翻译不通)\b以英文字母,数字为边界的字符串\bkeymatches key but not housekey(?#.......)注释(?i)区分大小写,可以是内部模式变量(?:a|b|c)a 或者 b 或者 c, 但在$n 中不存在字符(?=.....)包含..... 但不会存储在$&(?!.....)不包含..... 但不会存储在$&

替换

你可以使用这样的语句 s/pattern/replacement/把匹配到的 东西给替换。注释: 
这里的"s"表示命令,然后紧跟着三个分隔符(斜杠),前两个斜杠中的内容就是要匹配的内容,后两个斜 杠中的内容则为要替换为的内容。 

可以这样更换一个变量的内容: $var =~ s/pattern/replacement/; 
修改一个变量的内容,并把修改后的变量赋值给另外一个变量: 
($name = $line) =~ s/^(\w+).*$/$1/;
命令做什么s/A/B/;替换第一个出现的A为Bs/A/B/g;替换所有A为Bs/A+/A/g;替换1个或多个A为单一的As/^#//;删除所有以#为开头的字符串s/^/#/;在字符串最前增加一个#s/A(\d+)/B$1/g;A后边是一连串数字,把A替换为Bs/(\d+)/$1*3/e;把一连串数字后边再重复两遍该连串的数字使用 -e 可以直接在linux命令行中运行perl语句: perl -e '$aa = 4; $bb = '$aa'; $bb =~ s/(\$\w+)/$1/ee; print $bb,"\n";' s/here goes date/$date/g;把 "here goes date" 替换成 $dates/(Masumi) (Nakatomi)/$2 $1/g;把两个字符串的位置互换s/\000//g;把空字符串移除s/$/\033/;在每行后加一个^M,使其在dos下可读

输入输出

输出一个变量的值

print $var,"\n";

输出格式化的字符串

printf("%-20s%10d",$user,$wage);

把输入的值赋值给变量,并删除新行

chomp()(perl5)会删除\n以及后面的所有内容。chop()(perl4)会删除最后一个字符,而不管这个字符是什么. 

chomp($var = <STDIN>);

流线性读入一个文件

open(IN,"<filename") || die "Cannot open filename for input\n";while(<IN>){    command;}close IN;

把读入的文件赋值给数组

open(AAA,"<infile") || die "Cannot open infile\n";@bigarray = <AAA>;close AAA;

把输出的结果重定向到文件中

open(OUT,">file") || die "Cannot oben file for output\n";while(condition){    print OUT $mystuff;}close OUT;

检测打开文件是否会输出信息(eof)

open(IN,"<file") || die "Cannot open file\n";if(eof(IN)){    print "File is empty\n";}else{    while(<IN>){        print;    }}close IN;

命令行中提及程序的名字

空文件句柄“<>”能够迭代性的读取文件,当前程序的文件名可通过$ARGV得到。如下,可以在诸多行的行头加 上文件名:
while(<>){    $file = $ARGV;    print $file,"\t",$_;    open(IN,"<$file") or warn "Cannot open $file\n";    ....commands for this file....    close(IN);}

得到文件名

得知当前目录

@dir = <*>;

迭代性的使用当前目录

while(<*>){    ...commands...}

通过< >选择文件

@files = </longpath/*.c>;

通过glob()选择文件

This is the official way of globbing:
@files = glob("$mypatch/*$suffix");


Readdir()

Perl即使不使用globbing shell也是可以得到目录的,并且既快速又可控,但是我们必须要使用opendir()以及 closedir().
opendir(DIR,".") or die "Cannot open dir.\n";while(readdir DIR){    rename $_,lc($_); }closedir(DIR);

Unix命令的输入、输出的重定向

通过Unix管道得到程序要使用的数据

open(IN,"unixcommand|") || die "Could not execute unixcommand\n";while(<IN>){    command;}close IN;

把输出的数据给Unix管道

open(OUT,"|more") || die "Could not open the pipe to more\n";for $name (@names){    $length = length($name);    print OUT "The name $name consists of $lenght characters\n";}close OUT;

执行Unix命令

如果输出信息只有一行字符串

system("someprog -auexe -fv $filename");

执行Unix命令,并且把输出值赋给变量

如果输出信息只有一行字符串: 

chomp($date = qx!/usr/bin/date!); The chomp($date = qx!/usr/bin/date!); chomp()(perl5)把字符串后边的换行符“\n”去掉,$date得到Unix命令 "date"的值. 

如果输出信息是多行,那么Perl会把所有输出信息存到数组中: 

chomp(@alllines = qx!/usr/bin/who!);

替换整个Perl程序为一个Unix程序

exec anotherprog; 但是这样会删除掉Perl程序


Perl内置函数

字符串函数

所有小写变大小: $name = uc($name);只把第一个字母变大写: $name = ucfirst($name);所有大写变小写: $name = lc($name);只把第一个字母变小写: $name = lcfirst($name);得到字符串的长度:$size = length($string);截取字符串的第4到第5:$part = substr($whole,4,5);删除行尾(一般是删除换行符以及下面内容):chomp($var);删除最后一个字符:chop($var);拷贝(把$word的值拷贝给$salt):$code = crypt($word,$salt);把字符串当作perl代码来执行:eval $var;打印字符(substring)在字符串(string)的什么位置:$pos = index($string,$substring);最后面的substring在string的什么位置:$pos = rindex($string,$substring);引用字符串:$quote = quotemeta($string);

数组函数

和Unix中的grep类似,过滤出符合要求的元素:@found = grep(/[Jj]ohn/,@users);把数组各元素作为操作对象,本例中把每个元素所有字母都变为大写:@new = map(lc($_),@start);把数组中的所有元素组合成一个字符串,元素间用函数给定的分隔符分割,本例中分隔符为空格:$string = join(' ',@arr);把一个字符串分割成一个数组:@data = split(/&/,$ENV{'QUERY_STRING'};按字母顺序排序数组:sort(@salery);将数组的次序颠倒:reverse(@salery);获取哈希的键:keys(%hash);获取哈希的值:values(%hash);获取哈希的键和值:each(%hash);清空数组:@arr = ();删除哈希中的某个元素:delete $hash{$key};检查哈希的某个键是否存在:if(exists $hash{$key}){;}查看哈希是否有元素:scalar %hash;把数组最后一个元素取出来并返回其值:$last = pop(@IQ_list);把数组第一个元素取出来并返回其值:$first = shift(@topguy);在数组最后增加一个元素:push(@waiting,$name);在数组最前面增加一个元素:unshift(@nowait,$name);把数组从第0个元素起,共2个元素替换为$var,这里的0和2不是固定的:splice(@arr,0,2,$var);得到数组总共有多少个元素:scalar @arr;得到数组最后一个索引:$lastindex = $#arr;

操作文件的函数

打开文件作为输入内容:open(IN,"</path/file") || die "Cannot open file\n";打开文件作为输出内容:open(OUT,">/path/file") || die "Cannot open file\n";打开文件作为追加内容:open(OUT,">>$file") || &myerr("Couldn't open $file");关闭文件:close OUT;给文件设置权限:chmod 0755, $file;删除文件:unlink $file;给文件重命名:rename $file, $newname;给文件做硬链接:link $existing_file, $link_name;给文件做软链接:symlink $existing_file, $link_name;创建目录:mkdir $dirname, 0755;删除目录:rmdir $dirname;把$file的大小减小到$size:truncate $file, $size;修改文件的所属主以及所属组:chown $uid, $gid;找到软链接文件的源文件:$file = readlink $linkfile;获取文件的全部属性信息:@stat = stat $file;  

转换函数

数字变字符:chr $num;字符变数字:ord($char);十六进制变浮点:hex(0x4F);八进制变浮点:oct(0700);把time变成本地时间格式:localtime(time);把time变成格林威治格式:gmtime(time);把一个列表或数组以在实际机器存贮格式或C等编程语言使用的格式转化到一个简单变量中:$string = pack("C4",split(/\./,$IP));与pack功能相反,将以机器格式存贮的值转化成Perl中值的列表:@arr = unpack("C4",$string);

子程序(等同于C语言中的函数)

定义一个子程序

sub mysub {    command;}
例子:
sub myerr {    print "The following error occured:\n";    print $_[0],"\n";    &cleanup;    exit(1);}

调用一个子程序

&mysub;

给子程序参数

&mysub(@data);

在子程式中接收参数

作为全局变量:
sub mysub {    @myarr = @_;}
sub mysub {    ($dat1,$dat2,$dat3) = @_;}
作为局部变量:
sub mysub {    local($dat1,$dat2,$dat3) = @_;}


一些特殊的变量

语法

含义

$_在(@arr){ $field = $_ . " ok"; } 这个例子中,$_表示循环中的字符串$.表示当前程序的行号: while(){$0程序名$$运行该perl脚本的进程号$<当前程序的真实uid$>当前程序有效的uid$|该变量默认为0,当设定为非0时,会强制刷新输出: select XXX; $| = 1;$&最后一次匹配模式匹配到的字符串$1....The ()-embraced matches of the last patternsearch$`含有上次成功的模式匹配内容之前的字符串$'含有上次成功的模式匹配内容之后的字符串


派生

派生很简单,通常我们会以三种方式区分派生 if(){} ,分别是父、子 和 错误。
if($pid = fork){    # 父    command;}elsif($pid == 0){    # 子    command;    # 子必须以exit为结尾!!    exit;}else{    # 错误    die "Fork did not work\n";} 

为派生的子创建管道

创建一个管道

pipe(READHANDLE,WRITEHANDLE);

刷新管道

select(WRITEHANDLE); $| = 1; select(STDOUT);

在父与子之间设置两个管道

pipe(FROMCHILD,TOCHILD);  select(TOCHILD); $| = 1; select(STDOUT);pipe(FROMPARENT,TOPARENT);select(TOPARENT);$| = 1; select(STDOUT);if($pid = fork){    # 父    close FROMPARENT;    close TOPARENT;    command;}elsif($pid == 0){    # 子    close FROMCHILD;    close TOCHILD;    command;    exit;}else{    # 错误    command;    exit;}

创建一个套接字来连接另外的计算机

# 需要放置在脚本开始的一些地方require 5.002;use Socket;use sigtrap;# 需要提前设置好的一些信息$port   = 80;$remote = 'remotehost.domain';$iaddr  = inet_aton($remote);$paddr  = sockaddr_in($port,$iaddr);#  套接字socket(S,AF_INET,SOCK_STREAM,$proto) or die $!;# 刷新套接字select(S); $| = 1; select(STDOUT);# 连接connect(S,$paddr) or die $!;# 输入到套接字print S "something\n";#  从套接字中读取$gotit = <S>;#只读出一个字符read(S,$char,1);# 关闭套接字close(S);

获取Unix用户以及网络相关信息

获取指定用户的密码: @entry = getpwnam("$user"); 
或者通过指定uid的方式获取用户的密码: @entry = getpwuid ("$UID"); 

你也可以得到group, host, network, services, protocols这些信息通过以下命令: getgrnam, getgrid, gethostbyname, gethostbyaddr, getnetbyname, getnetbyaddr, getservbyname, getservbyport, getprotobyname, getprotobynumber

你还可以通过如下方式,获取一系列你想得到的信息,只不过你需要一个循环而已: 

setpwent; 
while(@he = getpwent){ 
commands... 
} 
entpwent; 

如,你想要得到一些用户的家目录:
setpwent;while(@he = getpwent){    printf("%-20s%-30s\n",$he[0],$he[7]);}endpwent;
类似的,该方法同样适用于上面刚刚描述的那些东西,只不过需要在设置命令后面加上一个"stayopen" .


数学计算

加: + 
减: - 
乘: * 
除:/ 
平方:** 
返回e的n次方,()内为n exp() 
模:% 
平方根: sqrt() 
绝对值: abs() 
Tangens: atan2() 
Sinus: sin() 
Cosine: cos() 
返回一个随机数: rand()


使用“format”格式化输出

这个要比printf要简单,首先需要在程序显眼的地方定义一下格式,然后就可以使用这个定义好的格式了: 

format filehandle = 
@<<<<<<<<<<@###.#####@>>>>>>>>>>@|||||||||| 
$var1, $var3, $var4 
. 

现在可以使用上面定义的输出格式了: 

write FILEHANDLE; 

The @<<< does left adjustment, the @>>> right adjustment, @##.## is for numericals and @||| centers.


命令行下使用Perl

输出Perl的版本号:perl -v;检查perl脚本语法是否正确而不运行:perl -wc <file>;命令行下直接执行perl语句:perl -e 'command';循环读取文件,并输出到标准输出:perl -pe 'command' <file>;循环读取文件,并输出到标准输出:perl -lpe 'command' <file>;在文件中直接就地修改:perl -i -pe 'command' <file>;该选项将会以空格分割输入,并把分割结果存放到@F中:perl -a -e 'print if $F[3] =~ /ETH/;' <file>;循环读入文件,但不是输入:perl -ne 'command' <file>;

原创粉丝点击