Perl.pod中文版
2010-08-16 15:15:22 阿炯

Perl.pod
标题
perl - Practical Extraction and Report Language

语法
perl [ -sTuU ] [ -hv ] [ -V[:configvar] ]
[ -cw ] [ -d[:debugger] ] [ -D[number/list] ]
[ -pna ] [ -Fpattern ] [ -l[octal] ] [ -0[octal] ]
[ -Idir ] [ -m[-]module ] [ -M[-]module... ]
[ -P ] [ -S ] [ -x[dir] ]
[ -i[extension] ] [ -e command ]
[ -- ] [ programfile ] [ argument ]...

如果你是一个 Perl 的初学者,那么你最好先阅读一下 perlintro, 那是一个为初学者专门准备的简单介绍,为你讲解一些 Perl 的基础知识,并且帮助你遨游于剩馀的大量 Perl 文档。 为了便于阅读,Perl 手册被分成了一下几个部分:

概述
perl:Perl 概述(本文档)
perlintro:为初学者准备的 Perl 简介
perltoc:Perl 文档目录
activeperl:ActivePerl 概述

教程
perlreftut:Perl 引用简介
perldsc:Perl 数据结构介绍
perllol:Perl 高级数据结构: 数组的数组(二维数组)
perlrequick:Perl 正则表达式快速入门
perlretut:Perl 正则表达式教程
perlboot:Perl OO 入门教程
perltoot:Perl OO 教程(一)
perltooc:Perl OO 教程(二)
perlbot:Perl OO 高级技巧与实例
perlstyle:Perl 程序风格指南
perlcheat:Perl 夹带
perltrap:Perl 陷阱
perldebtut:Perl 调试教程
perlfaq:Perl 常见问题(FAQ)
perlfaq1:普通问题
perlfaq2:开始使用和学习 Perl 时碰到的问题
perlfaq3:编程工具
perlfaq4:数据操作
perlfaq5:文件和格式
perlfaq6:正则表达式
perlfaq7:Perl 语言问题
perlfaq8:系统交互
perlfaq9:网络编程

参考手册
perlsyn:Perl 语法
perldata:Perl 数据结构
perlop:Perl 操作符和优先级
perlsub:Perl 子程序
perlfunc:Perl 内建函数
perlopentut:Perl open() 教程
perlpacktut:Perl pack() 和 unpack() 教程
perlpod:Perl POD 文档
perlpodspec:Perl POD 文档格式说明
perlrun:Perl 运行和选项
perldiag:Perl 诊断信息
perllexwarn:Perl 警告
perldebug:Perl 调试
perlvar:Perl 预定义变量
perlre:Perl 正则表达式高级教程
perlreref:Perl 正则表达式快速参考
perlref:Perl 引用高级教程
perlform:Perl 格式
perlobj:Perl 对象
perltie:Perl 绑定(将对象隐藏在普通变量背後)
perldbmfilter:Perl DBM 过滤器
perlipc:Perl 进程间通信
perlfork:Perl fork() 说明
perlnumber:Perl 数字符号
perlthrtut:Perl 线程教程
perlothrtut:以前的 Perl 线程教程
perlport:Perl 移植手册
perllocale:Perl 区域支持
perluniintro:Perl Unicode 介绍
perlunicode:Perl Unicode 支持
perlebcdic:考虑将 Perl 运行在 EBCDIC 编码的平台上
perlsec:Perl 安全
perlmod:Perl 模块编程: 入门教程
perlmodlib:Perl 模块编程: 如何书写并使用一个新模块
perlmodstyle:Perl 模块编程: 编写模块的规范
perlmodinstall:Perl 模块编程: 如何安装一个 CPAN 上发布的模块
perlnewmod:Perl 模块编程: 准备发布自己的模块
perlutil:Perl 打包、发布工具
perlcompile:Perl 编译器套件介绍
perlfilter:Perl source filters

Perl 内部实现和 C 语言接口
perlembed:嵌入 Perl 到你的 C/C++ 程序中去
perldebguts:Perl debugging guts and tips
perlxstut:Perl XS 教程
perlxs:Perl XS 编程接口(API)
perlclib:C 标准库的 Perl 替代
perlguts:使用 Perl 内部函数扩展 Perl
perlcall:从 C 中调用 Perl 时的约定
perlapi:Perl API 一览
perlintern:Perl 内部函数
perliol:C API for Perl's implementation of IO in Layers
perlapio:Perl 内部 IO 抽象接口
perlhack:Perl 黑客指南

杂项
perlbook:Perl 书籍讯息
perltodo:Perl 未来展望
perldoc:Look up Perl documentation in Pod format
perlhist:Perl 修改记录
perldelta: 相对于前一个版本的修改
perl586delta:5.8.6 版所做的修改
perl585delta:5.8.5 版所做的修改
perl584delta:5.8.5 版所做的修改
perl583delta:5.8.3 版所做的修改
perl582delta:5.8.2 版所做的修改
perl581delta:5.8.1 版所做的修改
perl58delta: 5.8.0 版所做的修改
perl573delta:5.7.3 版所做的修改
perl572delta:5.7.2 版所做的修改
perl571delta:5.7.1 版所做的修改
perl570delta:5.7.0 版所做的修改
perl561delta:5.6.1 版所做的修改
perl56delta: 5.6 版所做的修改
perl5005delta:5.005 版所做的修改
perl5004delta:5.004 版所做的修改
activeperl-release:ActivePerl 发布日志
activeperl-changes:ActivePerl 修订历史
perlartistic:Perl Artistic License
perlgpl:GNU GPL 许可协议内容

