实用摘要和报表语言 第 1 章 实用摘要和报表语言 1.1 什么是 Perl “懒惰、性急和傲慢。伟大的 Perl 程序员拥有这些优点。” ——Larry Wall Perl 是一种多用途的开源(免费软件)解释型语言,由称为 Perl Porters 的核心开发团队维护 和改进。它主要用作脚本语言,并且运行在众多平台上。尽管 Perl 最初是为 UNIX 操作系统设计 的,但是它以其可移植性以及现在与大多数操作系统捆绑在一起而著名。这些操作系统包括 RedHat Linux、Solaris、FreeBSD、Macintosh 等。由于 Perl 的通用性,它通常称为程序设计语言的“瑞士 军刀”。 Larry Wall 编写 Perl 语言来管理散布在网络中的日志文件和报表。依据 Wikipedia.org 上面的 说法:“Perl 最初命名为‘Pearl’,它出自于 Gospel of Matthew(《圣经马太福音》)中的‘Parable of the Pearl’(珍珠的寓言)。”该寓言的简要内容如下:一位商人寻找珍珠,他找到一颗如此贵重、 漂亮的珍珠,以至于他情愿倾其所有来购买它。最后,他甚至比以往更富有。无论你怎样解释这则 寓言,它都具有非常积极的寓意。 但是在 1987 年推出其官方发布版时,删去了“Pearl”中的字母“a”,自此将该语言称为“Perl”, 后来又把它称为实用摘要和报表语言(Practical Extraction and Report Language),还有一些人把它 称为病态折衷垃圾列表器(Pathologically Eclectic Rubbish Lister)。你很快将会看到,Perl 确实远 远胜过实用报表语言或折衷垃圾列表器。Perl 使编程简单、灵活和快速,因此使用它的人都会喜欢 它。其用户从经验丰富的程序员一直到只具有很少计算机知识的初学者,而且用户的数量也在飞速 增长。 Perl 传承自 UNIX。Perl 脚本在功能上类似于 UNIX awk、sed、shell 脚本和 C 程序。shell 脚 本主要由 UNIX 命令构成,Perl 脚本则不然。sed 和 awk 用于编辑和报告文件,但是 Perl 无需执行 文件即可工作。C 没有 shell、sed 和 awk 的任何模式匹配和通配元字符,而 Perl 却有扩展字符集。 Perl 最初用于操作文件中的文本、从文件中提取数据和编写报表,但经过不断的发展,它现在可以 操作进程、执行网络任务、处理 Web 页面、与数据库通信,以及分析科学数据。Perl 确实是程序设 计语言的“瑞士军刀”,任何人都可以使用它。 当指语言时,将 Perl 拼写为“Perl”;当指解释器时,则拼写为“Perl”。 第 1 章 本书中的示例是在 Solaris、Linux、Macintosh UNIX 和 Win32 系统上创建的。 Perl 通常与 O’Reilly Media 的商标即骆驼标志相关联,O’Reilly Media 出版了第一本关于 Perl 的 书籍,书名是《Programming Perl》,作者是 Larry Wall 和 Randal Schwartz,该书称为“Camel Book” (骆驼书)。 1.2 什么是解释语言 在编写 Perl 程序时,读者需要准备两样工具:一个文本编辑器和一个 Perl 解释器。读者 可以从许多 Web 站点上下载到后者,譬如 Perl.org、cpan.org 以及 activestate.com。和 C++、 Java 等编译语言不同,读者无需在执行程序之前将其编译成机器能理解的代码。Perl 解释器 会代劳这一切,它能完成程序的编译、解释和执行工作。像 Perl 这样的解释语言有很多优点, 首先,它能运行在几乎任何平台上;其次,它相对易于学习;最后,它的速度很快,并且灵活 度很高。 像 Python、Java 和 Perl 等解释语言都用到了中间代码,以便将编译和解释这两个过程 联系起来。它首先会把用户提供的代码编译成一种内部压缩格式,称之为字节码(bytecode) 或连接代码(threaded code),然后交给解释器执行。在运行 Perl 程序时,读者会观察到两 个阶段:编译阶段和执行阶段,后一阶段才会生成程序结果。如果程序中存在语法错误,譬 如关键字拼写错误或缺了引号,编译器就会报错。即使通过了编译,程序在开始执行时也可 能出现其他问题。成功通过上述两个阶段后,才可以做其他事情,譬如改进程序或者提升程 序性能等。 此外,解释器还提供了许多命令行开关项(选项)以控制其行为模式,譬如语法检查、发送警 告信息、遍历文件、执行语句、打开调试器等。本书后面章节将详细介绍这些开关项。 1.3 Perl 的用户 由于 Perl 内建函数能方便地处理进程和文件,再加上它是可移植的(即能运行在多种不同的平 台上),因此 Perl 深受那些需要同时监控多种不同系统的系统管理员的欢迎。万维网(World Wide Web)的迅速发展大大提高了人们对 Perl 的兴趣。它现在已经成为最流行的 CGI 脚本编写语言,负 责动态生成 Web 页面。即使现在出现了像 ASP.NET 之类的其他专用 Web 页面处理语言,Perl 还是 一如既往地受到系统管理员、数据库管理员、科学家、遗传学家以及所有需要从文件里收集和处理 数据的用户的欢迎。 任何人都可以使用 Perl。不过倘若读者已经熟悉 UNIX Shell 脚本、C 语言或其他从 C 派生的 语言(例如 C++ 和 Java)的话,学起 Perl 来会更容易一些。对上述用户而言,转到 Perl 语言的难 度相对较小。而对于那些缺乏编程经验的读者,则需要多花些时间来学习。不过只要学会了 Perl, 您可能就没有必要去使用其他语言了。 如果读者熟悉像 awk、grep、sed 和 tr 这样的 UNIX 工具,就知道它们的语法其实各不相同,其 选项和参数处理方式也不同,甚至不同工具所遵循的规则都互不相同。如果读者是一位 Shell 程序 员,往往需要经历一连串艰苦的学习过程,学会使用各种应用工具、shell 元字符、正则表达式元字实用摘要和报表语言 符和不计其数的引用。此外,Shell 程序功能有限,速度还很慢。用户若要执行更复杂的算术任务、 进程间通信任务或二进制数据处理任务,则不得不使用更高级的编程语言,譬如 C、C++ 或 Java。 了解 C 的读者都应当知道,要想用它在文件里搜寻模式,或者与操作系统交互处理文件或命令的话, 那可不是什么简单的事情。 Perl 集中了 Shell 程序、C 语言以及 UNIX 中 awk、grep、sed 和 tr 工具的优秀特性。由于它速 度很快,且不受特定容量数据块的限制,因此很多系统管理员和数据库管理员都从传统的 Shell 脚 本转向使用 Perl。C++ 和 Java 程序员还可以利用 Perl 5 中新增的面向对象特性,譬如创建可重复使 用的可扩展模块。Perl 现在可以由其他语言生成,而其他语言也可嵌入到 Perl 中。所有使用 Perl 的 用户在做每件事时都应当牢记这样一句话:“道路往往不止一条”(http://www.oreilly.com/catalog/ opensources/book/larry.html)。 在开始编写脚本时,读者并不需要了解有关 Perl 的所有内容,甚至可以不是一名程序员。本书 将为读者学习 Perl 打下良好的基础,帮助读者发现其诸多功能和优点。然后读者便可自行决定需要 深入多少。如果不出意外,大家一定会发现 Perl 的乐趣所在。 1.3.1 Perl 的版本 Perl 有很多版本,其中两个主要版本是 Perl 4 和 Perl 5。Perl 4 的最后一个版本是 Perl 4.036, 发布于很久之前的 1992 年。Perl 5.000 同样显得很古老,它发布于 1994 年秋天,其源代码经过彻 底重写,对语言进行了优化,并引入了对象以及很多其他特性。尽管变化巨大,Perl 5 仍然保持着 与以前版本的兼容性。本书示例都在这两个版本下进行了测试,若有区别之处,本书都将予以指明。 在编写本书时,Perl 的最新版本是 Perl 5.8.8。下一代的 Perl 6 将再次对 Perl 进行重新设计,目前 距离其官方发布还遥遥无期。尽管 Perl 6 可能会引入新的特性,但读者从本书学到的基本语法将永 远保持不变。 1.3.2 什么是 Perl 6 “Perl 5 是我本人对 Perl 进行的改写。我希望 Perl 6 将由全社区完成改写,并能属于 整个社区。” —— Larry Wall, State of the Onion Speech, TPC4 Perl 6 在本质上与 Perl 5 类似,所不同的是它加入了许多新的特性,但其基本语法、特性和目 标都将维持不变。如果读者对 Perl 已经有所了解,那么这些知识都不用推倒重来。如果按照本书内 容学习 Perl,读者便能为 Perl 6 的正式发布做好准备。学习 Perl 6 的过程就好像是从美式英语转向 澳大利亚英语,而不像从英语转向汉语那样麻烦。 如需获取有关 Perl 6 进展情况的进一步消息,请读者访问: http://www.perl.com/pub/a/2006/01/12/what_is_perl_6.html?page=2 此外,如需了解 Larry Wall 生平和 Perl 的历史,请访问: http://www.softpanorama.org/People/Wall/index.shtml#Perl_history 第 1 章 1.4 如何获得 Perl 读者可以通过多种途径获得 Perl 发布包,其中最常用的是 Perl 综合文献网(Comprehensive Perl Archive Network,CPAN)。(www.cpan.org。) 如需了解自己的系统平台可以运行哪种 Perl 发布包,可访问 http://www.cpan.org/port。读者 图 1-1 Perl 6 开发 Web 页面 图 1-2 CPAN 中的二进制包发布页面实用摘要和报表语言 若想方便快速地完成 Perl 的安装,则可以使用 ActivePerl 组件。它是基于标准 Perl 源代码构建的 一种完整的自安装包,支持 Windows、Max OS X、Linux、Solaris、AIX 和 HP-UX 等平台。它由 ActiveState 网站(www.activestate.com)在线发布。完整的 ActivePerl 安装包包含一份二进制形式 的 Perl 核心包,以及所有相关在线文档。 下面几个 Web 站点将有助于读者了解更多有关 Perl 的信息。 ・ 由 O’Reilly Media 公司负责维护的 Perl 官方主页:www.Perl.com。 ・ 由 Perl 基金会运营的 Perl 目录网站(Perl Directory),该网站的目标是成为囊括一切 Perl 相 图 1-3 含有资源链接的 Perl 目录页面 图 1-4 Perl 官方主页(O’Reilly Media 公司负责运营) 第 1 章 关信息的中央目录:www.perl.org。 ・ Perl 综合文献网,在这里读者也能找到有关 Perl 的所有信息:http://www.cpan.org。 ・ 读者可以在这个网站里找到有关 Perl 开发的常用工具:http://www.activestate.com Perl 的可用版本 如需获得有关 Perl 版本、当前二进制版本日期、补丁或其他版权信息的话,请输入下列命令, 参见示例 1.1 所示(其中 $ 符号是 Shell 的提示符)。 示例 1.1 $ perl -v 1 This is perl, v5.8.8 built for MSWin32-x86-multi-thread (with 50 registered patches, see perl -V for more detail) 2 Copyright 1987-2006, Larry Wall 3 Binary build 820 [274739] provided by ActiveState http://www.ActiveState.com Built Jan 23 2007 15:57:46 4 Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using "man perl" or "perldoc perl". If you have access to the Internet, point your browser at http://www.perl.org/, the Perl Home Page. This is perl, v5.8.8 built for MSWin32-x86-multi-thread (with 1 registered patch, see perl -V for more detail) 5 Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5.0 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using man perl or perldoc perl. If you have access to the Internet, point your browser to www.perl.com/, the Perl home page. ------------------------------------------------------------- 6 perl -v This is perl, v5.8.3 built for sun4-solaris-thread-multi (with 8 registered patches, see perl -V for more detail) Copyright 1987-2003, Larry Wall Binary build 809 provided by ActiveState Corp. http://www.ActiveState.com ActiveState is a division of Sophos. Built Feb 3 2004 00:32:12 Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in实用摘要和报表语言 the Perl 5 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using 'man perl' or 'perldoc perl'. If you have access to the Internet, point your browser at http://www.perl.com/, the Perl Home Page. 解释: 1. 示例所用 Perl 的版本是 5.8.8,是由 ActiveState 提供的 Windows 版。 2. Perl 的开发者 Larry Wall 拥有上述代码的版权。 3. 上述内容来自 ActiveState。 4. 读者可在遵守 Artistic Licence 或 GNU 相关条款的前提下复制 Perl。Perl 的发布过程遵 循自由软件基金会(Free Software Foundation)制定的 GNU 许可证,也就是说,Perl 是免费的。 5. 对 Solaris(UNIX)而言,Perl 的最新版本是 5.8.3。 1.5 什么是 CPAN CPAN 是通向所有 Perl 相关知识的大门,其全称是 Perl 综合文献网(Comprehensive Perl Archive Network)。该 Web 站点拥有读者所需要的所有免费 Perl 资料,包括文档、FAQ、模块和脚本、二 进制发布包与源代码、以及相关通知等。全世界有多个 CPAN 的镜像站点,读者可从如下 URL 获 得距离最近的镜像: www.perl.com/CPAN www.cpan.org 如果读者想要为手头工作寻找某个 Perl 模块的话,不妨可以去 CPAN 看看。CPAN 的搜索引擎 能在大量不同的分类中搜索所需模块。有关模块的知识将在本书第 12 章中予以介绍。 图 1-5 一个内容全面的 Perl 模块索引 第 1 章 1.6 Perl 文档 1.6.1 Perl 的 man 页面 标准的 Perl 发布包中包含了一份完整的在线文档,称为 man 页面,能为所有标准的 Perl 实用 工具提供帮助信息(这个名字来自于 UNIX 系统中的 man) 。Perl 的 man 页面被划分为许多类别。 如果读者在命令行提示符下输入下列内容: man perl 就会得到所有类别的列表信息。因此,如果读者想要获得有关如何使用 Perl 正则表达式的帮助 信息,可以输入: man perlre 如果还需要获得有关子例程的帮助,则可键入: man perlsub 下面列出了所有 Perl 的类别,其中后面的部分只适用于在线参考手册: perlbot 面向对象的技巧和示例 perldebug 调试 perldiag 诊断信息 perldsc 数据结构:介绍信息 perlform 格式 perlfunc 内建函数 perlipc 进程间通信 perllol 数据结构:有关列表(list)的列表 perlmod 模块 perlobj 对象 perlop 运算符和优先级顺序 perlpod 无格式的旧文档 perlre 正则表达式 perlref 引用 perlsock 套接字支持扩展 perlstyle 样式向导 perlsub 子例程 perltie 隐藏于简单变量后的对象 perltrap 捕获错误 perlvar 预定义变量 如需了解具体库模块的用途,读者可使用 perldoc 命令查询相关文档。例如,如果要了解 CGI. pm 模块,可在命令行输入: man 页面的意思是用户手册 manual 页面。——译者注实用摘要和报表语言 perldoc CGI 就能显示有关 CGI.pm 模块的文档。如果键入: perldoc English 则会显示有关 English.pm 模块的帮助文档。 如需获得某个特定 Perl 函数的文档,读者可以键入 perldoc -f 和函数名。例如,为了获得有 关 localtime 函数的帮助信息,可在命令行提示符下面执行如下命令(执行前可能需要设置相应的 UNIX/DOS 路径)。 perldoc -f localtime localtime EXPR localtime Converts a time as returned by the time function to a 9-element list with the time analyzed for the local time zone. Typically used as follows: # 0 1 2 3 4 5 6 7 8 ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); 1.6.2 HTML 文档 当您从 ActivePerl 网站(即从 ActiveState.com)下载 Perl 的时候,会发现该网站提供了出色 的帮助文档。由图 1-6 可见,从这里可以链接到有关 Perl 的几乎每一项内容。 1.7 读者应当学到的知识 1. 谁编写了 Perl ? 2. Perl 这个词代表什么? 3. “开源”的意思是什么? 4. Perl 的当前版本是多少? 图 1-6 ActiveState 提供的 HTML Perl 文档10 第 1 章 5. 使用 Perl 的目标是什么? 6. 什么是解释器? 7. 从哪里能得到 Perl ? 8. 什么是 ActivePerl ? 9. 什么是 CPAN ? 10. 从哪里能得到帮助文档? 11. 如何获得特定 Perl 函数的帮助文档? 1.8 下章简介 在下一章,读者将学习如何创建一个简单的 Perl 脚本并执行它,以及 Perl 脚本的内部结构,包 括 Perl 的语法、语句和注释。此外,读者还将学习如何检查语法错误,以及如何在命令行环境里使 用多个选项执行 Perl 程序。11Perl 快速入门 第 2 章 Perl 快速入门 2.1 快速入门和速查手册 2.1.1 给程序员的提示 如果读者已经拥有其他语言上的编程经验,如 Visual Basic、C/C++、Java、ASP 或 PHP 等; 并熟悉基本的编程概念如变量、循环、条件语句、函数的话,表 2.1 将让您一览 Perl 语言的结构和 语法。 在每个部分的结尾处,都会为读者提供该语法结构的相应章节号,并附有一段简短扼要的 Perl 程序示例,以显示如何使用该结构。 2.1.2 给非程序员的提示 如果您对编程一无所知,请跳过本章,直接阅读第 5 章。本章内容可作为读者以后学习过程中 的速查手册。 2.1.3 Perl 语法和结构 表 2-1 Perl 语法和结构 脚本文件 Perl 脚本可通过文本编辑器来创建。一般情况下,用户无需为脚本提供特别的文件扩展名,除 非执行该脚本的应用程序要求提供。譬如,如果是作为 Apache 容器中的 cgi 程序来执行脚本的话, 就必须为脚本文件名提供 .pl 或 .cgi 扩展名 自由格式 Perl 是一种格式自由的语言。一个 Perl 语句必须由一个分号结尾,但它可以出现在程序的任意 位置,亦可拆分为多行内容 注释 Perl 中的注释由 # 开头。解释器会在执行程序时自动忽略注释。注释可以出现在任何位置,但 不能拆分为多行内容 EXAMPLE print "Hello, world"; # This is a comment # And this is a comment12 第 2 章 (续) 打印输出 print 和 printf 是 Perl 的两个内建函数,用于显示输出内容。print 函数的参数是一系列由逗号隔 开的字符串或数字。printf 函数则类似于 C 语言中的 printf() 函数,它能够归整输出内容的格式。 用户无需在其参数两边提供括号(参阅第 3 章) print value, value , value ; printf ( string format [ , mixed args [ , mixed ... ]] ); EXAMPLE print "Hello, world\n"; print "Hello,", " world\n"; print ("It's such a perfect day!\n"); #Parens optional;. print "The the date and time are: ", localtime, "\n"; printf "Hello, world\n"; printf("Meet %s%:Age 5d%:Salary \$10.2f\n", "John", 40, 55000); (参阅第 4 章) 数据类型 / 变量 Perl 变量支持三种基本的数据类型:标量、数组和关联数组(即散列 Hash) Perl 变量无需在使用前声明 变量名以一个特殊字符(funny letter)开头,后面跟有任意数目的字母,包括下划线。其中,特 殊字符表明该变量的类型和上下文语境。位于特殊字符后面的字母都是大小写敏感的。如果变量是 以字母开头的话,其后便可跟随任意数目的字母(下划线等同于一个字母)和 / 或数字;如果变量 不是以字母开头的话,则后面只能跟有一个字母(参阅第 5 章) 标量 标量是一种变量,只能保存单个值、单个字符串或单个数字。标量名必须以美元符号“$”开头。 标量上下文则负责表明当前正在使用单个值的情况 EXAMPLE $first_name = "Melanie"; $last_name = "Quigley"; $salary = 125000.00; print $first_name, $last_name, $salary; 数组 数组是一组有序排列的标量,如字符串和 / 或数字。数组中的各个元素由从 0 开始的整数来索 引。数组变量名以“@”符号开头 @names = ( "Jessica", "Michelle", "Linda" ); print "$names"; #Prints the array with elements separated by a space print "$names[0] and $names[2]"; #Prints "Jessica" and "Linda" print "$names[-1]\n"; # Prints "Linda" $names[3]="Nicole"; # Assign a new value as the 4th element 下面是一些常用的内建函数: pop 移除最后一个元素 push 把新元素添加到数组末尾 shift 移除第一个元素 unshift 把新元素添加到数组开头 splice 在数组指定位置添加或移除数组元素 sort 对数组元素进行排序 散列 关联数组(associative array),又称为散列(hash),是一组未经排序的键 / 值对(key-value pair),并通过字符串进行索引。散列变量名以“%”号开头(请注意,若位于单引号或双引号中的 话,%符号将不会解析)13Perl 快速入门 (续) 散列 EXAMPLE %employee = ( "Name" => "Jessica Savage", "Phone" => "(925) 555-1274", "Position" => "CEO" ); print "$employee{"Name"}; # Print a value $employee{"SSN"}="999-333-2345"; # Assign a key/value 下面是一些常用的内建函数: keys 检索散列数组中的所有键 values 检索散列数组中的所有值 each 检索散列数组中的某个键 / 值对 delete 删除某个键 / 值对 预定义变量 Perl 提供了大量的预定义变量。下面列举了常用的一些预定义变量: $_ 在执行输入和模式搜索操作时使用的默认空格变量 $. 文件中最后处理的当前行号 $@ 由最近一个 eval() 运算符提供的 Perl 语法报错信息 $! 获取当前错误信息值,常用于 die 命令 $0 含有正在执行的程序名 $$ 正在执行本脚本的 Perl 进程号 $PERL_VERSION / $^V Perl 解释器的版本、子版本和修订版本信息 @ARGV 含有命令行参数 ARGV 一个特殊的文件句柄,用于遍历 @ ARGV 中出现的所有文件名 @INC 库文件的搜索路径 @_ 在子例程中,@_ 变量含有传给该子例程的变量内容 %ENV 关联数组型变量 %ENV 含有当前环境信息 %SIG 关联数组型变量 %SIG 含有指向信号内容的句柄 常量(文本值) 固定不变的值,一旦设置就不能再更改。典型的常量包括 PI,或一英里长度的英尺数。这些值 都是从不变化的。用户可借助 constant 保留字来定义常量,这里给出示例如下: EXAMPLE use constant BUFFER_SIZE => 4096; use constant PI => 4 * atan2 1, 1; use constant DEBUGGING => 0; use contstant ISBN => "0-13-028251-0"; PI=6; # Cannot modify PI; produces an error. 数字 Perl 支持整数(十进制、八进制以及十六进制整数)、浮点数、科学计数法、布尔型(Boolean) 以及 null(空值) EXAMPLE $year = 2006; # integer $mode = 0775; # octal number in base 8 $product_price = 29.95; # floating point number in base 10 $favorite_color = 0x33CC99; # integer in base 16 (hexadecimal) $distance_to_moon=3.844e+5; # floating point in scientific notation $bits = 0b10110110; # binary number14 第 2 章 (续) 字符串和引号 所谓字符串,是位于引号内的一组字节(字符)。 如果要用引号来标识字符串的话,必须保证这些引号是成对出现的 ;譬如“String”或者‘String’。在双 引号中,标量、数组变量($x、@name)和反斜杠序列(如 \n、\t 和 \”等)都是可解释的。反斜杠可用于 标识引号本身。单引号可嵌入到一对双引号之间,双引号也可嵌入到一对单引号之间。所谓 here 文档(here document),则是指一段嵌入于用户自定义标签中的文本块,其中第一个标签必须以 << 开头。 下面显示了引用一个字符串的三种不同方式: 单引号 :'It rains in Spain'; 双引号 :"It rains in Spain"; Here 文档: print< 字符串相等 eq、ne、cmp 算术大小关系 >、>=、<、<= 字符串大小关系 gt、ge、lt、le 范围运算符 5 .. 10 # 范围是 5 至 10 之间,逐个递增 逻辑运算符 &&、and、||、or、XOR、xor、! 自动递增 / 递减 ++、-- 文件运算符 -r、-w、-x、-o、-e、-z、-s、-f、-d、-l,等等 位运算符 ~、&、|、^、<<、>> 字符串连接 . 字符串复制 x 代数运算符 *、/、-、+、% 模式匹配 =~、!~ EXAMPLE print "\nArithmetic Operators\n"; print ((3+2) * (5-3)/2); print "\nString Operators\n"; # Concatenation print "\tTommy" . ' ' . "Savage"; print "\nComparison Operators\n"; print 5>=3 , "\n"; print 47==23 , "\n";15Perl 快速入门 (续) 运算符 print "\nLogical Operators\n"; $a > $b && $b < 100 $answer eq "yes" || $money == 200 print "\nCombined Assignment Operators\n"; $a = 47; $a += 3; # short for $a = $a + 3 $a++; # autoincrement print $a; # Prints 51 print "\nPattern Matching Operators\n" $color = "green"; print if $color =~ /^gr/; # $color matches a pattern # starting with 'gr' $answer = "Yes"; print if $answer !~ /[Yy]/; # $answer matches a pattern # containing 'Y' or 'y' 条件判断 基本的 if 语句能够判断小括号内的表达式取值,如果值为 true,则执行其后跟随的表达式。 if 语句 if ( 表达式 ) { 语句; } EXAMPLE if ( $a == $b ){ print "$a is equal to $b"; } if/else 语句 if/else 实现了双向判断。如果 if 后面表达式条件为 true,则执行其后的语句 ;否 则执行 else 后面的语句。 if ( 表达式 ) { 语句 ; } else { 语句 ; } EXAMPLE $coin_toss = int (rand(2 )) + 1; # Generate a random # number between 1 and 2 if( $coin_toss == 1 ) { print "You tossed HEAD\n"; } else { print "You tossed TAIL\n"; } if/elsif 语句 if/elsif/else 提供了多路分支选择功能。如果 if 后面的表达式值不是 true,则会依 次判断其后每个 elsif 的条件,直到其中某个条件值为 true;如果没有一个为 true 的话,则执行最后的 else 语句。 if ( 表达式 ) { 语句; elsif(表达式){ 语句; } elsif(表达式){ 语句; else{ 语句; }16 第 2 章 (续) 条件判断 EXAMPLE # 1 is Monday, 7 Sunday $day_of_week = int(rand(7)) + 1; print "Today is: $day_of_week\n"; if ( $day_of_week >=1 && $day_of_week <=4 ) { print "Business hours are from 9 am to 9 pm\n"; } elsif ( $day_of_week == 5) { print "Business hours are from 9 am to 6 pm\n"; } else { print "We are closed on weekends\n"; } 条件运算符 和 C/C++ 类似,Perl 也为 if/else 语法结构提供了相应的简化模式,该模式拥有两个运算符和三 个操作数(因此又称为三元运算符)。如果条件值为 true,则执行紧跟在问号后面的语句;如果是 false,则执行冒号后面的语句。其格式为: ( 条件 ) ? 条件为 true 时执行的语句 : 条件为 false 时执行的语句 EXAMPLE $coin_toss = int (rand(2 )) + 1; # Generate a random number # between 1 and 2 print ($coin_toss == 1 ? "You tossed HEAD\n" : "You tossed TAIL\n" ); 循环 循环结构能够指定一段重复执行多次的代码。Perl 支持多种不同类型的循环:while 循环、do- while 循环、for 循环以及 foreach 循环。 while/until 循环 while 循环: while 后面跟随一个以小括号包围起来的表达式,以及一段执行语句。当表 达式取值为 true 时,便继续执行循环内容。其语法结构为: while(条件表达式){ 代码段 A } EXAMPLE $count=0; # Initial value while ($count < 10 ){ # Test print $n; $count++; # Increment value } until 循环: until 后面跟随一个以小括号包围起来的表达式,以及一段执行语句。当表 达式取值为 false 时,继续执行循环内容。其语法结构为: until(条件表达式){ 代码段 A } EXAMPLE $count=0; # Initial value until ($count == 10 ){ # Test print $n; $count++; # Increment value } do-while 循环 do-while 循环类似于 while 循环,所不同的是它是在循环体末尾检查循环表 达式的,而不是在开头检查。这就保证了循环体至少能执行一次。 其代码结构为: do{代码段 A }while(表达式); EXAMPLE $count=0; # Initial value do { print "$n "; $count++; # Increment value while ($count < 10 ); # Test }17Perl 快速入门 (续) 循环 for 循环 for 循环需要判断三个条件表达式的取值,各表达式之间以分号隔开。第一个表达 式负责初始化变量,在整个循环过程中只调用一次。第二个表达式负责判断条件值 是否为 true,如果是 true 的话便执行循环体;否则就退出循环。当循环体执行完毕 后,控制权便转移到第三个表达式,由它负责更新待检测变量的值。然后,再由第 二个表达式进行下一次判断,如此反复。其代码结构如下所示: for( 初始化鍬量;条件表达式;自加 / 自减 ){ 代嗎段 } EXAMPLE for( $count = 0; $count < 10; $count = $count + 1 ) { print "$count\n"; } foreach 循环 foreach 循环仅用于逐个遍历列表(list)内容。 foreach$ 列表项(@ 列表){ print$ 列表项,"\n"; } EXAMPLE @dessert = ( "ice cream", "cake", "pudding", "fruit"); foreach $choice (@dessert){ # Iterates through each element of the array echo "Dessert choice is: $choice\n"; } 循环控制 last 语句可用于从循环体中跳出一个循环。next 语句可用于跳过当前这次循 环的剩余内容,直接从头开始下一轮循环。 EXAMPLE $n=0; while( $n < 10 ){ print $n; if ($n == 3){ last; # Break out of loop } $n++; } print "Out of the loop.
"; EXAMPLE for($n=0; $n<10; $n++){ if ($n == 3){ next; # Start at top of loop; # skip remaining statements in block } echo "\$n = $n
"; } print "Out of the loop.
"; 子例程 / 函数 函数(function)是一组能完成某项任务的代码体,并供程序其他部分调用。用户可以通过参数 将数据传送给这个函数。函数可以有返回值,也可以不返回任何值。任何合法的 Perl 代码均可出 现在函数体的定义中。定义于函数外面的变量在函数体内也同样可用。而 my 函数则能把指定的变 量局部化。 (参阅第 11 章)。 sub function_name{ 函数体 }18 第 2 章 (续) 子例程 / 函数 EXAMPLE sub greetings() { print "Welcome to Perl!
"; # Function definition } &greetings; # Function call greetings(); # Function call EXAMPLE $my_year = 2000; if ( is_leap_year( $my_year ) ) { # Call function with an argument print "$my_year is a leap year\n"; } else { print "$my_year is not a leap year"; } sub is_leap_year { # Function definition my $year = shift(@_); # Shift off the year from # the parameter list, @_ return ((($year % 4 == 0) && ($year % 100 != 0)) || ($year % 400 == 0)) ? 1 : 0; # What is returned from the function } 文件处理 Perl 提供了 open 函数用于打开文件,也提供了 pipe,用于读写、追加文件内容。其中,open 函 数的参数包括一个用户自定义的文件句柄(一般表现为一串大写字符),以及一个含有文件路径和 读 / 写 / 追加标志的字符串(详见第 10 章)。 EXAMPLE To open a file for reading: open(FH, "filename"); # Opens "filename" for writing. # Creates or truncates file. To open a file for appending: open(FH, ">>filename"); # Opens "filename" for appending. # Creates or appends to file. To open a file for reading and writing: open(FH, "+filename"); # Opens "filename" for write, then read. To close a file: close(FH);19Perl 快速入门 (续) 文件处理 To read from a file: while(){ print; } # Read one line at a time from file. @lines = ; # Slurp all lines into an array. print "@lines\n"; To write to a file: open(FH, ">file") or die "Can't open file: $!\n"; print FH "This line is written to the file just opened.\n"; print FH "And this line is also written to the file just opened.\n"; EXAMPLE To Test File Attributes print "File is readable, writeable, and executable\n" if -r $file and -w _ and -x _; # Is it readble, writeable, and executable? print "File was last modified ",-M $file, " days ago.\n"; # When was it last modified? print "File is a directory.\n " if -d $file; # Is it a directory? 管道(Pipe) 管道用于把系统命令输出内容作为输入流传送给 Perl,或者将 Perl 的输出内容转发给系统命令 以作为其输入。管道又称作过滤器(filter)。用户必须通过 open 系统调用来使用管道。该系统调 用接受两个参数:一个用户自定义的句柄和一个操作系统命令,并需在操作系统命令的前面或者后 面加上一个“|”符号。如果“|”符号出现在命令之前,则表示该命令将把 Perl 的输出作为输入内 容;否则表示 Perl 将读取该命令的输出内容。也就是说,如果命令后面带有“|”,就表示 Perl 将 从管道中读取输入内容;否则说明 Perl 将把输出内容写入到管道中去。 (详见第 10 章) EXAMPLE Input filter open(F, " ls |") or die; # Open a pipe to read from while(){ print ; } # Prints list of UNIX files Output filer open(SORT, "| sort" ) or die; # Open pipe to write to print SORT "dogs\ncats\nbirds\n" # Sorts birds, cats, dogs on separate lines. 正则表达式。所谓正则表达式,是由斜杠圈起的一组字符集合。它们可用于在文本中匹配指定模 式,并进行相应替换操作。一直以来,Perl 都因为其优秀的模式匹配机制而闻名于世。(参阅第 8 章。) 表 2-2 一些正则表达式元字符 元字符 表达含义 ^ 匹配行首 $ 匹配行尾 a.c 匹配一个 a,后面任意单个字符,再后面是一个 c 的情况20 第 2 章 元字符 表达含义 [abc] 匹配 a 或者 b 或者 c 的情况 [^abc] 匹配字符既不是 a 又不是 b 也不是 c 的情况 [0-9] 匹配位于 0 到 9 之间的单个数字 ab*c 匹配一个 a 后面跟 0 到多个 b,最后是一个 c 的情况 ab+c 匹配一个 a 后面跟 1 到多个 b,最后是一个 c 的情况 ab?c 匹配一个 a 后面跟 0 到 1 个 b,最后是一个 c 的情况 (ab)+c 匹配 1 到多个 ab 后面跟着 1 个 c 的情况 (ab)(c) 捕获 ab 并将其值赋予变量 $1,同时捕获 c 值并赋予 $2 EXAMPLE $_ = "looking for a needle in a haystack"; print if /needle/; If $_contains needle, the string is printed. $_ = "looking for a needle in a haystack"; # Using regular expression metacharacters print if /^[Nn]..dle/; # characters and "dle". $str = "I am feeling blue, blue, blue..." $str =~ s/blue/upbeat/; # Substitute first occurrence of "blue" with "upbeat" print $str; I am feeling upbeat, blue, blue... $str="I am feeling BLue, BLUE..."; $str = ~ s/blue/upbeat/ig; # Ignore case, global substitution print $str; I am feeling upbeat, upbeat... $str = "Peace and War"; $str =~ s/(Peace) and (War)/$2, $1/i; # $1 gets 'Peace', $2 gets' War' print $str; War and Peace. $str = "He gave me 5 dollars.\n" s/5/6*7/e; # Rather than string substitution, evaluate replacement side print $str; He gave me 42 dollars." 在命令行下传送参数。Perl 通过 @ARGV 数组保存了命令行提供的参数内容。如果用到了 ARGV 文件句柄,则这些命令行参数将被视为文件;否则就视为是来自命令行环境的字符串,供脚 本直接使用(详见第 10 章)。 EXAMPLE $ perlscript filea fileb filec (In Script) (续)21Perl 快速入门 print "@ARGV\n"; # lists arguments: filea fileb filec while(){ # filehandle ARGV -- arguments treated as files print; # Print each line of every file listed in @ARGV } 引用,指针。 Perl 引用(reference)又称作是指针(pointer)。它在本质上是一个标量型变 量,负责保存另一个变量的地址。在创建指针时,用户需要使用反斜杠运算符(详见第 13 章)。 EXAMPLE # Create variables $age = 25; @siblings = qw("Nick", "Chet", "Susan","Dolly"); %home = ("owner" => "Bank of America", "price" => "negotiable", "style" => "Saltbox", ); # Create pointer $pointer1 = \$age; # Create pointer to scalar $pointer2 = \@siblings; # Create pointer to array $pointer3 = \%home; # Create pointer to hash $pointer4 = [ qw(red yellow blue green) ]; # Create anonymous array $pointer5 = { "Me" => "Maine", "Mt" => "Montana", "Fl" => "Florida" }; # Create anonymous hash # Dereference pointer print $$pointer1; # Dereference pointer to scalar; prints: 25 print @$pointer2; # Dereference pointer to array; # prints: Nick Chet Susan Dolly print %$pointer3; # Dereference pointer to hash; # prints: styleSaltboxpricenegotiableownerBank of America print $pointer2->[1]; # prints "Chet" print $pointer3->{"style"}; # prints "Saltbox" print @{$pointer4}; # prints elements of anonymous array 对象。Perl 中的对象是一种特殊类型的变量。在 Perl 中,一个类代表着含有一组变量(属性) 与函数(方法)的一个包。Perl 没有提供专门的 class 关键字。其中,属性就是能够描述这个对象 的变量;而方法则是一种特殊的函数,允许用户创建并操作给定的对象。在创建对象时,用户需使 用 bless 函数(详见第 14 章)。 创建一个类 EXAMPLE package Pet sub new{ # Constructor my $class = shift; my $pet = { "Name" => undef, "Owner" => undef, "Type" => undef, };22 第 2 章 bless($pet, $class); # Returns a pointer to the object sub set_pet{ # Accessor methods my $self = shift; my ($name, $owner, $type)= @_; $self->{'Name'} = $name; $self->{'Owner'}= $owner; $self->{'Type'}= $type; } sub get_pet{ my $self = shift; while(($key,$value)=each($%self)){ print "$key: $value\n"; } } 实例化一个类 EXAMPLE $cat = Pet->new(); # alternative form is: $cat = new Pet(); # Create an object with a constructor method $cat->set_pet("Sneaky", "Mr. Jones", "Siamese"); # Access the object with an instance $cat->get_pet; 此外,Perl 还支持方法的继承,为此必须把基类置于数组 @ISA 内。 库和模块。库文件都带有 .pl 扩展名;而模块则拥有 .pm 扩展名。目前,.pm 文件比 .pl 文件更 为常用(详见第 12 章)。 库的路径 @INC 数组负责保存一组标准 Perl 库的路径列表。 导入外部文件 如需导入某个外部文件,应使用 require 或者 use 关键字。 require("getopts.pl"); # Loads library file at run time use CGI; # Loads CGI.pm module at compile time 诊断模式。如需在 Perl 脚本退出时获得出错原因等信息,用户可使用内建的 die 函数或者 exit 函数。 EXAMPLE open(FH, "filename") or die "Couldn't open filename: $!\n"; if ($input !~ /^\d+$/){ print STDERR "Bad input. Integer required.\n"; exit(1); } 此外,用户还可使用如下 Perl 选项: use warnings; # 提供警示信息,但不退出程序 use diagnostics; # 提供详细的警示信息,但不退出程序 use strict; # 检查全局变量、无引号单词等内容;允许退出程序23Perl 快速入门 use Carp; # 与 die 函数类似,但能提供更多程序出错信息 2.2 本章小结 本章是为那些希望快速浏览 Perl 全貌的程序员而准备的,介绍了 Perl 的一般语法与编程结构。 本章仅给出了 Perl 的概况。本书后面的章节将帮助读者发掘更多有关 Perl 的知识。 在读者积累了一定编程经验之后,本章还可作为一个微型的实践教程,以帮助读者快速回忆起 所需知识点,而不必抱着索引慢慢查找。 2.3 下章简介 在第 3 章中,我们会讨论 Perl 脚本的创建,譬如:如何命名一个脚本、如何执行脚本、以及如 何添加注释、语句和内建函数。我们还将学习如何使用 Perl 的命令行开关,以及如何定位特定的一 些错误。24 第 3 章 第 3 章 Perl 脚本 3.1 创建脚本 下面是一个简单的 Perl 脚本: 示例 3.1 (The Script) #!/usr/bin/perl print "What is your name? "; chomp($name = ); # Program waits for user input from keyboard print "Welcome, $name, are you ready to learn Perl now? "; chomp($response = ); $response=lc($response); # response is converted to lowercase if($response eq "yes" or $response eq "y"){ print "Great! Let's get started learning Perl by example.\n"; } else{ print "O.K. Try again later.\n"; } $now = localtime; # Use a Perl function to get the date and time print "$name, you ran this script on $now.\n"; (Output) What is your name? Ellie Welcome, Ellie, are you ready to learn Perl now? yes Great! Let's get started learning Perl by example. Ellie, you ran this script on Wed Apr 4 21:53:21 2007. 示例 3.1 提供了一个 Perl 脚本示例。在不远的将来,读者也能写出与之相似的脚本。Perl 脚本 由一系列 Perl 语句和声明(declaration)组成。其中所有语句均以分号(;)结尾。(由于声明只在 子例程和报表格式中才会用到,因此本书将在后面相关章节里予以介绍。)用户可以在脚本任意位 置创建变量。如果变量没有初始化的话,系统将根据上下文语境自动把它赋值为 0 或 null。请读者 留意,上述脚本中的变量名均以 $ 开头。用户可以把数字、文本字符串或函数输出值赋给变量。不 同类型的变量通过其开头不同的特殊标志(funny symbol)予以区分,具体情况请参考第 4 章内容。 对每一条语句,Perl 将从头至尾仅执行一遍。25Perl 脚本 3.2 脚本 3.2.1 启动 UNIX/Mac 操作系统。如果脚本第一行含有以 #! 标志开头的 Perl 可执行文件全路径的话(又 称为 shbang 行),就将告诉系统核心应当使用哪种程序来解释该脚本。下面是一个起始行的示例: #!/usr/bin/perl 请务必注意,一定要在 shbang 行(即 #! 后面)中指定正确的解释器路径。不同系统中安装的 Perl 可能位于不同的目录下。在调用 Perl 编写的 CGI 脚本时,大多数 Web 服务器都会先读取这一行 内容。因此,如果指定的路径与实际情况不符的话,将会引起严重错误。为了获知自己系统上 Perl 解释器的路径,读者可在 UNIX 提示符后键入 : which perl 如果 shbang 行位于脚本的第一行,读者便可在命令行下通过其文件名直接执行该脚本。如果 shbang 行不在脚本的第一行,UNIX Shell 就会尝试把脚本解释为 shell 脚本,同时把 shbang 行当 作注释来处理。(若要了解如何才能执行 Perl 脚本,请读者参阅 “执行脚本”一节。) Mac OS 在本质上属于 UNIX 的一个变种,因此它默认也带有 Perl 5.8。其使用方法和 Solaris、 Linux、*BSD、HP-UX、AIX OS X 等操作系统完全相同。 Windows。Win32 平台并不支持 shbang 行或其他类似机制 。在 Windows XP 或者 Windows NT 4.0 中,用户可以把 Perl 脚本关联到相应文件扩展名上,譬如 .pl 或者 .plx,然后便可直接从 命令行执行脚本了。在命令行模式下或者系统控制面板中,用户可将 PATHEXT 环境变量设置成需 要关联到 Perl 脚本上的文件扩展名。在命令行中,设置环境变量的命令是: SET PATHEXT = .pl ; %PATHEXT% 在控制面板中,为了设置好上述关联信息,需执行如下操作: 1. 进入开始菜单。 2. 选择“设置”,或者直接选择“控制面板”。 3. 选择“控制面板”。 4. 在控制面板中,点击“系统”图标。 5. 点击“高级”。 6. 点击“环境变量”。 7. 点击“新建”。 8. 在变量名一栏中输入 PATHEXT。 9. 在变量值一栏中输入相应文件扩展名,并以分号和 %PATHEXT% 结尾。 10. 确定上述设置。 另一种获取解释器路径的途径是使用:find / -name ‘*Perl*’ -print。 尽管 Win32 平台一般不需要 shbang 行,但 Apache Web 服务器却需要它。因此如果读者编写的 CGI 脚本将 由 Apache 负责执行的话,那还是必需提供 shbang 行。 在 Windows 95 中,除非是以 Explorer 窗口打开应用程序,否则文件关联功能将无法使用。26 第 3 章 现在,用户可以创建一个 Perl 脚本,并在其文件名中指定所选的扩展名,譬如 myscript.pl 或 myscript.plx。然后,只需在命令行中输入不带扩展名的脚本名称(如 myscript),便可直接执行该 脚本了。(有关脚本执行方面的更多知识请参阅“执行脚本”一节。) 3.2.2 选择文本编辑器 在编写 Perl 脚本时需要使用文本编辑器。为此,用户可以使用操作系统提供的任何编辑软件, 也可从网上下载专门为 Perl 设计的更为高级的文本编辑器,包括各种第三方编辑器以及集成开发环 境(Integrated Development Environment,即 IDE)。表 3-1 列举了常用的一些编辑器。 表 3-1 几种编辑器 BBEdit、JEdit Macintosh Wordpad、Notepad、UltraEdit、vim、PerlEdit、JEdit、TextPad Windows pico、vi、emacs、PerlEdit、JEdit Linux/UNIX Komodo Linux、Mac OS、Windows OptiPerl、PerlExpress Windows Affus Mac OS X 3.2.3 为 Perl 脚本取名 在给 Perl 脚本取名时,惟一需要遵守的规则就是用户所在操作系统的文件命名规范(譬如大小 图 3-1 设置 PATHEXT 环境变量27Perl 脚本 写字符、数字等等)。例如,如果当前操作系统是 Linux,文件名就是大小写敏感的;此外,由于存 在着大量系统命令,因此最好给脚本加上一个扩展名,以便让它是全局惟一的。用户并不一定得为 脚本提供扩展名,除非需要创建库或模块,或者服务器要求所写 CGI 脚本拥有指定的扩展名,又或 Windows 系统设置要求必须提供扩展名。一旦给脚本加上了惟一的扩展名,便可避免脚本和系统中 其他程序的名字发生冲突。譬如,UNIX 系统已经提供了一个名叫“test”的命令。如果用户将某个 脚本命名为“test”的话,那系统应当执行哪个“test”呢?如果没有把握的话,最好在 Perl 脚本文 件名的末尾加上一个 .plx 或者 .Perl 扩展名,这样就能让脚本名全局惟一了。 当然,最好能为脚本准备具有实际含义的名称,以指明脚本的用途是什么。请读者不要使用像 “foo”、“foobar”或“test”之类的名字。 3.2.4 语句、空白和换行 Perl 是一种格式自由的语言,意味着用户可以在任何位置放置脚本语句,甚至还能让同一个语 句跨行出现。“空白”这个词包括空格、制表符以及换行符。Perl 脚本使用“\n”来表示换行符,并 通过双引号将它包围起来。空白部分用于隔开不同的单词。在两个标记或单词之间可以出现任意多 个空白字符。出现在引号内的空白部分将被保留;如果不在引号内,解释器则会忽略它们。下面这 几个表达式表达了相同的意思。 5+4*2 等价于 5 + 4 * 2; 下面这两个 Perl 语句也都是正确的,其中用引号围起来的空白字符部分将会显示出来。 print "This is a Perl statement."; print "This is also a Perl statement."; 尽管用户在编写 Perl 脚本时拥有很多自由,但最好还是能将语句整理到各自所在行内,并为大 段代码提供缩进(本书将在第 5 章讨论该内容)。当然,为自己的程序添加注释也是一样重要的,这 样可以让自己和别人明确地理解各段程序分别在做什么。有关注释的更多知识请阅读下一节内容。 3.2.5 注释 用户可能当天写出了一段非常精彩的代码,但仅仅两周后就忘了这段代码是干什么用的。如果 把这段代码发给别人,引起的困惑就更大了。所谓注释,其实是一段纯文本内容,允许用户在 Perl 脚本中插入说明性文本,同时对程序的执行不造成任何影响。其目的是帮助开发者或其他程序员对 脚本进行维护和调试。Perl 注释都以 # 符号开头,其作用域一直能到本行末尾,但不延伸到下一行。 Perl 无法处理 C 语言提供的注释标识符 /* 和 */,或者 C++ 的注释标识符 //。 示例 3.2 1 # This is a comment 2 print "hello"; # And this is a comment28 第 3 章 解释: 1. 正如 UNIX Shell、sed 和 awk 脚本一样,注释是以 # 号开头的代码行,并能延伸到这一行的末尾。 2. 注释可以出现在脚本的任意行中。上面这个示例显示的是一个注释后面跟随着一行合法的 Perl print 语句。 3.2.6 Perl 语句 Perl 脚本的大部分内容都是其可执行语句。与 C 语言一样,语句由一个或一系列表达式组成, 并以分号结尾。Perl 语句分为简单语句和复合语句两种,由各种运算符、修饰符、表达式和函数组 成,如下面示例所示。 print "Hello, to you!\n"; $now = localtime(); print "Today is $now.\n"; $result = 5 * 4 / 2; print "Good-bye.\n"; 3.2.7 使用 Perl 内建函数 对于任意一种编程语言来说,其内建的或打包于特殊库中的函数集都是该语言的重要组成部分 (请参考附录 A.1)。Perl 也提供了很多有用的函数,这些函数都是独立的程序代码,负责完成某些 特定的工作。在调用内建函数时,用户只需键入函数名,或者也可在函数名后带上一对小括号。所 有的函数名都是小写的,其中许多函数还要求用户提供参数,也就是需要传给函数的消息。例如, 如果用户不提供任何参数的话,print 函数就只能输出一行空白内容,因为其参数指定了需要在屏 幕上输出的内容。如果函数需要参数的话,可将这些参数以逗号隔开,依次放在函数名后面即可。 函数在完成特定任务后往往还会返回一些内容。在本章开头的示例中,调用了两个内建的 Perl 函 数:print 与 localTime。其中,print 函数接受一个字符串参数,并将字符串的内容显示到屏幕上; 而 localTime 函数则不需要任何参数,并能返回当前日期和时间。下面两个语句都是带着参数调用 函数的合法形式。其参数都是“Hello,there.\n”。 print ( "Hello,there.\n " ); print "Hello,there.\n "; 3.2.8 执行脚本 如果脚本中含有 #! 起始行并拥有执行权限(见示例 3.3),或者已经按前面所述步骤在 Windows 中设置好了文件扩展名关联,用户便可在命令行中直接通过脚本名称来执行 Perl 脚本。倘若 #! 不 是脚本第一行的话,读者也可把脚本路径作为参数传给 Perl 解释器,这样便可执行该脚本。 接着,Perl 会通过其内部格式对用户提供的脚本进行编译和运行。如果脚本中存在语法错误, Perl 会马上告知用户。用户还可以通过 -c 开关检查脚本是否编译成功,如下所示: $ perl -c scriptname 若要在 UNIX 或 MS-DOS 命令行提示符下执行脚本,请输入:29Perl 脚本 $ perl scriptname 3.2.9 脚本实例 下面这段示例显示了 Perl 脚本的五个主要部分: 1. 起始行(UNIX) 2. 注释 3. 脚本体内的可执行语句 4. 检查 Perl 语法 5. 脚本的执行(UNIX、Windows) 示例 3.3 $ cat first.perl (UNIX display contents) 1 #!/usr/bin/perl 2 # My first Perl script 3 print "Hello to you and yours!\n"; 4 $ perl -c first.perl # The $ is the shell prompt first.perl syntax OK 5 $ chmod +x first.perl (UNIX) 6 $ first.perl or ./first.perl 7 Hello to you and yours! 解释: 1. 起始行,负责告诉 Shell 哪里有 Perl。 2. 注释部分,描述了程序员希望提供的有关该脚本的信息。 3. 可执行语句,其中含有一个 print 函数。 4. -c 开关,可用于检查语法错误。谢天谢地,上述程序一切正常。 5. chmod 命令,负责开启脚本的执行权限。 6. 执行脚本,(如果 UNIX 的默认路径中含有“.”目录的话)。如果显示“Command not found” 错误(或其他类似反馈信息),请在脚本名前面加上一个句点和一条正斜杠。 7. 脚本会在屏幕上输出字符串“Hello to you and yours!”。 示例 3.4 $ type first.perl (MS-DOS display contents) 1 # No startup line; This is a comment. 2 # My first Perl script 3 print "Hello to you and yours!\n"; 4 $ perl first.perl (Both UNIX and Windows) 5 Hello to you and yours! 解释: 1. 上述示例中没有以 #! 开头的起始行。在使用 Windows 时,脚本并不一定需要起始行。如果 读者使用的是 ActiveState,也可借助一个名叫 pl2bat 的实用工具创建脚本文件。30 第 3 章 2. 这是一行描述性内容;注释内容说明了该脚本不含起始行。 3. 可执行语句,其中含有一个 print 函数。 4. 在命令行环境中,把脚本名称作为参数传给 Perl 程序,以便执行该脚本,并打印脚本输出内 容。读者在任何操作系统上都可通过上述方式执行 Perl 脚本。 3.2.10 可能出现的错误 读者应当作好程序出错乃至大量出错的心理准备。为了得到一个能够完美运行的程序,读者可 能需要再三尝试,屡败屡战。了解碰到的错误信息,和了解老板的怪癖、了解您的另一半、甚至和 了解你自己一样重要。有些程序员就往往一而再、再而三地犯着同样的错误。不过也不用着急,读 者马上就能学到大多数此类出错信息的含义,以及如何去避免它们。 在执行一段 Perl 脚本时,虽然读者经历的操作只有一步,但在 Perl 解释器内部却经历了两个阶 段。首先,它会把整个程序编译成字节码,即该程序的一种内部表达。然后,由 Perl 的字节码引擎 负责逐行运行这些字节码。如果碰到编译器错误,譬如行末缺少分号、关键字拼写错误、或引号不 配对的话,读者便会看到所谓的语法出错信息。用户可通过 -c 开关收集此类错误信息,一旦熟悉之 后,往往就能很容易地发现它们。 示例 3.5 (The Script) print "Hello, world"; 1 print "How are you doing? 2 print "Have you found any problems in this script?"; (Output) Bareword found where operator expected at errors.plx line 3, near "print "Have" (Might be a runaway multi-line "" string starting on line 2) (Do you need to predeclare print?) syntax error at errors.plx line 3, near "print "Have you " Search pattern not terminated at errors.plx line 3. 解释: 1. 这一行应当拥有配对的双引号,并在行尾添上一个分号。 2. 这个 Perl 语句本身是正确的。但是由于 Perl 正在搜寻上一行遗漏的双引号,因此会和本行 的“print”一词产生混淆。这是因为 Perl 认为该行还是上一行的一部分。为什么呢?因为前 一行缺少了一个引号,并且行末没有分号。因此,一旦读者在出错信息里面看到了“runaway” 这个词,往往就意味着代码中某个引号已经 runaway(缺失)了。倘若看到“Bareword”一 词,则意味着某个词语两边缺少引号。 当程序成功通过了编译阶段(也就是说,编译器没有发出任何语法错误或冲突消息)之后,用 户还可能碰到一些运行时错误,又称作逻辑错误。这些错误相对更难以发现,可能是开发者没有 预计到程序运行时才出现的某些问题所致,也有可能该程序在设计时的逻辑就是错的。运行时错 误的原因很多,譬如打开的文件或数据库不存在、用户输入了错误的内容、陷入死循环、或者把 0 当作除数等。不论如何,这一类问题更难以排查,因而又被称为“Bug”。Perl 提供了一个调试 器,允许用户逐行执行脚本中的语句,帮助排查逻辑错误的原因所在。(详见“调试器”一节。)31Perl 脚本 3.3 从命令行使用 Perl 虽然 Perl 的大部分功能都需要用到脚本,但对于那些简单的任务如测试函数、打印语句或只测 试 Perl 语法,用户也可直接在命令行中予以执行。Perl 提供了大量命令行开关(switch),又称为命 令行选项,用于控制或修改其行为表现。下列开关列表并不完整(完整的列表请参阅附录 A),但 它基本说明了命令行下直接调用 Perl 的语法。 在使用命令行时,用户可以看到 shell 提示符。shell 又称为“命令行解释器”。其中,UNIX shell 如 Korn 和 bash 都默认显示 $ 提示符,而 C shell 则使用 % 提示符。UNIX、Linux 和 Mac OS 的 shell 在解析命令方面非常相似。在默认情况下,如果读者使用的是 WindowsXP 或 Vista 的话, 会使用名叫 command.com 的 MS-DOS shell ;倘若使用的是 Windows NT,其 shell 则是 cmd.exe 中的控制台应用程序。上述二者均显示 $ 提示符 。Win32 shell 具有其独特的命令行分析方式。由 于大多 Perl 编程工作都是在脚本文件中完成的,因此用户很少需要关心与 shell 的交互细节。但是, 当把脚本与操作系统进行交互时,如果不知道该使用的命令以及 shell 如何执行它们的话,就有可 能会发生问题。 3.3.1 -e 开关 -e 开关允许 Perl 从命令行而不是脚本来执行 Perl 语句。这是在把 Perl 语句放入脚本前检测其 正确性的好办法。 示例 3.6 1 $ perl -e 'print "hello dolly\n";' # UNIX/Linux hello dolly 2 $ perl -e "print qq/hello dolly\n/;" # Windows and UNIX/Linux hello dolly 解释: 1. Perl 打印字符串 hello dolly 到屏幕,最后是一个换行符 \n。其中,美元符号($)是 UNIX 的命令行提示符。把 Perl 语句包围起来的单引号负责在扫描和解释命令行时区分 Perl 命令和 UNIX shell。 2. 在 MS-DOS 提示符下,Perl 语句内容必须出现在双引号中间。其中,hello dolly 两侧出现的 qq 结构是 Perl 表示双引号的另一种形式。譬如,qq/hello/ 即等价于“hello”。如果在 MS-DOS 提 示符下输入下列命令,则会显示出错信息: $ perl -e ' print "hello dolly\n" ; ' Can't find string terminator " " anywhere before EOF at -e line 1. 请注意:UNIX 系统也可使用上述命令格式。 3.3.2 -n 开关 如果需要打印文件内容或搜索文件中含有特定模式的行,则可以使用 -n 开关隐式地逐一按行 遍历文件。与 sed 和 awk 一样,Perl 也通过其强大的模式匹配技术在文本中查找目标模式。在使 用户看到的命令行提示符也有可能被定制为含有当前目录路径、历史数、驱动盘符等内容。——译者注32 第 3 章 用 -n 开关时,Perl 只会打印指定行的内容。 从文件读取。-n 开关允许用户遍历整个文件,只需在参数中提供该文件的文件名即可。其 Perl 语句位于引号之间,文件名则在命令行的尾部列出。 示例 3.7 (The Text File) 1 $ more emp.first Igor Chevsky:6/23/83:W:59870:25:35500:2005.50 Nancy Conrad:6/18/88:SE:23556:5:15000:2500 Jon DeLoar:3/28/85:SW:39673:13:22500:12345.75 Archie Main:7/25/90:SW:39673:21:34500:34500.50 Betty Bumble:11/3/89:NE:04530:17:18200:1200.75 2 $ perl -ne 'print;' emp.first # Windows: use double quotes Igor Chevsky:6/23/83:W:59870:25:35500:2005.50 Nancy Conrad:6/18/88:SE:23556:5:15000:2500 Jon DeLoar:3/28/85:SW:39673:13:22500:12345.75 Archie Main:7/25/90:SW:39673:21:34500:34500.50 Betty Bumble:11/3/89:NE:04530:17:18200:1200.75 3 $ perl -ne 'print if /^Igor/;' emp.first Igor Chevsky:6/23/83:W:59870:25:35500:2005.50 解释: 1. 在屏幕上打印文本文件 emp.first。在第 2 行,Perl 把这个文件名用作命令行参数。 2. 通过隐式地逐行遍历文件的方式,Perl 打印出 emp.first 文件的所有行。请注意,Windows 用 户应当将命令放在双引号中,而非单引号。 3. Perl 通过正则表达式规定待匹配的模式。模式 Igot 出现在反斜杠之间,并在行首加上了一个 ^ 字符。^ 字符又被称为行首锚点(beginning of line anchor)。Perl 将只打印那些以 Igor 开 头的行。请注意,Windows 用户应当将命令放在双引号中,而非单引号。 从管道读取。由于 Perl 只是又一个应用程序而已,因此用户也可通过管道把命令的输出内容传 输给 Perl。Perl 将把来自管道而不是文件的内容当作输入内容。为此需要使用 -n 开关,以便让 Perl 遍历来自管道的所有内容。 示例 3.8 (UNIX) 1 $ date | perl -ne 'print "Today is $_";' 2 Today is Mon Mar 12 20:01:58 PDT 2007 (Windows) 3 $ date /T | perl -ne "print qq/Today is $_/;" 4 Today is Tue 04/24/2007 解释: 1. UNIX date 命令的输出内容通过管道传送到 Perl,存储在 $_ 变量中。然后,程序会在屏幕上 打印引号之间的字符串 Today is 和 $_ 变量内容,并在最后追加一个换行符。 2. 输出 $_ 变量,其内容则是今天的日期值。 3. Windows NT 的 date 命令,它使用 /T 选项,能够生成当天日期。然后把输出内容通过管道 传送给 Perl,并存储于 $_ 变量中。这里必须使用双引号括起 print 语句。33Perl 脚本 通过标准 I/O 的重定向操作,Perl 也可接受来自文件的输入,并能把输出重定向到文件中去。 示例 3.9 1 $ perl -ne 'print;' < emp.first Igor Chevsky:6/23/83:W:59870:25:35500:2005.50 Nancy Conrad:6/18/88:SE:23556:5:15000:2500 Jon DeLoar:3/28/85:SW:39673:13:22500:12345.75 Archie Main:7/25/90:SW:39673:21:34500:34500.50 Betty Bumble:11/3/89:NE:04530:17:18200:1200.75 2 $ perl -ne 'print' emp.first > emp.temp 解释: 1. Perl 的输入内容来自文件 emp.first,其输出内容则显示在终端屏幕上。请注意,Windows 用 户应当将命令放在双引号中,而非单引号。 2. Perl 的输入内容来自文件 emp.first,其输出内容则被重定向到文件 emp.temp 中。请注意, Windows 用户应当将命令放在双引号中,而非单引号。 3.3.3 -c 开关 正如本章前面所述,-c 开关用于检查 Perl 语法,而不是执行 Perl 命令。如果其语法正确,Perl 就会通知用户。读者最好始终打开 -c 开关,以便时刻检查脚本的正确性。这对于 Perl 编写的 CGI 脚本显得尤为重要,因为这样便可把那些通常显示在屏幕上的出错信息发送到日志里去。 示例 3.10 1 print "hello'; Search pattern not terminated at line 1. Can't find string terminator '"' anywhere before EOF at test.plx 2 print "hello"; test.plx syntax OK 解释: 1. 字符串 hello 以双引号开头,却以单引号结束。引号必须是配对的,因此上面这个双引号应 当和字符串末尾的另一个双引号配对。但实际上其末尾并没有双引号,出现的反而是单引号。 通过打开 -c 开关,Perl 便会显示其在编译时是否发现了语法错误。 2. 纠正上述问题后,Perl 便会显示该语法是正确的。 3.4 读者应当学到的知识 1. 如何创建一个脚本? 2. 每个语句是以什么符号结尾的? 3. 什么是空白字符? 4. 格式自由(free form)的涵义是什么? 5. 什么是内建函数 6. 什么是 UNIX 中的 #! 行? 7. 如何设置脚本使它变得可执行?34 第 3 章 8. 为什么要使用注释? 9. 如果一个 Perl 脚本没有 shbang 行,应当如何执行它? 10. 哪个命令行选项提供对 Perl 语法的检查? 11. -e 开关的作用是什么? 3.5 下章简介 如果不能在程序中打印出其所完成的工作内容,那就和尝试理解哑巴的思想一样麻烦。在下一 章内容中,我们将讨论用于向屏幕(stdout)打印输出内容的 Perl 函数,以及如何归整输出的格式。 读者将了解 Perl 是如何看待字符、空格、文本、反斜杠序列、数字和字符串的。并学习如何使用单 引号、双引号和反引号,以及它们的替代模式。我们将讨论 here 文档,以及如何在 CGI 脚本中使 用它们。此外,读者还将学到如何借助警示信息和诊断信息对脚本中的错误进行排查。 练习 3 了解 Perl 的语法 1. 在命令行提示符下,编写打印如下内容的 Perl 语句。 Hello world!! Welcome to Perl programming. 2. 执行另外一个 Perl 命令,打印 datebook 文件的内容。(读者可在华章网站(www.hzbook.com) 上找到该文件。) 3. 执行 Perl 命令,显示当前使用的 Perl 版本和修订信息。 4. 将示例 3.1 示例程序复制到文本编辑器中,保存并检查它的语法是否正确,然后执行该程序。35获得打印句柄 第 4 章 获得打印句柄 4.1 文件句柄 在通常情况下,每当程序开始执行时,父进程(通常就是 shell 程序)便会打开三个预先定义 的流,分别叫做 stdin、stdout 和 stderr。在默认情况下,这三个流都连接在终端屏幕上。 stdin 流是输入的来源,即终端键盘;stdout 是输出目的地,即屏幕;而 stderr 则是打印程序错 误信息的地方,一般也是终端屏幕。 Perl 会从 shell 继承上述 stdin、stdout 和 stderr 流。Perl 并不直接访问这些流,而是把它们命名 为文件句柄。Perl 只能通过这些文件句柄来访问上述流。其中,stdin 的文件句柄是 STDIN、stdout 的文件句柄是 STDOUT;而 stderr 的文件句柄则是 STDERR。后面将详细介绍如何创建自己的文件 句柄。读者现在只需使用上述预先定义好的句柄即可。 在默认情况下,print 和 printf 函数都会把输出发送到 STDOUT 文件句柄中。 4.2 字(Word) 在向 STDOUT 输出一系列字符内容时,读者最好先能理解 Perl 是如何处理这些字的。在 Perl 中,任何未加引号的字都必须以字母或数字开头,并由字母、数字或下划线组成。Perl 是区分字母 大小写的。如果没有在字两边加上引号,就有可能与其他表示文件句柄的词、标记或其他保留字发 生冲突。如果某个字在 Perl 中没有特殊含义的话,就应当把它放在单引号中。 4.3 print 函数 print 函数负责将字符串或由逗号隔开的字列表打印到 Perl 的 STDOUT 文件句柄中。如果调用 成功,print 函数就返回 1,否则返回 0。 字符串常量 \n 可以出现在字符串的末尾,表示换行;亦可嵌入到字符串中间位置,以便割裂该 字符串。与 shell 一样,为了解释反斜杠,Perl 也要求将 \n 这样的转义序列置入到双引号中去。 示例 4.1 (The Script)36 第 4 章 1 print "Hello", "world", "\n"; 2 print "Hello world\n"; (Output) 1 Helloworld 2 Hello world 解释: 1. 每个传送给 print 函数的字符串都位于双引号之间,并以逗号分隔。如果需要打印空白字符, 则必须把这些空白字符也放到引号中。为了表达换行符,还应当把 \n 转义序列也放入双引号 之间。 2. 将双引号之间的整个字符串打印到标准输出。 示例 4.2 (The Script) 1 print Hello, world, "\n"; (Output) 1 No comma allowed after filehandle at ./perl.st line 1 解释: 如果没有给字符串加上引号,则必须指定 STDOUT 文件句柄。否则 Perl 便会把它碰到的第一 个参数作为文件句柄的名字(譬如本例中的 Hello 字符串将被当作文件句柄)。在文件句柄后面不应 跟随逗号;逗号只能用于待输出的各字符串之间。 示例 4.3 (The Script) 1 print STDOUT Hello, world, "\n"; (Output) 1 Helloworld 解释: 如果没有给字符串加上引号,则必须指定 STDOUT 文件句柄。如需解释 \n,则应当将它置于 双引号之间。最好不要以这种方式使用不带引号的字符串。不在引号内的词又称为裸词(bareword)。 注:在 STDOUT 后面不应有逗号。 4.3.1 引号 不论用户使用 Perl 的什么功能,都可能碰到引号,尤其是在打印字符串的时候。一般而言,字 符串都由两个配对的双引号或单引号包围起来。当字符串两头是单引号时,串内所有字符都被当作 文本进行处理;而当两头是双引号时,尽管大部分字符都被当作纯文本,也会有少量字符是作为变 量替换(variable substitiution)和特殊字符序列(special escape sequence)来处理的。本章将讨论 特殊字符序列;而有关变量的内容请读者阅读第 5 章“变量”。 在 Perl 中,某些字符具有特殊含义,譬如美元符号($)和 @ 符号。如果需要把这些符号当作 纯文本来处理的话,就必须在它前面加上反斜杠(\),或者在它两边加上单引号(' ')。其中,反斜 杠只能处理单个字符,而不是整个字符串。37获得打印句柄 示例 4.4 (The Script) 1 $name="Ellie"; 2 print "Hello, $name.\n";# $name and \n evaluated 3 print 'Hello, $name.\n';# String is literal; newline not # interpreted 4 print "I don't care!\n";# \n is interpreted in double quotes 5 print 'I don\'t care!', "\n";# Backslash protects single quote # in string "don\'t" (Output) 2 Hello, Ellie. 3,4 Hello, $name.\nI don't care! 5 I don't care! 用户碰见最频繁的错误莫过于和引号相关的错误了。这里将介绍一些最常见的因为引号不匹配 或者裸字情况而出现的出错信息。 读者不妨可以把引号想象成是 Perl 字符串的外套。如果脱了这层外套,就有可能收到下列与 “BareWord”相关的信息: Bareword “there” not allowed while “strict subs” in use at try.pl line3. Execution of program. pl aborted due to compilation errors. 读者也可把引号想象成是一对夫妻。一个双引号的配偶必然是另一个与之配对的双引号;而 一个单引号也只能和与之相配的单引号成为夫妻。如果没有对引号进行匹配的话,就好像半边 天不见了,或者说“runaway”了。那剩下的另一半该何去何从呢?这时用户就会收到如下错误 消息: (Might be a runaway multi-line " " string starting on line 3) 触犯引号规则的情况 示例 4.5 (The Script) #!/usr/bin/perl # Program to illustrate printing literals 1 print "Hello, "I can't go there"; # Unmatched quotes 2 print "Good-bye"; (Output) Bareword found where operator expected at qtest.plx line 2, near ""Hello, "I" (Missing operator before I?) Bareword found where operator expected at qtest.plx line 3, near "print "Good" (Might be a runaway multi-line "" string starting on line 2) (Do you need to predeclare print?) String found where operator expected at qtest.plx line 3, at end of line (Missing semicolon on previous line?) syntax error at qtest.plx line 2, near ""Hello, "I can't " Can't find string terminator '"' anywhere before EOF at qtest.plx line 338 第 4 章 解释: 1. 字符串“Hello 的开头有一个双引号,但其尾部却没有与之配对的双引号。这种格式会造成麻 烦的问题。Perl 会假定 I 前面的那个双引号是与上面这个引号配对的。结果造成后面另一个 字符串“I can’t go there”又成了光杆字符串。而这一行末尾的双引号则会与下一行开头的双 引号配对。总之一切都乱套了。 2. Perl 还会认为“Good_bye”也是光杆字符串,因为无法为它找到可供匹配的引号。第 1 行 “there”末尾的双引号已经把本行“Good_bye”开头的引号配对过去了,从而造成“Good_bye” 成了光杆字符串。 4.3.2 实量(常量) 当把某个实量值(literal value )赋予某个变量或在屏幕上打印它时,该实量可以表示某个数字。 这些数字可以是十进制、八进制或者十六进制,也可以是用浮点模式或科学计数法表示的浮点数。 位于双引号之间的字符串中也可以含有实量(literal),譬如 \n 表示换行符,\t 表示制表符,\e 则 表示取消(escape)。字符串实量是以一个反斜杠开头的位于字母表中的字符。它们可用于表达十进 制、八进制、十六进制或控制符。 Perl 还提供了用于表示当前脚本名称、当前脚本行号和当前脚本逻辑末尾位置的实量。 鉴于读者可能会在 print 或 printf 函数中用到这些实量,不 妨让我们在这里看看它们的庐山真面目。(若需获得更多有关 常量定义的信息,请参阅附录 A 中的“常量”一栏。) 数字实量。数字实量能够以十进制、八进制或十六进制形 式表示某个正整数或负数(详见表 4-1)。此外还能以浮点形 式或科学计数法形式表达浮点数。其中,八进制整数必需以 0 (零)开头,而十六进制数则必需以 0x(零和 x)开头。以科 学计数法表达的数字必需在末尾带有一个 E,并在其后加上一 个正数或负数以表示其指数情况。 字符串实量。和 shell 中的字符串相似,Perl 字符串也是 通过单引号或双引号隔开的。在字符串中存在着所谓字符串实 量(string literal),又称为逸出字符序列(escape sequence)。它必须出现在双引号之间,常用于解 释反斜杠字符。 表 4-2 字符串实量 逸出序列 描述信息(ASCII 名称) \t 制表符 \n 换行符 \r 回车符 \f 表格缩进 实量又称为常量(Constant),但 Perl 方面的专家都爱用“实量(Literal)”这个词。本书为了向他们看齐, 也将使用“实量”这个词。 表 4-1 数字实量 示例 描述 12345 整数 0b1101 二进制数 0x456fff 十六进制数 0777 八进制数 23.45 浮点数 .234E-2 科学计数法39获得打印句柄 逸出序列 描述信息(ASCII 名称) \b 退格符 \a 警报 / 闹钟 \e 取消(escape) \033 八进制字符 \xff 十六进制字符 \c[ 控制符 \l 从下一字符开始切换为小写字母 \u 从下一字符开始切换为大写字母 \L 从下一字符开始到 \E 结束,切换为小写字母 \U 从下一字符开始到 \E 结束,切换为大写字母 \Q 在所有字符前追加反斜杠,直到碰到 \E \E \L 或 \U 的相应结束符 \\ 反斜杠 示例 4.6 print "This string contains \t\ttwo tabs and a newline.\n" # Double quotes (Output) This string containstabs and a newline. print 'This string contains\t\ttwo tabs and a newline.\n; #Single quotes (Output) This string contains\t\ttwo tabs and a newline.\n 特殊实量。Perl 提供了两个特殊实量 _LINE_ 和 _FILE_。它们往往用作分隔符,不论在单引号 还是双引号中都不会解释。它们分别代表当前脚本的行数和名称。Perl 中的特殊实量等价于 C 语言 中预定义的特殊宏(Macro)。 在脚本中,特殊实量 _END_ 负责表示文件的逻辑末尾位置。任何位于 _END_ 后面的内容都将 被忽略,就好像它们成了注释一样。在 UNIX 中,表示文件末尾的控制序列是 -d(\004),而 在 MS-DOS 中则是 -z(\032);二者都等效于 _END_。 _DATA_ 特殊实量则负责表示一个文件句柄,允许用户处理来自脚本的文本数据,而非外来数据。 示例 4.7 print "The script is called", _ _FILE_ _, "and we are on line number ", _ _LINE_ _,"\n"; (Output) The script is called ./testing.plx and we are on line number 2 注意:在特殊实量两边必须各提供两个下划线。(详见表 4-3) (续)40 第 4 章 表 4-3 特殊实量 实 量 描 述 _ _LINE_ _ 表示当前行号 _ _FILE_ _ 表示当前文件名 _ _END_ _ 表示脚本的逻辑末尾位置;其后的内容都将被忽略 _ _DATA_ _ 表示特殊文件句柄 _ _PACKAGE_ _ 表示当前包;默认的包是 main 4.3.3 打印实量 在前面我们介绍了实量的表现形式,本节则探讨如何在 print 函数中使用这些实量(常量)。 打印数字实量 示例 4.8 (The Script) #!/usr/bin/perl # Program to illustrate printing literals 1 print "The price is $100.\n"; 2 print "The price is \$100.\n"; 3 print "The price is \$",100, ".\n"; 4 print "The binary number is converted to: ",0b10001,".\n"; 5 print "The octal number is converted to: ",0777,".\n"; 6 print "The hexadecimal number is converted to: ",0xAbcF,".\n"; 7 print "The unformatted number is ", 14.56, ".\n"; 8 $now = localtime(); # A Perl function 9 $name = "Ellie"; # A string is assigned to a Perl variable 10 print "Today is $now, $name."; 11 print 'Today is $now, $name.'; (Output) 1 The price is. 2 The price is $100. 3 The price is $100. 4 The binary number is converted to: 17. 5 The octal number is converted to: 511. 6 The hexadecimal number is converted to: 43983. 7 The unformatted number is 14.56. 10 Today is Sat Mar 24 15:46:08 2007, Ellie. 11 Today is $now, $name. 解释: 1. 字符串 The price is $500 位于双引号中。其中,$ 是 Perl 规定的特殊字符,用于表示标量(参 阅第 4 章“变量”),而不是表示货币。因此,由于程序中并没有定义 $100 变量,所以上述 语句不会打印任何内容。由于单引号具有屏蔽所有字符解释的作用,因此若把 $100 置入单 引号之间,便可避免它被当作变量处理。除此之外,在 $ 前面加上反斜杠也能起到相同的效 果。不过,如果使用的是单引号的话,\n 也将成为普通的字符串,而不是表示换行。 2. 本行用反斜杠引用美元符号 $,故而会把它作为实量来处理。41获得打印句柄 3. 如果需要作为数字而非字符串进行处理的话,数字 100 就必须以单字(word)格式出现。这 里即便没有跟随变量名,也必须对美元符号进行转义处理。若要解释为特殊字符,则必须将 \n 置于双引号之间。 4. 这是一个二进制数,因为它是以 0b(0 和 b)开头的。本行将打印其十进制值。 5. 这是一个八进制数,因为它是以 0(零)开头的。本行将打印其十进制值。 6. 这是一个十六进制数,因为它是以 0x(0 和 x)开头的。本行将打印其十进制值。 7. 这是一个数字,其打印形式和实际含义相同,都是 14.56。print 函数不会改变输出内容的格式。 8. Perl 拥有大量函数。读者已经学会了 print 函数。而 localTime() 则是 Perl 提供的另外一个函 数。(函数名后面的括号是可有可无的。)该函数能够返回当前日期和时刻。这里把时间结果 值赋给了一个名叫 $now 的 Perl 变量。在下一章中,读者将学习如何使用变量。 9. 本行把 $name 赋值为字符串“Ellie”。 10. 当字符串位于双引号之间时,print 函数会输出 $now 和 $name 这两个变量的值。 11. 当字符串位于单引号之间时,print 函数会把他们当作纯字符串予以输出。 打印字符串实量 示例 4.9 (The Script) #!/usr/bin/perl 1 print "***\tIn double quotes\t***\n"; # Backslash interpretation 2 print '%%%\t\tIn single quotes\t\t%%%\n'; # All characters are # printed as literals 3 print "\n"; (Output) 1 *** In double quotes *** 2 %%%\t\tIn single quotes\t\t%%%\n 3 解释: 1. 如果字符串位于双引号之间,则执行反斜杠解释。其中,\t 是一个字符串实量,负责产生制 表符;\n 则表示换行符。 2. 如果字符串位于单引号之间,则不解释特殊字符 \t 和 \n。将他们按原样打印。 3. 若要解释换行符 \n,则必需将其置于双引号之间。“\n”字符串将能实现换行。 示例 4.10 (The Script) #!/usr/bin/perl 1 print "\a\t\tThe \Unumber\E \LIS\E ",0777,".\n"; (Output) 1 (BEEP) The NUMBER is 511. 解释: \a 能够产生警报或蜂鸣音。后面紧跟两个 \t 制表符。\U 表示要以大写形式打印其后的字符串, 直到碰到 \E 或行末为止。因此,这里的 number 将以大写形式打印出来,直到碰到 \E。而 \E 之前的字符串 IS 则会变为小写形式。本行接着打印八进制数字 0777 的值,最后打印句点和换 行符。42 第 4 章 打印特殊实量 示例 4.11 (The Script) #!/usr/bin/perl # Program, named literals.perl, written to test special literals 1 print "We are on line number ", _ _LINE_ _, ".\n"; 2 print "The name of this file is ",_ _FILE_ _,".\n"; 3 _ _END_ _ And this stuff is just a bunch of chitter–chatter that is to be ignored by Perl. The _ _END_ _ literal is like Ctrl–d or \004.a (Output) 1 We are on line number 3. 2 The name of this file is literals.perl. 解释: 1. 如需解释特殊实量 _LINE_,则应将其置于引号之间。该特殊实量负责保存 Perl 脚本的当前 行号。 2. 当前脚本名是 literal.Perl。特殊实量 _FILE_ 能保存当前 Perl 脚本名。 3. 特殊实量 _END_ 表示脚本的逻辑终止位置,告诉 Perl 忽略其后出现的一切字符。 示例 4.12 (The Script) #!/usr/bin/perl # Program, named literals.perl2, # written to test special literal _ _DATA_ _ 1 print ; 2 _ _DATA_ _ This line will be printed. And so will this one. (Output) This line will be printed. And so will this one. 解释: 1. print 函数将显示特殊实量 _DATA_ 下的所有文本内容。由于特殊实量 _DATA_ 是位于尖括 号中的,因此 Perl 将把它作为文件句柄打开,print 函数将显示 读取的行内容。 2. 这是 文件句柄使用的数据(读者可以用 _END_ 代替 _DATA_,也能得到相同的结果)。 4.3.4 warning 编译指示符和 -w 开关 -w 开关能在用户使用潜在保留字或者其他可能导致程序出错的特性时发出警告。Larry Wall 在 Perl 5 的 man 主页中说:“每当碰到无法解释的问题时,不妨试着打开 -w 开关!即便没有,最好也 打开它。” 用户可使用 -w 作为 Perl 的命令行选项,譬如: perl -w 43获得打印句柄 或者在 Perl 脚本中的 shbang 行中指定,如: #!/usr/bin/perl -w 编译指示符(pragma)是一种特殊的 Perl 模块,负责告诉编译器如何编译语句块。用户可利用 这种模块控制程序的行为。从 Perl 5.6.0 起,在标准 Perl 库中出现了 warning.pm 模块,其功能类似 于 -w 开关。后者是一个编译指示符,用于控制警告类型。 用户亦可在程序的 #! 行下(若没有 #! 行则在脚本开头)添加下行内容: use warnings; 这样就启用了所有可能的警告信息。若要关闭警告信息,只需在脚本中添加下列内容: no warnings; 这样便可关闭脚本中所有可能的警告信息。 示例 4.13 (The Script) #!/usr/bin/perl # Scriptname: warnme 1 print STDOUT Ellie, what\'s up?; (Output) (At the Command Line) $ perl -w warnme Unquoted string "what" may clash with future reserved word at warnme line 3. Backslash found where operator expected at warnme line 3, near "what\" Syntax error at warnme line 3, near "what\" Can't find string terminator "'" anywhere before EOF at warnme line 3. 解释: -w 开关(详见附录 A)负责打印那些标识符意义不明情况的警告信息,譬如只使用了一次的变 量,不正确的字符串和数字转换等。由于字符串 Ellie 没有加引号,因此 Perl 会错误地认为它是保留 字或未定义的文件句柄。本例中其他的错误信息则是由字符串中不匹配的引号所引发的。 示例 4.14 (The Script) #!/usr/bin/perl # Scriptname: warnme 1 use warnings; 2 print STDOUT Ellie, what\'s up?; (Output)Unquoted string "what" may clash with future reserved word at warnme line 3. Backslash found where operator expected at warnme line 3, near "what\" Syntax error at warnme line 3, near "what\" Can't find string terminator "'" anywhere before EOF at warnme line 3. 解释: Perl 5.6 和其后出现的版本都使用 warning 编辑指示符代替了 -w 开关。其中,use 函数负责使 用那些位于 Perl 库中的模块。warning 编译指示符则负责发送有关标识符二义性的警告信息,由于 字符串 Ellie 没有加引号,因此 Perl 会错误地认为它是保留字或未定义的文件句柄。由于该程序没 有闭合引号以结束字符,因此编译器会发出警告。44 第 4 章 4.3.5 diagnostics 编译指示符 该编译指示符除了能够显示警告信息外,还能提供有关当前错误的更详细解释。与 warning 编 译指示符类似,它只能影响脚本的编译阶段。但和前者不同的是,它将假定读者是不懂得编程的新 手,并提供尽可能详细的出错信息。 示例 4.15 (The Script) use diagnostics; print "Hello there'; # Unmatched quote print "We are on line number ", _ _LINE_ _,"\n"; (The output)Bareword found where operator expected at test.plx line 3, near "$now = "Ellie" (Might be a runaway multi-line "" string starting on line 2) (#1) (S syntax) The Perl lexer knows whether to expect a term or an operator. If it sees what it knows to be a term when it was expecting to see an operator, it gives you this warning. Usually it indicates that an operator or delimiter was omitted, such as a semicolon. (Missing operator before Ellie?) String found where operator expected at test.plx line 3, at end of line (#1) (Missing semicolon on previous line?) syntax error at test.plx line 3, near "$now = "Ellie" Can't find string terminator '"' anywhere before EOF at test.plx line 3 (#2) (F) Probably means you had a syntax error. Common reasons include: A keyword is misspelled. A semicolon is missing. A comma is missing. An opening or closing parenthesis is missing. print "hello there'; print "We are on line number ", _ _LINE_ _,"\n"; 解释: Perl 5.6 及其后续版本使用 diagnostics 编译指示符代替了 -w 开关和 warning 编译指示符。这 个特殊的 Perl 模块将发送有关脚本出错详情的消息。由于这个脚本中的字符串 Hello 不含匹配的引 号,因此 diagnostics 编译指示符会列出造成问题的所有潜在因素。编译器将要求该字符串以另一个 匹配的引号结尾。 4.3.6 strict 编译指示符 本节将讨论的是 strict 编译指示符。当使用该指示符时,一旦程序违反了相关约束条件,就会 编译不通过。如上面例子所示,如果出现了裸字符 (即未加引号的字符),则 strict 编译指示符就 不能通过,从而造成程序退出。用户可通过各种参数控制 strict 编译指示符的内容(完整的列表内 为字符加上引号就好比为它穿上衣服。如果去掉引号,字符就好像裸露了一样。45获得打印句柄 容请参阅附录 A)。 示例 4.16 (The Script) #!/usr/bin/perl # Program: stricts.test # Script to demonstrate the strict pragma 1 use strict "subs"; 2 $name = Ellie; # Unquoted word Ellie 3 print "Hi $name.\n"; (Output) $ stricts.test Bareword "Ellie" not allowed while "strict subs" in use at ./stricts.test line 5. Execution of stricts.test aborted due to compilation errors. 解释: 通过 use 函数便可使用 Perl 标准库中的模块。当 strict 编译指示符以 subs 作为参数时,便 会在内部编译时捕获程序中任何位置的裸字。一旦发现有裸字,就立刻退出程序并报错。 4.4 printf 函数 printf 函数负责将格式化的字符串输出到选定的文件句柄。其默认文件句柄是 STDOUT。 它和在 C、awk 中使用的 printf 函数功能相同。如果 printf 函数执行成功,其返回值是 1,否 则为 0。 printf 函数后面带有一个加有引号的负责格式规范的控制字符串。其后则是一系列由逗号隔开 的参数,这些参数都必须是简单表达式。其格式说明符以 % 开头。而对于每个以 % 开头的格式说 明符,必须有一个参数与它对应(详见表 4-4 和表 4-5)。 用户也可选择在字符串和表达式两边加上小括号。 示例 4.17 解释: 1. 待打印的字符串位于双引号之间。其第一个格式说明符是 %s,对应于参数 John,直接定位 在第一个逗号的右侧。跟在百分号 % 后面的 s 也叫转换字符(conversion character)。s 说明 该处将进行字符串转换。在本例中,John 将在打印输出时替换其中的 %s。 2. %d 格式则说明十进制(整数)值 50 将在字符串中打印出来。 printf("The name is %s and the number is %d\n","John",50);46 第 4 章 表 4-4 格式化转换符 转换符 定 义 %b 无符号二进制整数 %c 字符 %d、i 十进制整数 %e 科学计数法浮点数 %E 使用大写字母 E 的科学计数法浮点数 %f、%F 浮点数 %g 使用 e 或 f 转换符的浮点数,取其最小宽度 %G 使用 e 或 f 转换符的浮点数,取其最大宽度 %id、%D 长整型十进制数 %lu、%U 无符号长整型十进制数 %lo、%O 长整型八进制数 %p 指针(十六进制数) %s 字符串 %u 无符号的十进制数 %x 十六进制数 %X 使用大写字母 X 的十六进制数 %lx 长整型十六进制数 %% 打印百分号实量 标记修饰符位于 % 之后,负责进一步定义输出的格式。例如,%-20s 表示输出长度为 20 个字 符的左对齐的字符串。 表 4-5 标记修饰符 转换符 定 义 %- 左对齐修饰符 %# 如果是八进制数,则在显示时带上前导 0;如果是十六进制数,则在显示时带有前缀 0x %+ 对于使用 d、e、f 和 g 的转换符,显示其整数部分,并显示正负号+、- %0 把显示内容中的空白部分以 0 补足 %number 最大字段宽度。譬如,若 number 为 6(如 %6d),说明最大字段宽度是 6 %.number 指定浮点数的精度。例如,%.2f 表示小数点后两位 ;%8.2 表示最大字段宽度为 8,并 精确到小数点后两位 在打印参数时,字段(field)负责为打印结果提供位置,其宽度是包含在字段中的字符数。字47获得打印句柄 段的宽度由百分号 % 与代表字段最大宽度的数字来决定,其后紧跟着相应转换字符。譬如,%20s 表示字段宽度为 20 个字符的右对齐的字符串;%-25s 表示字段宽度为 25 个字符的左对齐的字符 串;%10.2f 则表示字段宽度为 10 个字符的浮点数(其中小数点算一个字符),并精确到小数点后 2 位。如果参数宽度超过了最大字段宽度的话,printf 也不会对数字进行截取,只不过其输出格式可能 变得较为难看。譬如,如果需要截去小数点右边的数字的话,printf 将对该数字进行四舍五入。如 格式转换符为 %.2f,相应参数为 56.55555,则会打印出 56.6。 示例 4.18 (The Script) #!/usr/bin/perl 1 printf "Hello to you and yours %s!\n","Sam McGoo!"; 2 printf("%-15s%-20s\n", "Jack", "Sprat"); 3 printf "The number in decimal is %d\n", 45; 4 printf "The formatted number is |%10d|\n", 100; 5 printf "The number printed with leading zeros is |%010d|\n", 5; 6 printf "Left-justified the number is |%-10d|\n", 100; 7 printf "The number in octal is %o\n",15; 8 printf "The number in hexadecimal is %x\n", 15; 9 printf "The formatted floating point number is |%8.2f|\n", 14.3456; 10 printf "The floating point number is |%8f|\n", 15; 11 printf "The character is %c\n", 65; (Output) 1 Hello to you and yours Sam McGoo! 2 Jack Sprat 3 The number in decimal is 45 4 The formatted number is | 100| 5 The number printed with leading zeros is |0000000005|. 6 Left-justified the number is |100 | 7 The number in octal is 17 8 The number in hexadecimal is f 9 The formatted floating point number is | 14.35| 10 The floating point number is |15.000000| 11 The character is A 解释: 1. 位于引号之间的字符串含有 %s 格式转换符。在打印时用字符串 Sam Mcgoo 替换 %s。 2. 字符串 Jack 的字段宽度为 15 个字符,并且是左对齐的。字符串 Sprat 的字段宽度为 20 个字 符,也是左对齐的。其中的括号是可选的。 3. 数字 45 将以十进制整数格式打印。 4. 数字 100 的字段宽度是 10,并且是右对齐的。 5. 数字 5 的字段宽度是 10,它是右对齐的,其前缀是先导零(0)而非空格。如果在表示字段 宽度的数字前放置修饰符 0 的话,就表示在打印时必须用先导零补全字段前部的空白部分。 6. 数字 100 的字段宽度为 10,左对齐。 7. 字段 15 以八进制整数形式打印。 8. 数字 15 以十六进制整数形式打印。 9. 数字 14.3456 的字段宽度为 8 个字符,包括一个小数点字符。其可选部分表示精确到两位小 数并进行四舍五入。48 第 4 章 10. 数字 15 的字段宽度是 8 个字符,右对齐,默认精确到小数点后 6 位。 11. 数字 65 将转换为 ASCII 字符 A 并打印出来。 4.4.1 sprintf 函数 sprintf 函数与 printf 函数基本类似,所不同的是前者允许给变量赋予格式化字符串。sprintf 函 数和 printf 函数使用相同的转换符表(详见表 4-4 和表 4-5)。有关变量的内容将在第 5 章“变量” 中予以详细介绍。 示例 4.19 (The Script) 1 $string = sprintf("The name is: %10s\nThe number is: %8.2f\n", "Ellie", 33); 2 print "$string"; (Output) 2 The name is: Ellie The number is: 33.00 解释: 1. sprintf 函数遵循和 printf 相同的字符、字符串和数字转换规则。其真正的区别仅限于 sprintf 允许把格式化后的输出内容保存到变量中。在本例中,格式化后的输出内容将存储于标量型 变量 $string 里。字符串中插入的 \n 会导致换行的发生。有关标量的详细内容将在第 5 章“名 字里的乾坤”予以详细介绍。本行的小括号是可选的。 2. 打印出来的变量值是 sprintf 函数产生的格式化输出。 4.4.2 无引号打印:here 文档 Perl 的 here 文档(here document)特性直接来自于 UNIX shell 中的 here 文档。它允许引用一 整块文档内容,这些内容必须位于名叫“用户自定义终止符(user-defined terminator)”的字段之 间。在第一个终止符到最后一个终止符之间出现的文本就相当于加上了引号,换而言之就是为“从 here 到 here”的本文加上引号。here 文档是一种面向行的使用格式,其格式是:首先提供一个起始 的终止符和分号,然后提供 << 运算符。<< 运算符后面可能没有空格,除非其终止符本身也需要被 引用。如果终止符的两边没有加上单引号或双引号的话,则执行变量扩展。如果终止符两边有单引 号的话,则不执行变量扩展。本文的每一行将插入到第一个和最后一个终止符之间。最终终止符必 须自己独占一行,并且周围不能出现空白字符。 与 shell 不同的是,Perl 在 here 文档中不能执行命令替换(即备份引用,backquotes)操作。此 外,当终止符出现在备份引用内容中时,Perl 也能执行 here 文档中的命令。 在 CGI 脚本里 here 文档常用于提供大块的 HTML 标签内容。 示例 4.20 (The Script) 1 $price=1000; # A variable is assigned a value. 2 print <Town Crier
"; EXAMPLE for($n=0; $n<10; $n++){ if ($n == 3){ next; # Start at top of loop; # skip remaining statements in block } echo "\$n = $n
"; } print "Out of the loop.
"; 子例程 / 函数 函数(function)是一组能完成某项任务的代码体,并供程序其他部分调用。用户可以通过参数 将数据传送给这个函数。函数可以有返回值,也可以不返回任何值。任何合法的 Perl 代码均可出 现在函数体的定义中。定义于函数外面的变量在函数体内也同样可用。而 my 函数则能把指定的变 量局部化。 (参阅第 11 章)。 sub function_name{ 函数体 }18 第 2 章 (续) 子例程 / 函数 EXAMPLE sub greetings() { print "Welcome to Perl!
"; # Function definition } &greetings; # Function call greetings(); # Function call EXAMPLE $my_year = 2000; if ( is_leap_year( $my_year ) ) { # Call function with an argument print "$my_year is a leap year\n"; } else { print "$my_year is not a leap year"; } sub is_leap_year { # Function definition my $year = shift(@_); # Shift off the year from # the parameter list, @_ return ((($year % 4 == 0) && ($year % 100 != 0)) || ($year % 400 == 0)) ? 1 : 0; # What is returned from the function } 文件处理 Perl 提供了 open 函数用于打开文件,也提供了 pipe,用于读写、追加文件内容。其中,open 函 数的参数包括一个用户自定义的文件句柄(一般表现为一串大写字符),以及一个含有文件路径和 读 / 写 / 追加标志的字符串(详见第 10 章)。 EXAMPLE To open a file for reading: open(FH, "