语言相关
perlcn:Perl 简体中文简介(采用 EUC-CN 编码)
perljp:Perl 日文简介(采用 EUC-JP 编码)
perlko:Perl 朝鲜文简介(采用 EUC-KR 编码)
perltw:Perl 繁体中文简介(采用 Big5 编码)

平台相关
perlaix:AIX 平台说明
perlamiga:AmigaOS 平台说明
perlapollo:Apollo DomainOS 平台说明
perlbeos:BeOS 平台说明
perlbs2000:POSIX-BC BS2000 平台说明
perlce:WinCE 平台说明
perlcygwin:Cygwin 平台说明
perldgux:DG/UX 平台说明
perldos:DOS 平台说明
perlepoc  EPOC 平台说明
perlfreebsd:FreeBSD 平台说明
perlhpux:HP-UX 平台说明
perlhurd:Hurd 平台说明
perlirix:Irix 平台说明
perlmachten:Power MachTen 平台说明
perlmacos:Mac OS (Classic) 平台说明
perlmacosx:Mac OS X 平台说明
perlmint:MiNT 平台说明
perlmpeix:MPE/iX 平台说明
perlnetware:NetWare 平台说明
perlopenbsd:OpenBSD 平台说明
perlos2:OS/2 平台说明
perlos390:OS/390 平台说明
perlos400:OS/400 平台说明
perlplan9:Plan 9 平台说明
perlqnx:QNX 平台说明
perlsolaris:Solaris 平台说明
perltru64:Tru64 平台说明
perluts:UTS 平台说明
perlvmesa:VM/ESA 平台说明
perlvms:VMS 平台说明
perlvos:Stratus VOS 平台说明
perlwin32:Windows 平台说明

如果你使用的是类 UNIX 系统,那么上述手册页默认情况下安装在 /usr/local/man/ 目录。

除此之外,还有大量的 Perl 模块的文档。这些附加文档默认被放置在 /usr/local/lib/perl5/man 目录或者是 Perl 库目录下的 man 子目录 这里边有一部分附加文档是 Perl 的标准发布包中自带的,其馀则是自行安装的 第三方模块的文档。

译者注:如果你使用的是 windows 平台下的 ActiveState Perl,并且安装在了 C:\Perl,那么上述手册页的 html 版本都在 C:\Perl\html 目录,pod 版本则在 C:\Perl\lib\pod 目录。如果你是用 .msi 安装包形式安装的话,安装包就会在 [开始] -> [程序] -> [ActiveState ActivePerl 5.x] -> [Documentation] 这 位置生成一个快捷方式,你点击它就可以浏览所有的 ActivePerl 文档。

在类 UNIX 环境下,通过正确设置相应的系统文件,或者修改 MANPATH 环境变量, 你可以使用 man(1) 程序来打开手册页。

用以下命令可以知道你的 Perl 手册页被安装到了什么地方:

perl -V:man.dir

如果返回结果中包含有一个常用的部分,比如是 /usr/local/man/man1 和 /usr/local/man/man3, 那么你只需要添加 (/usr/local/man) 到你的 man(1) 的配置文件或者 MANPATH 环境变量中。

如果返回结果不包括这些常用目录,那你只好将每一行都添加到 man(1) 的配置文件或者 MANPATH 环境变量了。此外还可以用 Perl 自带的 perldoc 脚本来浏览 Perl 手册页或者模块 文档。(译者注:在 windows 平台下,因为没有 man 命令,所以要想在命令行下浏览Perl 文档,那么这是唯一的方式。)

如果你的程序不能正确工作而你又不知道该去看哪个手册页,那么请试一下 -w 命令行选项,它通常会给你一个相对准确的信息以指出到底是哪儿出了麻烦。

说明
Perl 可以轻易的实现扫描任意文本文件、从中提取有用的信息、然後按照要求的 格式打印出报表,Perl 也是一种优秀的系统管理工具。

Perl 语言被设计成是“实用的”(易于使用、高效、功能丰富),而不是“华丽的” (简单、优美、精致)。『译者注:译文无法体现原文的意思,原文为 "The language is intended to be practical (easy to use, efficient, complete) rather than beautiful (tiny, elegant, minimal)."』

据作者来看,Perl 结合了 C、sed、awk、sh 等一些工具的许多优秀特性。 这样熟悉以上工具的人可以只花费较少的代价就可以使用 Perl(语言历史学家们可能还记得 csh, Pascal, 甚至还有 BASIC-PLUS.)。

以下是 Perl 的一些特点:
1.Perl 的表达式语法和 C 的表达式语法非常相似。
2.和大多数 UNIX 工具不同,Perl 本身不会限制你的数据大小,只要你拥有足够的内存。
3.Perl 可以一口气读取整个文件的内容并且保存到一个普通的字符串变量中。
4.递归嵌套层数没有限制。
5.使用“哈希表(hash)”(有些资料也称作“关联数组(associative arrays)”) 来 表示表格。在必要的时候哈希表会自动增长额外的空间以免妨碍到性能。
6.使用“模式匹配”来快速扫描大量数据。
7.Perl 也可以和二进制数据打交道,通过某些技术,可以使一个 DBM 文件象一个 哈希表一样操作。
8.出于 setuid 方式运行的 Perl 脚本要比同样的 C 程序安全,Perl 拥有一个贯穿数据流的污染检测系统,这可以防止大多数令人生厌的安全漏洞。

如果你碰到了一个任务需要用 sed、 awk 或者 sh 来完成,结果却发现 无法做到或者是想运行起来更快一些,并且你又不想写 C 程序来处理,那幺 Perl 就 是你最好的选择。Perl 允许你将现有的 sed 脚本或者 awk 脚本转换为 Perl 脚本。

早在 1993 年(参见 perlhist)的时候, Perl 5 几乎被完全重写了一次,经历了这 次版本更新以後,Perl 增加了一下几个重要特性:
模块化和可重用的编程思想,以及无数的模块。 在 perlmod、 perlmodlib 和 perlmodinstall 中有介绍。
嵌入和扩展,在 perlembed, perlxstut, perlxs, perlcall, perlguts, 和xsubpp 中有介绍。
变量绑定(包含许多关联的 DBM 工具) ,在perltie 和 AnyDBM_File 中介绍。
子程序可以被重载、自动加载、指定原型在 perlsub 中介绍。
任意嵌套的数据结构和匿名子程序,在 perlreftut, perlref, perldsc, 和 perllol 中介绍。
面向对象编程,在 perlobj, perlboot, perltoot, perltooc, 和 perlbot 中介绍。
支持“轻量级进程”(线程) ,在 perlthrtut 和 threads 中介绍。
支持 Unicode, 国际化, 区域化,在 perluniintro, perllocale 和 Locale::Maketext 中介绍。
词法作用域,在 perlsub 中介绍。
增强了正则表达式功能,在 perlre 中介绍,增加的示例参见 perlop。
增强了调试器和交互式环境,可以集成外部编辑器。 在 perldebtut, perldebug 中 perldebguts 介绍。
POSIX 1003.1 兼容库,参见 POSIX。
好了,有了以上这些宣传,我想肯定已经足够了。

可用性
在大多数操作系统上 Perl 都是可用的。实际上所有类 UNIX 平台都是可用的。 详情参见 "Supported Platforms" perlport/"Supported Platforms", 那里有一个清单。

运行环境
参见 perlrun.

作者
Larry Wall , 以及其他许多普通人;如果你认为你的 Perl 成功案例能够帮助那些需要在他们的项目中使用 Perl 的人, 或者你想简单地表达一下你对于 Larry 以及其他 Perl 开发人员的谢意,那么请写信给 perl-thanks@perl.org。

文件
"@INC":定位 Perl 模块搜索路径

参见
a2p:awk 脚本到 perl 脚本的翻译器
s2p:sed 脚本到 perl 脚本的翻译器
Perl 主页
Perl 文章(O'Reilly 维护)
CPAN:完整的 Perl 档案网络
Perl Mongers

诊断
使用 use warnings 语句(或者 -w 命令行选项)可以得到很多有用的诊断信息。查看 perldiag 可以得到所有的 Perl 诊断信息的解释。使用 use diagnostics 语句可以自动地将错误信息转换成更加详细的形式。

编译时产生的错误信息可以告诉你错误发生的行号,以及具体出错的位置 (译者注:因为 Perl 总是在错误发生之後才会发现错误,因此实际的错误位置往往要比提示的靠前一些)。

如果 Perl 脚本是通过命令行参数 -e 传递过去的,那么每一个 -e 参数将被当作一行来计算行号。

Setuid 脚本可以添加约束,从而产生类似于“Insecure dependency” (不安全的依赖) 之类的消息,参见 perlsec。

我们是不是应该提倡在每个程序中都使用 -w 开关呢?

BUGS
-w 命令行选项不是强制的。

Perl 的有些操作是和你的机器相关的,比如 type casting(类型转换)、 atof()操作、还有使用 sprintf()进行浮点数的输出等。

如果你的标准输入输出库需要在每次读或者写操作之後移动文件指针,那么 Perl 中会同样如此,但是 sysread() 和 syswrite() 函数中不会这样。

尽管没有一种内建数据类型的尺寸会受到除了物理内存大小之外的其它任何限制,但是仍然有一些东西在制约着你:变量名的长度不能超过 251 个字母;另外,诊断时显示 出来的行号在 Perl 内部是使用短整型存储的,因此最大不能超过 65535(超过这个数之后将发生环绕)。

你可以将你发现的 bug 汇报到 perlbug@perl.org。不过最好附上一份完整的配置信 息,perl 源代码树中的 myconfig 可以帮你做这件事,或者用 perl -V 也行。如果 你成功地编译了 Perl,那么你可以在 utils/ 目录下找到 perlbug 脚本,它可 以帮助你邮寄一份 bug 报表。

Perl 事实上是一个“夭折的垃圾列表器”,但是千万不要告诉任何人我这么说过。

注意事项
Perl 的格言是“条条大路通罗马”(There's more than one way to do it.),具体有多少作为练习留给读者去猜测好了。程序员的三种主要美德是:懒惰、急躁和傲慢。至于为什么这么说请参考骆驼书。

TRANSLATORS
王兴华-"flw"


Perl的命令行参数和ARGV

程序名:$0

$0表示当前正在运行的Perl脚本名,有三种情况:
1.如果执行方式为perl x.pl,则$0的值为x.pl而非perl命令本身
2.如果执行方式为./x.pl,则$0的值为./x.pl
3.如果执行的是perl -e或perl -E一行式perl程序,则$0的值为-e或-E

命令行参数ARGV

perl将perl命令行的参数列表放进数组ARGV(@ARGV)中。既然是数组,就可以访问($ARGV[n])、遍历,甚至修改数组元素
1.ARGV数组分三种情况收集:
1.1.perl x.pl a b c方式运行时,脚本名x.pl之后的a b c才会被收集到ARGV数组
1.2../x.pl a b c方式运行时,a b c才会被收集到ARGV数组
1.3.perl -e 'xxxxx' a b c方式运行时,a b c才会被收集到ARGV数组

2.ARGV数组索引从0开始计算,索引0位从脚本名(perl程序名)之后的参数开始计算
3.默认,这些命令行参数是perl程序的数据输入源,也就是perl会依次将它们当作文件进行读取
4.参数是有序的,读取的时候也是有序的

5.需要区分ARGV变量和ARGV数组:
5.1.$ARGV表示命令行参数代表的文件列表中,当前被处理的文件名
5.2.@ARGV表示命令行参数数组
5.3.$ARGV[n]:表示命令行参数数组的元素
5.4.ARGV:表示<>当前正在处理的文件句柄

例如脚本test.pl的内容如下:
/usr/bin/perl
print '$ARGV[0] ---> ',$ARGV[0],"\n",
'$ARGV[1] ---> ',$ARGV[1],"\n",
'$ARGV[2] ---> ',$ARGV[2],"\n",
'$ARGV[3] ---> ',$ARGV[3],"\n",
'$ARGV[4] ---> ',$ARGV[4],"\n";

执行这个程序:
shell> ./test.pl -w a b c d
$ARGV[0] ---> -w
$ARGV[1] ---> a
$ARGV[2] ---> b
$ARGV[3] ---> c
$ARGV[4] ---> d

因为是数组,所以可以修改数组,比如强制指定元素:
/usr/bin/perl
@ARGV=qw(first second third);
print '$ARGV[0] ---> ',$ARGV[0],"\n",
'$ARGV[1] ---> ',$ARGV[1],"\n",
'$ARGV[2] ---> ',$ARGV[2],"\n";

shell> ./test.pl a b c d
$ARGV[0] ---> first
$ARGV[1] ---> second
$ARGV[2] ---> third

例如,读取两个文件(a.txt,b.txt)的内容:
/usr/bin/perl
while(<>){
    print $_;
}

shell> ./test.pl fa.txt fb.txt

如果想读取标准输入,只需使用"-"作为文件参数即可。

$ echo -e "abcd1\nefg2" | ./test.pl fa.txt - fb.txt

上面将按先后顺序读取fa.txt,标准输入(管道左边命令的输出内容),fb.txt。

通过在脚本中对@ARGV来屏蔽命令行中所传入的参数

@ARGV=qw# freeoa net noname #;    #强制让<>操作符只读取这3个文件
while(<>){
    chomp;
    say $;
}


从Perl的命令行速看其语法

变量

为变量加上大括号是标准的写法
${var} = 369
@{arr} = (1,2,3)

注意变量的输出:print、printf、say和sprintf。printf函数,语法格式是printf "format_string",expr,...sprintf()函数表示按照printf的方式进行格式化,然后保存起来并可以赋值给变量。

如果想要判断变量是否已经定义,可以使用defined($var)。

数值、字符串和反斜线序列

1).数值和字符串之间的转换取决于做什么运算。例如加法表示数学运算,会让字符串转换成数值
2).浮点数和整数之间的转换仍然取决于所处的环境,在需要整数的时候浮点数会直接截断成整数

字符串使用单引号或双引号包围,此外,Perl中也有反引号,这3种引用和shell中的单、双、反引号类似:
1).双引号表示弱引用,变量可以在双引号中进行内容替换
2).单引号表示强引用,内部不会进行变量替换,反斜线序列也会失效
2.1).在unix下的perl一行式程序中因为一般使用单引号包围-e表达式,所以一行式perl中单引号比较少用
2.2).如果非要使用单引号,可以考虑使用q()来引用,见下文对q、qq和qx的解释
3).反引号表示执行操作系统命令并取得命令的输出结果,需要记住的是它自带尾随换行符(除非所执行命令就没有带尾随换行)。例如:$files = `ls /root`
$ perl -e '$a = `date +"%F %T"`;print $a'
2024-03-25 08:13:28

Perl中有以下几个常见的反斜线序列:
\n
\r
\t
\l    # 将下个字母转换为小写
\L    # 将后面的多个字母都转换为小写,直到遇到\E
\u    # 将下个字母转换为大写
\U    # 将后面的多个字母都转换为大写,直到遇到\E
\Q    # 和\E之间的所有字符都强制当作字面符号
\E    # \L、\U和\Q的结束符号

字符串连接需要使用".",例如"abc"."def"等价于"abcdef"。字符串重复次数可以使用小写字母x,例如"a" x 3得到"aaa","abc" x 2得到abcabc。

Perl中数值和字符、字符串都支持自增、自减操作。

在Perl中,引号不一定非要写成符号,可以使用q()来表示单引号、qq()来表示双引号、qx()表示反引号。其中这里的括号可以替换成其它成对的符号,例如qq{}、qq//、qq%%都是可以的。

使用q类型的引用可以避免在shell中一行式Perl程序的引号转义问题。例如在一行式Perl中想要保留单引号:perl -e "print q(abc'd)"


列表与数组

数组$#arr或$#{arr}表示数组的最后一个数组索引值,所以数组元素个数等于该值加1。如果想要直接取得数组的个数,将数组赋值给一个变量或者使用scalar()函数即可。这涉及到Perl的上下文知识。数组的索引可以是负数,-1表示最后一个元素,-2表示倒数第二个元素。所以$arr[-1]等价于$arr[$#arr],都是最后一个元素。

数组支持切片功能,切片操作使用@符号,切片操作会返回一个新的列表(数组)。切片时同一个元素可以出现多次,且顺序随意,这比其它语言的切片要自由的多。如果想要取从第2个到倒数第2个元素,可以使用这种切片方式@arr[1..$#{arr}-1]。

数组可以使用pop/push函数来移除、追加最尾部的一个元素,使用shift/unshift函数移除、插入首部第一个元素。如果想要操作中间某个元素,可以使用splice()函数。另外,还有sort()、reverse()函数,其中sort比较强大。对于sort还需注意的是,它不是在原地排序的,而是生成一个排序后的新列表,原数组中元素的顺序并不会受排序的影响。所以需要将这个新列表赋值给另一个数组变量才能得到排序后的结果。但也有方法可以直接输出排序后的结果,而且这个技巧非常有用:
$ perl -e '@arr=qw(Perl Python Shell Ruby PHP);print "@{ [ sort @arr ] }\n"'
PHP Perl Python Ruby Shell

这属于Perl的高级技巧,这里大致解释一下,它分成2个部分:
第一部分是[],它表示构造一个匿名列表,匿名列表的内容可以来自于字面元素,也可以来自函数的结果或者表达式的结果,正如上面是将sort函数排序的结果构造成匿名列表;
第二部分是@{xxx},它表示将列表xxx引用进行解除,然后可以插入到双引号中进行输出。

所以,当想要将某个操作的结果直接输出时,就可以采取这种方式:
@{ [ something you do ] }

要遍历数组,可以使用for、foreach、each(v5.12+支持对数组返回索引号),当然也可以使用while。必须注意,Perl中for/foreach以元素存在性测试遍历时,控制变量$i是各个元素的引用,而不是复制各个元素再赋值给$i,所以在遍历过程中修改$i的值会直接修改原数组。

$ perl -e '@arr=qw(Perl Python Shell Ruby PHP);
    for $i (@arr) {$i .= "X"};  # 为每个元素加尾随字符"X"
    print "@arr\n"'

perl -e '@arr=qw(Perl Python Shell Ruby PHP);
        for(@arr) {$_ .= "X"};
        print "@arr\n"'

>
PerlX PythonX ShellX RubyX PHPX

join()用于将列表连接成字符串,split()用于将字符串分割成列表。


Hash(关联数组)

hash结构中key/value一一映射,和Shell中的关联数组是一个概念。

在Perl中,无论是数组还是hash结构,本质都是列表。所以下面的列表数据可以认为是数组,也可以认为是hash,关键在于它赋值给什么类型。

("name","FreeOA","age",32)

在Perl中,数组类型使用@前缀表示,hash类型则使用%前缀表示。列表作为hash结构时,每两个元素组成一个key/value对,其中key部分必须是字符串类型。在使用胖箭头的写法时,如果key是符合命名规范的,可以省略key的引号(因为它是字符串,正常情况下是应该加引号包围的)。hash数据不能在双引号中进行内容替换,可以直接输出它,但直接输出时hash的元素是紧挨在一起的,这表示hash的字符串化。如下:

perl -e '%hash = qw(name FreeOA age 32);print %hash,"\n"'
>
age32nameFreeOA

perl -e '%hash = qw(name FreeOA age 32);print "%hash","\n"'
>
%hash

从hash中切片使用@表示,如@hash{KEY1,KEY2}这和数组是一样的。虽然hash类型本身不能在双引号中进行内容替换,但hash取值或者hash切片可以在双引号中替换。
perl -e '%hash = (name=>"FreeOA",age=>36);print "@hash{name,age,name,age}","\n"'
>
FreeOA 36 FreeOA 36

相关函数
主要有keys()、values()、exists()和delete()。
keys()返回hash结构中所有key组成的列表。例如keys %hash
values()则返回hash结构中所有value组成的列表。例如values %hash
exists()用来检测hash结构中元素是否存在。例如exists $hash{KEY}
delete()用来删除hash中的一个元素。例如delete $hash{KEY}

排序hash结构只需对keys的结果应用sort/reverse函数,再进行遍历输出即可。

要遍历hash结构,可以使用while + each或者for/foreach遍历hash的key或value。


默认变量$_、@ARGV、@_

$_是对于标量变量而言的默认变量。对于需要列表/数组的时候,默认变量不再是$_,而是@ARGV或@_:在自定义子程序(函数)内部,默认变量是@_,在自定义子程序外部,默认变量是@ARGV。

布尔值判断

在Perl中,真假的判断很简单:
数值0为假,其它所有数值为真
字符串空""为假,字符串"0"为假,其它所有字符串为真(例如"00"为真)
空列表、空数组、undef、未定义的变量、数组等为假

注意,Perl中没有true和false的关键字,如果强制写true/false,它们可能会被当作字符串,而字符串为真,所以它们都表示真。

Perl的比较操作符和bash完全相反。数值比较采用符号,字符串比较采用字母。

<=>和cmp用于比较两边的数值/字符串并返回状态码-1/0/1:小于则返回-1、等于则返回0、大于则返回1

对于<=>,如果比较的双方有一方不是数值,该操作符将返回undef。

Perl支持逻辑与(and &&)、逻辑或(or ||)、逻辑非(not !)运算,还支持一个额外的//操作(v5.10+)。它们都会短路计算。其短路计算非常特别,它返回的是最后运算的表达式的值。也就是说,它有返回值,通过返回值可以判断短路计算的布尔逻辑是真还是假。

如果这个返回值对应的布尔值为真,则整个短路计算自然为真;如果这个返回值对应的布尔值为假,则整个短路计算自然为假。

所以这个返回值既保证短路计算的结果不改变,又能得到返回值。这个返回值有时候很有用,例如,可以通过逻辑或的操作来设置默认值:
$name = $myname || "FreeOA"

上面的语句中,如果$myname为真,则$name被赋值为$myname,如果$myname为假,则赋值为"FreeOA"。

但上面有一种特殊的情况,如果$myname已经定义了,且其值为数值0或字符串"0",它也返回假。这和预期有所冲突,这时可以使用//来替代||。//表示只要左边的内容已经定义了就返回真,而无论左边的内容代表的布尔值是真还是假。
$name = $myname // "FreeOA"

所以就算$myname的值为0,$name也会赋值为0而不是"FreeOA"。


流程控制

1).if、unless和三目运算逻辑

2).while和until循环

3).for和foreach循环
C语言风格的for),3个部分都可以省略,但分号不能省略:
(1).省略第一部分expr1表示不做初始化
(2).省略第二部分expr2表示不设置条件,意味着可能会无限循环
(3).省略第三部分expr3表示不做任何操作,可能也会无限循环
三个部分全都省略,将会无限循环。与上面的一样,遍历是引用被迭代的元素,而不是复制列表中被迭代的元素;迭代过程中修改第迭代变量会直接影响元素列表数据。

4).each遍历
each用来遍历hash或数组,每次迭代的过程中,都获取hash的key和value,数组的index(数值,从0开始)和元素值。

5).表达式形式的流程控制语句
Perl支持单表达式后面加流程控制符:command OPERATOR CONDITION;

改写的方式几个注意点:
(1).控制符左边只能用一个命令。除非使用do语句块
(2).for/foreach的时候,不能自定义控制变量,只能使用默认的$_
(3).while或until循环的时候,因为要退出循环,只能将退出循环的条件放进前面的命令中
print "abc",($n += 2) while $n < 10;
print "abc",($n += 2) until $n > 10;

改写的方式不能满足需求时,可以使用普通的流程结构。

6).大括号:运行一次的语句块
使用大括号包围一段语句,这些语句就属于这个语句块。这个语句块其实是一个循环块结构,只不过它只循环一次。语句块有自己的范围,例如可以将变量定义为局部变量。
perl -e '$a = 33;{my $a = "abc";print $a,"\n";}print $a,"\n";'

7).do语句块
其结构:do {...}

do语句块像是匿名函数一样,没有名称,给定一个语句块,直接执行;do语句块的返回值是最后一个执行的语句的返回值。前面说过,表达式形式的流程控制语句控制符左边只能是一个命令。例如:
print $_+1,"\n";print $_+2,"\n" if $_>3;

# 等价于下面两条语句:
print $_+1,"\n";
print $_+2,"\n" if $_>3;

使用do语句块,可以将多个语句组合并当作一个语句。例如:
do{print $a+1,"\n";print $a+2,"\n"} if $a>3;

do{}中有自己的作用域范围,可以声明属于自己范围内的局部变量。

不要把do和大括号搞混了,大括号是被解释的语句块范围的语法符号,可以用来标记自己的作用域范围。但do{}是语句,是被执行的语句块,也有自己的作用域范围。

8).last/next/redo/continue
(1).last相当于其它语言里的break关键字,用于退出当前循环块
(2).(for/foreach/while/until/执行一次的语句块都属于循环块),注意是只退出当前层次的循环,不会退出外层循环
(3).next相当于其它语言里的continue关键字,用于跳入下一次迭代。同样只作用于当前层次的循环
(4).redo用于跳转到当前循环层次的顶端,所以本次迭代中曾执行过的语句可能会再次执行
(5).continue表示每轮循环的主体执行完之后,都执行另一段代码

9).执行期语句块
Perl的代码块执行期

命令行参数和ARGV
Perl命令行的参数存放在数组ARGV(@ARGV)中,所以可以访问$ARGV[n]、遍历,甚至修改命令行参数。命令行模式下@ARGV数组是从-e表达式之后才开始收集的。

其实ARGV数组有点特别,如果参数中有被读取的文件参数,那么每开始读一个文件,这个文件就从ARGV数组中剔除。所以在程序编译期间(BEGIN语句块),ARGV数组中包含了完整的参数列表,处理第一个参数文件时,ARGV数组中包含了除此文件之外的其它参数列表,处理第二个参数文件时,ARGV数组中继续剔除这个文件参数。

例如在perl一行式命令中,"-p"选项会输出参数文件的每一行被处理后的数据,也就是说它会读取参数文件。
$ echo a1>a.txt
$ echo b2>b.txt
$ perl -pe 'BEGIN{print "in BEGIN:\n";print "@ARGV\n"}
print "in argv file: @ARGV\n";
END{print "in END:\n";print "@ARGV\n"}' a.txt b.txt

>
in BEGIN:
a.txt b.txt
in argv file: b.txt
a1
in argv file:
b2
in END:

还有几个相关的ARGV:
(1).$ARGV:该变量表示当前正在被读取的参数文件
(2).ARGV:是一个预定义的文件句柄,只对<>有效,表示当前正被<>读取的文件句柄
(3).ARGVOUT:也是一个预定义的文件句柄,表示当前正打开用于输出的文件句柄,比较常和一行式Perl程序的-i选项结合使用

perl -pE 'BEGIN{say "in BEGIN:";say "@ARGV"}say "reading me: $ARGV";' a.txt b.txt

10).三目运算符
expression ? if_true : if_false


与Shell交互

Perl可以直接执行shell中的命令方式有3种:比较常见的有反引号(或qx)、system()函数这两种。

1).反引号

反引号`COMMAND`或 qx(COMMAND)的方式是执行shell命令COMMAND后,将COMMAND的输出结果保存下来作为返回值,它可以赋值给变量,也可以插入到某个地方。就像shell中的反引号是一样的。
perl -e '$datetime = qx(date +"%F %T");print $datetime'

需要注意的是,反引号执行的结果中一般都会保留尾随换行符(除非像printf一样明确不给尾随换行符),所以在print这些变量的时候,可以不用指定"\n"。或者为了保持同意,使用chomp()或chop()函数操作变量,去掉最后一个字符。
$ perl -e '$datetime = qx(date +"%F %T");chop $datetime;    # 或chomp $datetime;print "$datetime\n";'

还可以更简便一些,直接在赋值语句上chop或chomp:
$ perl -e 'chop($datetime = qx(date +"%F %T"));print "$datetime\n";'

2).system()

第二种和shell交互的方式是system()函数。它会直接输出所执行的命令,而不是将命令的结果作为返回值。所以system()函数不应该赋值给变量,其要执行的命令部分需要使用引号包围。而对于一行式perl程序来说,直接使用引号是一个难题,所以可以考虑使用qq()、q()的方式。例如:
$ perl -e 'system q(date +"%F %T");'

如果一定想要将system()的结果赋值给变量,得到的赋值结果是system()的返回值,而它的返回值表示的是命令是否成功调用(严格地说是wait()的返回值)。
$ perl -e '$datetime = system q(date +"%F %T");print "$datetime\n";'

还可以使用shell的管道、重定向等功能:
$myname="FreeOA";
system "echo $myname >/tmp/a.txt";
system "cat <1.pl";
system 'find . -type f -name "*.pl" -print0 | xargs -0 -i ls -l {}';
system 'sleep 30 &';

system()的用法其实很复杂,更多可参考《perl调用执行外部命令》。

3).向perl命令行传递来自shell的数据

将shell的变量、shell中命令的执行结果通过变量传递给perl命令的情形。

方式一:通过环境变量$ENV{VAR}
perl程序在运行时,会自动注册一个hash结构%ENV,它收集来自shell的环境变量。例如读取shell的PATH环境变量、查看当前所使用的SHELL。
$ perl -e 'print "$ENV{PATH}\n"'
$ perl -e 'print "$ENV{SHELL}\n"'

所以想要获取shell的变量,可以先将其导出为环境变量,再从%ENV中获取。
$ export name="FreeOA"
$ perl -e 'print "$ENV{name}\n"'

方式二:将perl -e 'xxxx'的单引号拆开重组,直接将需要被shell解析的指令给shell去解释
$ name=FreeOA
$ perl -e 'print "'$name'\n"'
FreeOA

上面分成三部分'print "'是一部分,$name是一部分,它没有被任何引号包围,所以直接暴露给shell进行变量替换,'\n"'是最后一部分。

这种方式需要对shell的引号解析非常熟练,对sed来说这种写法有时候是必要的,因为这是sed和shell交互的唯一方式,但对于perl命令行来说,没有必要这样写,因为可读性太差,且很难写。

方式三:将变量放入参数位置,使其收集到ARGV数组中,然后在perl表达式中shift这些数据
$ name=FreeOA
$ age=23
$ perl -e '$name = shift;$age = shift;print "$name,$age\n"' $name $age
>
FreeOA,23

注意:上面的shift没有给定参数,所以shift会使用默认变量。由于shift期待的操作对象是一个数组(列表),且不是在子程序内部(子程序内部表示在sub语句块内),所以使用默认数组@ARGV。也就说上面的代码等价于:
$ perl -e '$name = shift @ARGV;$age = shift @ARGV;print "$name,$age\n"' $name $age

方式四:使用perl -s选项
perl的-s选项允许解析--之后的-xxx=yyy格式的开关选项。
$ perl -e 'xxxxx' -- -name=FreeOA -age=23 a.txt b.txt

从--之后的参数,它们本该会被收集到ARGV中,但如果开启了-s选项,这些参数部分如果是-xxx=yyy格式的,则被解析成perl的变量$xxx并赋值为yyy,而那些不是-xxx=yyy格式的参数则被收集到ARGV数组中:
$ perl -se 'print "ARGV: @ARGV\n";print "NAME & AGE: $name,$age\n";' -- -name="FreeOA" -age=23 abc 321
ARGV: abc 321
NAME & AGE: FreeOA,23

上面传递的参数是-name="FreeOA" -age=23 abc 321,因为开启了-s选项,所以解析了两个perl变量$name=FreeOA $age=23,另外两个参数'abc 321'则被放置到ARGV数组中。

方式五:完全用perl替代shell,在perl中反引号或qx()也支持运行shell程序
$ name=FreeOA
$ perl -e '$name = qx(echo \$name);print "name in perl: $name"'
name in perl: FreeOA

$ perl -e '$time = qx(date +"%T");print "time in perl: $time"'
time in perl: 10:21:49

注意上面qx(echo \$name)的反斜线不可少,否则$name会被perl解析,转义后才可以保留$符号被shell解析。


文件读写

重点说一行式perl是如何读、写数据的。

读取标准输入<STDIN>

<STDIN>符号表示从标准输入中读取内容,如果没有,则等待输入。<STDIN>读取到的结果中,一般来说都会自带换行符(除非发生意外,或EOF异常):
$ perl -e '$name=<STDIN>;print "Your name is: $name";'
FreeOA    # 这是我输入的内容
Your name is: FreeOA    # 这是输出的内容

因为读取的是标准输入,所以来源可以是shell的管道、输入重定向等等。例如:
$ echo "FreeOA" | perl -e '$name=<STDIN>;print "Yourname is: $name";'
Your name is: FreeOA

在比如,判断行是否为空行。
$ perl -e '$line=<STDIN>;if($line eq "\n"){print "blank line\n";} else {print "not blank: $line"}'

注意,<STDIN>每次只读取一行,遇到换行符就结束此次读取。使用while循环可以继续向后读取,或者将其放在一个需要列表的地方,也会一次性读取所有内容保存到列表中。
# 每次只读一行
$ echo -e "abc\ndef" | perl -e '$line=<STDIN>;print $line;'
abc

# 每次迭代一行
$ echo -e "abc\ndef" | perl -e 'while(<STDIN>){print}'
abc
def

# 一次性读取所有行保存在一个列表中
$ echo -e "abc\ndef" | perl -e 'print <STDIN>'
abc
def

另外需要注意的是,如果将<STDIN>显示保存在一个数组中,因为输出双引号包围的数组各元素是自带空格分隔的,所以换行符会和空格合并导致不整齐。
$ echo -e "abc\ndef" | perl -e '@arr=<STDIN>;print "@arr"'
abc
 def

解决办法是执行一下chomp()或chop()操作,然后在输出时手动指定换行符。
$ echo -e "abc\ndef" | perl -e '@arr=<STDIN>;chomp @a;print "@arr\n"'
abc def
$ echo -e "abc\ndef" | perl -e '@arr=<STDIN>;chomp(@a);print @arr,"\n"'

读取文件输入<>

使用两个尖括号符号<>表示读取来自文件的输入,例如从命令行参数@ARGV中传递文件作为输入源。这个符号被称为"钻石操作符"。例如读取并输出/etc/passwd和/etc/shadow文件,只需将它们放在表达式的后面即可:
$ perl -e 'while(<>){print $_;  # 使用默认变量}' /etc/passwd

可以直接在perl程序中指定ARGV数组让<>读取,是否还记得$ARGV变量表示的是当前正在读取的文件否?
perl -e '@ARGV=qw(/etc/passwd);while(<>){print}'

一般来说,只需要while(<>)中一次次的读完所有行就可以。但有时候会想要在循环内部继续读取下一行,这时可以单独在循环内部使用<>,表示继续读取一行。例如下面的代码表示读取每一行,但如果行首以字符串freeoa开头,则马上读取下一行。
perl -e 'while(<>){if($_ =~ /^freeoa/){<>;}...}'

文件句柄

其实<>和<STDIN>是两个特殊的文件读取方式。如果想要以最普通的方式读取文件,需要打开文件句柄,然后使用<FH>读取对应文件的数据。打开文件以供读取的方式为:
open FH, "<", "/tmp/a.log"
open $fh, "/tmp/a.log"     # 与上面等价

文件句柄名一般使用大写字母(如FH)或者直接使用变量(如$fh)。然后读取即可:
while(<FH>){...}
while(<$fh>){...}

例如:
$ perl -e 'open FH,"/etc/passwd";while(<FH>){print}'

除了打开文件句柄以供读取,还可以打开文件句柄以供写入:
open FH1,">","/tmp/a.log";   # 以覆盖写入的方式打开文件/tmp/a.log
open FH2,">>","/tmp/a.log";  # 以追加写入的方式打开文件/tmp/a.log

要写入数据到文件,直接使用print、say、printf即可,只不过需要在这些函数的第一个参数位上指定输出的目标。默认目标为STDOUT,也就是标准输出。例如:
print FH1 "hello world\n";
say   FH "hello World\n";
printf FH1 "Hello world\n";

在向文件句柄写入数据时,如果使用的是变量形式的文件句柄,那么print/say/printf可能会无法区分这个变量是文件句柄还是要输出的内容。所以应当使用{$fh}的形式避免歧义。
print {$fh} "hello world\n";


正则表达式

正则表达式的书写方式为m//或s///,前者表示匹配,后者表示替换。关于m//或s///,其中斜线可以替换为其它符号,规则如下:
(1).双斜线可以替换为任意其它对应符号,例如对称的括号类,m()、m{}、s()()、s{}{}、s<><>、s[][],相同的标点类,m!!,m%%、s!!!、s###、s%%%等等
(2).只有当m模式采用双斜线的时候,可以省略m字母,即//等价于m//
(3).如果正则表达式中出现了和分隔符相同的字符,可以转义表达式中的符号,但更建议换分隔符,例如/http:\/\//转换成m%http://%

所以要匹配/替换内容,有以下两种方式:
方式一:使用data =~ m/reg/、data =~ s/reg/rep/,可以明确指定要对data对应的内容进行正则匹配或替换
方式二:直接/reg/、s/reg/rep/,因为省略了参数,所以使用默认参数变量,它等价于$_ =~ m/reg/、$_ =~ s/reg/rep/,也就是对$_保存的内容进行正则匹配/替换

s///替换操作是原地生效的,会直接影响原始数据。其返回值是替换成功的次数,没有替换成功返回值为0。所以,s///自身可以当作布尔值来使用。如果想要直接输出替换后得到的字符串,可以加上r修饰符(v5.14+),这时它不再返回替换成功的次数,而是直接返回替换后的数据。



该文章最后由 阿炯 于 2024-04-22 09:50:16 更新,目前是第 2 版。