目录


第二十九章,函数 (A-D)

为了便于参考,本章以字母顺序(注:有时候,紧密联系的函数在系统手册页里组合在一起, 因此我们在这里也将尊重那些分组。比如,要寻找 endpwent 的描述,你得先找 getpwent。) 描述内建的 Perl 函数。每个函数描述以一个该函数语法的简短概要开头。象 THIS 这样的参数 名字代表实际表达式的占位符,而跟在语法概要后面的文本将描述提供(或者省略)该实际参数的 语意。

你可以把函数和文本以及变量想象成一个表达式里的项。或者你可以把它们想象成前缀操作符, 处理它后面的参数。要知道我们有一半时间叫它们操作符。

这些操作符,哦,是函数,中有一些接受一个 LIST 作为一个参数。这个 LIST 的元素应该用 逗号分隔(或者用 =>,它只是逗号的一种有趣的形式)。这个 LIST 的元素在列表环境中计算, 所以每个元素都会返回一个标量或者一个列表值——取决于它对列表环境的敏感程度。每个返回的 值,不管是标量还是列表,都会被解释成一个总体标量值序列的一部分。也就是说,把所有列表 平面化成一个列表。从接收参数的函数的角度来看,全部参数 LIST 总是一个一维列表数值。 (要把一个数组代换成一个元素,你必须明确地创建一个引用并用它代换该数组。)

预定义的 Perl 函数既可以带着圆括弧使用,也可以不带圆括弧使用;在本章中的语法概要里是 省略圆括弧的。如果你确实使用圆括弧,其简单但有时有些怪异的规则如下:如果它看上去象 函数,那它就是函数,所以优先级并不紧要。否则,它就是一个列表操作符或者一个单目操作符, 而优先级就有关系了。这里要小心,因为就算你在关键字和它的左圆括弧之间放了空格,也不能不 让它成为一个函数:

   print 1+2*4      #打印 9。
   print(1+2) * 4;   #打印 3!
   print (1+2)*4;   #还是打印 3!
   print +(1+2)*4;   # 打印12。
   print ((1+2)*4)   # 打印12。

如果你带着 -w 开关运行 Perl,它会警告你这些问题。比如,上面的第二和第三行产生下面 这样的信息:

   print (...) interpreted as function at - line 2.
   Useless use of integer multiplcation in void context at - line 2.

给定一些函数的简单定义的前提下,你可以使用的传递参数的方法有相当可观的自由度。比如, 使用 chmod 的最常见的方法就是把文件权限(模式)作为一个初始参数传递:

   chmod 0644, @array

但是 chmod 的定义只是说:

   chmod LIST

所以你也可以说:

   unshift @array, 0644;
   chmod @array;

如果列表的第一个参数不是一个有效的模式,chmod 调用将失败。但这是一种运行时语意问题, 和调用它的语法没有关系。如果该语意要求任何特殊的参数必须先传递,那么文本将描述这些 限制。

与简单的 LIST 函数相反,其他函数强制附加的语法约束。比如,push 的语法概要看起来象下面 这样:

   push ARRAY,LIST

这意味着 push 要求一个正确的数组作为它的第一个参数,但是不在乎随后的参数。那就是最后的 LIST 的意思。(LIST 总是最后出现,因为它把所有剩下的数值都吃掉。)如果一个语法概要在 LIST 之前包含任何参数,那么编译器会认为那些参数是有语法区别的,而不仅仅是在它稍后运行 时解释器看到的语意区别。这样的参数从来不会放在列表环境里计算。它们可能会在标量环境里 计算,或者它们也可能是象 push 里的数组一样的引用参数。(描述会告诉你某个参数是哪种 类型。)

对于那些自身的操作直接基于 C 库的函数,我们不想复制你的系统的文档。如果一个 function 描述里说:参阅 function(2),它的意思就是你应该查看对应的 C 版本的函数,学习更多其语意 方面的内容。如果你安装了手册页,那么圆括弧里的数字指示你查阅手册页时会看到的系统程序员 手册一节。(当然,如果你没装手册页那么你也看不到。)

这些手册页可能记载了一些系统相关的行为,象 shadow 口令文件,访问控制列表,等等。许多 来自 Unix 里的 C 库的 Perl 函数现在甚至在非 Unix 平台上都有仿真。比如,也许你的操作 系统可能不支持 flocl(2) 或 fork(2) 系统调用,但 Perl 也会通过使用所有你的平台提供的 设施尽可能地模拟这些调用。

有时候,你会发现那些记载了的 C 函数比对应的 Perl 函数的参数多。通常,那些缺少了的参数 是 Perl 已经知道的东西,比如前面一个参数的长度,所以你用不着给 Perl 提供它们。任何 其他的不同都是因为 Perl 和 C 在声明文件句柄和成功/失败值上的区别造成的。

通常,Perl 里用做系统调用封装的函数和该系统调用同名(比如 chown(2),fork(2), closedir(2),等等),都是成功时返回真,而失败时返回 undef,在随后的描述里也有提到。 这样的特性与这些操作的 C 库的接口是不同的,C 接口在失败时都返回 -1。这条规则的例外是 wait,waitpid,和 syscall。系统调用在失败的时候还设置特殊的 $!($OS_ERROR)变量。 除非故意,其他函数并不这样做。

对于那些在标量或者列表环境中都可以使用的函数,在标量环境中失败的时候通常是返回一个假值 (通常是 undef)来表明而在列表环境中通常是返回空列表。成功地执行通常是用返回一个(在 环境中)可以计算出真值的数值来表明的。

记住下面的规则:没有任何规则把一个函数在列表环境中的行为和在标量环境中的行为相互联系 起来,反之亦然。这两种情况可能是做两件完全不同的事情。

每个函数都知道它被调用的环境。同一个函数,如果它在列表环境中调用时返回一个列表,那么 它在标量环境中调用将返回它认为最合适的任何类型的数值。有些函数返回它在列表环境中返回的 列表的长度,有些操作符返回列表中的第一个数值。有些函数返回列表中最后的数值。有些函数 返回“其他”数值——如果那些东西可以通过数字或者名字找出。有些函数返回成功操作的计数。 通常,Perl 函数会干你想干的事情,除非你想要一致性。

最后一条注意:我们已经非常注意保持自己对“字节”和“字符”两个术语的使用的一致性了。 因历史原因,这些术语已经是互相混淆在一起了(并且它们自身也含糊不清)。但是当我们说 “字节”的时候,我们的意思总是一个八位字节,8位。而当我们说到“字符”的时候,我们的 意思是一个抽象的字符,“通常”是一个 Unicode 字符,它在你的字串里可能会用一个或更多个 字节代表。

不过要注意我们说的“通常”。Perl 在 use bytes 声明的范围里故意混淆了字节和字符的 概念,所以如果我们说到“字符”,那么在 use bytes 环境里时你应该把它看作一个字节, 否则,看作一个 Unicode。换句话说,use bytes 只是把字符的定义封装回到了老版本的 Perl 里的概念。因此,如果我们说一个标量 reverse 一个字符一个字符地反转一个字串,你就别问 我们这里真的意思是字符还是字节,因为答案就是,“没错,它就是干这事。”

29.1 按类分的 Perl 函数

下面是按照类别排列的 Perl 的函数和类函数关键字。有些函数在多个标题下出现。

29.2 按照字母顺序排列的 Perl 函数

后面的许多函数名字都带有许多注解。下面是它们的含义:

这里没有标记那些给它们填充了感染了的数据则返回感染数据的函数,因为大多数函数都是这样。 尤其是如果你在 %ENV 或者 @ARGV 上使用任何函数,你都会拿到感染了的数据。

那些标记着(XARG)的函数如果在要求你给它一个特定类型的参数,(比如用于 I/O 操作的 文件句柄啦,用于 bless 的引用啦等等)但是没有收到的时候,它们就会抛出一个例外。

那些标记着(XRO)的函数有时候需要修改它们的参数。如果因为该参数标记着只读标志而不能 修改,那么它们就会抛出一个例外。只读变量的例子是那些包含在模式匹配过程中捕获的数据的 特殊变量以及那些实际上是一个常量的别名的变量等。

标记着(XU)的函数可能没有在所有平台上实现。尽管许多这些函数都是用它们在 Unix C 库中 的对应函数的名字命名的,但也不要因为你运行的不是 Unix 系统就认为你不能调用任何这些 函数。它们中有许多都通过仿真实现了,甚至是那些你可能从来没想着要看到的——比如在 Win32 系统上的 fork,它在 5.6 版本的 Perl 里是可以用的。更多的有关系统相关的函数的 移植性和行为的信息,请参阅 perlport 手册页,以及任何随着你的 Perl 移植版本一起发布的 平台相关的文档。

那些抛出其他杂项例外的函数是用($@)标记的,包括抛出范围错误的数学函数,比如 sqrt(-1)。

abs
   abs VALUE
   abs

这个函数返回它的参数的绝对值。

   $diff = abs($first - $second);

注意:这里和随后的例子,好的编程风格(以及 use strict 用法)将要求你加一个 my 来声明 一个新的词法范围的变量,比如:

   my $diff = abs($first - $second);
不过,为了清晰起见,我们在大多数例子里省略 my。只是假设任何这样的变量都事先定义过了。
accept
   accept SOCKET, PROTOSOCKET
这个函数用于那些希望监听来自客户端套接字连接的服务器进程。PROTOSOCKET 必须是一个已经 通过 socket 操作符打开,并且与服务器的一个网络地址或者 INADDR_ANY 绑定的文件句柄。 它的执行会延迟到连接发生的时候,在该点 SOCKET 文件句柄被打开并且附着到新建立的连接上。 最初的 PROTOSOCKET 保持不变;它(PROTOSOCKET)唯一的用途就是克隆成一个真正的套接字。 如果调用成功,该函数返回连接上的地址,否则返回假。比如:
   unless ($peer = accept(SOCK, PROTOSOCK)) {
      die "Can't accept a connection: $!\n";
   }
在那些支持 exec 时关闭(close-on-exec)标志的系统上,将为新打开的文件描述符设置该 标志,和 $^F($SYSTEM_FD_MAX)的值决定的一样。

参阅 accept(2)。又见第十六章,进程间通讯,里的“套接字”一节里的例子。

alarm
   alarm EXPR
   alarm

这个函数在 EXPR 秒后给当前进程发送一个 SIGALRM 信号。

每次只能有一个定时器处于活跃状态。每次调用都关闭前面一个定时器,并且如果你给的 EXPR 是 0,那么就能取消所有前面设置的定时器而又不会启动一个新的定时器。它的返回值是前面 一个定时器里剩下的时间的数量。

   print "Answer me within one minute, or die: ";
   alarm(60);         # 一分钟后终止程序
   $answer = ;
   $timeleft = alarm(0);   # 清除警报
   print "You had $timeleft seconds remaining\n";

把 alarm 和 sleep 混合在一起使用通常是一个错误,因为许多系统使用 alarm(2) 系统调用 机制实现 sleep(3)。在老一些的系统上,流逝的时间可能比你声明的少上最多一秒钟,这是由于 秒计数的方式造成的。另外,一台繁忙的系统可能无法立即运行你的进程。参阅第十六章获取关于 信号处理的信息。

如果你需要比一秒的颗粒度更细的间隔,那么你可能要使用 syscall 函数访问 setitimer(2), 前提是你的系统支持这个系统调用。CPAN 模块,Timer::HiRes,也提供了用于这个目的的函数。

atan2
   atan2 Y, X

这个函数返回 -pi 和 pi 之间的 Y/X 的反正切值。一个获取 pi 的近似值的快速方法是:

   $pi = atan2(1,1) * 4;

对于正切操作,你可能要用 Math::Trig 或者 POSIX 模块里的 tan 函数,或者只是使用类似的 关系:

   sub tan { sin($_[0]) / cos($_[0]) }

bind
   bind SOCKET, NAME

这个函数把一个地址(一个名字)附着到一个由 SOCKET 文件句柄声明的已经打开的套接字上。 如果它成功了,那么返回真,否则返回假。NAME 应该是该套接字使用的类型正确的打包地址。

   use Socket;
   $port_number = 80;      # 假装我们想成为一个 web 服务器
   $sockaddr = sockaddr_in($port_number, INADDR_ANY);
   bind SOCK, $sockaddr or die "Can't bind $port_number: $!\n";

参阅 bind(2)。又见第十六章的“套接字”一节里的例子。

binmod
   binmod FILEHANDLE, DISCIPLINES
   binmod FILEHANDLE

这个函数为 FILEHANDLE 安排具有 DISCIPLINES 参数声明的语意的属性。如果省略了 DISCIPLINES,那么给该文件句柄提供二进制(或者“raw”)语意。如果 FILEHANDLE 是一个 表达式,那么将根据情况把其值当作文件句柄或者一个指向文件句柄的引用。

binmode 函数应该是在调用完 open 之后,但在对该文件句柄做任何 I/O 之前调用。重置一个 文件句柄的该模式的唯一方法是重新打开该文件,因为各种不同的纪律(模式)会把一点一滴的 数据各自存放在不同的缓冲区里。这个限制可能在将来会放松。

在古老的年代,binmode 主要是用于那些运行时间库区分文本和二进制文件的操作系统上。在那些系统上, binmode 的作用是关闭缺省的文本语意。不过,自从增加了 Unicode 的先进性之后,在所有系统上的所有 程序都一定有一些可识别的区别,即使是在 Unix 和 Mac 系统上也如此。现在,只有一种二进制 文件存在了(至少 Perl 是这么认为的),但是有许多种文本文件,但 Perl 也愿意用一种方法 来处理这些文本文件。所以 Perl 对于 Unicode 文本只有一种内部格式,UTF-8。因为有许多 不同类型的文本文件,所以文本文件在输入时常需要转换成 UTF-8,而在输出时常需要转换回 某些传统的字符集,或者 Unicode 的一些其他表现形式。你可以用纪律(DISCIPLINES)告诉 Perl 如何精确地(或者不精确地)做这些转换。(注:更准确地说,你将能用纪律做这些事情, 但是我们写本书的时候还在实现这些东西。)

比如,一个为 ":text" 的纪律将告诉 Perl 做一般性的文本处理而不用告诉 Perl 是做具体的 哪种文本处理。但象 ":utf8" 和 ":latin1" 这样的纪律就告诉 Perl 读和写哪种文本格式。 另一方面,":raw" 纪律告诉 Perl 把它的肥爪从数据上拿开,不对数据做任何额外处理。有关 纪律如何运转(或者将如何运转)的更多内容,请参考 open 函数。这个讨论的其余部分描述 没有 DISCIPLINES 参数的时候,binmode 干的事情,也就是说,binmode 的历史含义,也就 等效于:

    binmode FILEHANDLE, ":raw";

除非你另外指明了,否则 Perl 将把你新打开的文件以文本模式读写。文本模式意味着 \n (换行/新行)将是你的内部的行终止符。所有系统都用 \n 做内部的行终止符,但是它真正 代表的东西是因系统而异,因设备而异,甚至是因文件而异的,具体情况取决于你如何访问该 文件。在这样的一种传统的系统里,(包括 MS-DOS 和 VMS),你的程序看做 \n 的东西可能 不是物理上存储在磁盘上的东西。比如,该操作系统可能把在文本文件里存储成 \cM\cJ 序列, 到你在程序里显示为 \n 这样的东西,而当输出到文件中去的时候,又把你的程序里的 \n 转换 回 \cM\cJ 序列。binmode 关闭在这些系统上的这种转换工作。

如果没有 DISCIPLINES 参数,binmode 在 Unix 或者 Mac OS 里没有什么作用,它们都用 \n 结束每一行,并且把它表现为单个字符。(不过,这个字符可能是不一样的:Unix 使用 \xJ 而 老一些的 Mac 使用 \c。没关系。)

下面的例子演示了 Perl 脚本如何从一个 GIF 图象文件里读取数据并把它打印到标准输出上去。 在有些系统上,如果不做 binmode 处理,那么它就会把显示数据篡改成与数据的物理存储形式 不同的东西,所以你必须把下面程序里的两个句柄都准备成二进制模式。尽管你可以很容易地在 GIF 文件打开上使用 ":raw" 纪律,想在一个象 STDOUT 这样的提前打开了的文件句柄上做这件 事可不那么容易:

   binmode STDOUT;
   open(GIF, "vim-power.gif") or die "Can't open vim-power.gif: $!\n";
   binmode GIF;
   while (read(GIF, $buf, 1024)) {
      print STDOUT $buf;
   }

bless
   bless REF, CLASSNAME
   bless REF

这个函数告诉 Perl 由引用 REF 指向的引用物现在是一个在 CLASSNAME 包里的对象了——如果 没有声明 CLASSNAME 那么就是当前包。如果 REF 不是一个有效的引用,那么就会抛出一个例外。 为方便起见,bless 返回该引用,因为它通常是一个构造器子过程里的最后一个函数。比如:

   $pet = Beast->new(TYPE => "cougar", NAME => "Clyde");

   # 然后在 Beast.pm:
   sub new {
      my $class = shift;
      my %attrs = @_;
      my $self  = { %attrs };
      return bless($self, $class);
   }

通常你应该把对象赐福到混合大小写的 CLASSNAME 里。名字空间里所有字母都小写的名字是 Perl 保留着在内部用做 Perl 用法(编译器指示器)的。内建的类型(比如“SCALAR”, “ARRAY”,“HASH”,等等,更不用说所有类的基类,“UNIVERSAL”)都是所有字母大写, 因此你应该避免这样的包名字。

一定要确保 CLASSNAME 不是假值;把引用赐福到假包里目前还不支持,并且可能导致不可预见 的后果。

Perl 里没有 curse (诅咒)操作符并不是臭虫。(不过我们有 sin (罪恶)操作符。)参阅 第十二章,对象,看看更多有关给对象赐福的东西。

caller
   caller EXPR
   caller

这个函数返回关于当前子过程调用等方面的堆栈信息。如果没有参数,它返回包名,文件名,和 调用当前子过程的程序的行号:

   ($package, $filename, $line) = caller;

下面是一个极为挑剔的函数的例子,它利用了第二章,集腋成裘,里描述特殊记号 PACKAGE 和 __FILE__:

   sub careful {
      my ($package, $filename) = caller;
      unless ($package eq __PACKAGE__ && $filename  eq __FILE__) {
         die "You weren't supposed to call me, $package!\n";
      }
      print "called me safely\n";
   }

   sub safecall {
      careful();
   }

如果带参数调用,caller 把 EXPR 算做从当前的堆栈位置向回退的桢数。比如,参数 0 意思是 当前堆栈桢,1 意思是该调用者,2 意思是调用者的调用者,以此类推。该函数还汇报下面显示的 附加信息:

   $i = 0;
   while (($package, $filename, $line, $subroutine,
      $hasargs, $wantarray, $evaltext, %is_require,
      $hints, $bitmask) = caller($i++) _
   {
      ...
   }

如果该堆栈桢是一个子过程调用,那么如果该子过程有自己的 @_ 数组(不是那个它从调用者那里 借过来的)那么 $hasargs 就是真。否则,如果该桢不是一次子过程调用,而是一个 eval,那么 $subroutine 可能是 "(eval)"。如果是这样,那么设置附加的元素 $evaltext 和 $is_require:如果该桢是由 require 或者 use 语句创建的那么 $is_requier 为真,而 $evaltext 包含 eval EXPR 语句的文本。特别是,对于一个 eval BLOCK 语句,$filename 是 "(eval)",但 $evaltext 是未定义。(还要注意每个 use 语句都在一个 eval EXPR 桢里 创建一个 require 桢。)$hints 和 $bitmask 是内部值;除非你是仙境成员,否则不要动 它们。

还有一点更深一层的东西:caller 还把数组 @DB::args 设置为传递到给定堆栈桢的参数—— 不过只有在它是被从 DB 包里面调用的才做这个工作。参阅第二十章,Perl 调试器。

chdir
   chdir EXPR
   chdir

如果可能,这个函数改变当前进程的工作目录到 EXPR。如果省略 EXPR,则使用调用者的家目录。 这个函数成功时返回真,否则返回假。

   chdir "$prefix/lib" or die "Can't cd to $prefix/lib: $!\n";

又见 Cwd 模块,在第三十二章,标准模块,里描述,它可以自动跟踪你的当前目录。

chmod
   chmod LIST

这个函数改变一列文件的权限。列表的第一个元素必须是一个数字模式,就象在 chmod(2) 系统 调用里的一样。该函数返回成功改变了的文件的数目。比如:

    $cnt = chmod 0755, 'file1', 'file2';

会把 $cnt 设置为 0,1,或 2,具体是多少取决于改变的文件的数目。成功是通过没有错误来 表示的,而不是通过实际的修改的数目,因为一个文件可能会拥有和操作之前相同的模式。一个 错误可能意味着你缺乏修改文件模式的足够的权限,你可能既不是文件的所有者也不是超级用户。 检查 $! 看看失败的实际原因是什么。

下面是更多的一些典型用法:

   chmod(0755, @executables) == @executables
      or die "couldn't chmod some of @executables: $!";

如果你想知道是哪个文件不允许这样的修改,使用象下面这样的代码:

   @cannot = grep {not chmod 0755, $_} 'file1', 'file2', 'file3';
   die "$0: could no chmod @cannot\n" if @cannot;

这个惯用法使用 grep 函数选择列表里那些 chmod 函数对之操作失败的元素。

如果使用非文本模式数据,那么你可能需要用 oct 函数把一个八进制字串转换成一个数字。这 就是为什么 Perl 不会因为一个字串有一个前导 "0" 而就假定它包含一个八进制数字。

   $DEF_MODE = 0644;      # 这里不能用引号!
   PROMPT: {
      print "New mode? ";
      $strmode = ;
         exit unless defined $strmode;      # 测试 eof
      if ($strmode =~ /^\s*$/) {            # 测试空白行
         $mode = $DEF_MODE;
      }
      elsif ($strmode !~ /^\d+$/) {
         print "Want numeric mode, no $strmode\n";
         redo PROMPT;
      }
      else {
         $mode = oct($strmode);         # 把 "755" 转换成 0755
      }
      chmod $mode, @files;
   }

这个函数与数字模式一起使用的时候很象 Unix chmod(2) 系统调用。如果你需要象 chmod(1) 命令提供的那样的符号接口,你可以看看 CPAN 上的 File::chmod 模块。

你还可以从 Fcntl 模块里输入 S_I* 符号常量:

   use Fcntl ':mode';
   chmod S_IRWXU | S_IRGRP | S_IXGRP | S_IXOTH , @executalbes;

有些人认为上面这个比 0755 的可读性更好。你可以自己试试。

chmop
   chomp VARIABLE
   chomp LIST
   chomp

这个函数通常把一个变量里包含的字串尾部的换行符删除。它使 chop 函数(下面描述)的一个 略微安全些的版本,因为它对没有换行符的字串没有影响。更准确地说,它根据 $/ 的当前值删除 字串终止符,而不只是最后一个字符。

和 chop 不同,chomp 返回删除的字符数量。如果 $/ 是 ""(处于段落模式下),chomp 从选 出的字串里删除所有结尾的换行符。你不能 chomp 一个文本常量,只能处理变量。

比如:

   while () {
      chomp;      # 避免在最后一个字段里出现 \n
      @array = split /:/;
      ...
   }

在版本 5.6 里,chomp 的含义略微改变了一些,我们可以用输入纪律覆盖 $/ 变量的值,并且把 字串打上它们应该如何砍断的标记。这样做的优点是输入的纪律可以识别多于一种的行终止符( 比如 Unicode 段落和行分隔符),而且还能安全的 chomp 掉终止当前行的东西。

chop
   chop VARIABLE
   chop LIST
   chop

这个函数把一个字串变量的最后一个字符砍掉,并且返回砍掉的字符。chop 主要用于从一条输入 记录的尾部删除换行符,并且比使用一个子过程更高效。如果这就是你在做的事情,那么用 chomp 更安全一些,因为 chop 不管字串里的是什么都会剪短它,而 chomp 则更有选择性一些。

你不能 chop 文本常量,只能 chop 一个变量。

如果你 chop 一列 LIST 变量,那么列表中的每个字串都被剪短:

   @lines = `cat myfile`;
   chop @lines;

你可以 chop 任何是左值的东西,包括一个赋值:

   chop($cwd = `pwd`);
   chop($answer = );

上面的和下面这句是不同的:

   $answer = chop($tmp =  );   # 错误

它把一个新行放到了 $answer 里,因为 dhop 返回砍掉的字符,而不是剩下的字串(在 $tmp 里)。获取我们这里想要的结果的一个方法是用 substr:

   $answer = substr , 0, -1;

不过我们更经常的是写:

   chop($answer = );

在最常见的情况下,chop 可以用 substr 来表示:

   $last_char = chop($var);
   $last_char = substr($var, -1, 1, "" );   # 一样的东西

一旦你理解了这个等效原理,你就可以用它做更大的砍削。要砍掉多于一个字符,那么把 substr 当作左值使用,赋予一个空字串。下面的代码删除 $caravan 的最后五个字符:

   substr($caravan, -5) = " ";

这里的负数脚标令 substr 从字串的尾部而不是头部开始计算。如果你想保存这样删除的字符, 你可以使用四个参数形式的 substr,创建一个五倍的砍削:

   $tail = substr($caravan, -5, 5, "");

chown
   chown LIST

这个函数修改一列文件的所有者和组。列表的头两个元素必须是数字 UID 和 GID,顺序如前。 其中任何一个位置的 -1 的值在大多数系统上解释为把该值保留不变。该函数返回成功改变的 文件的数目。比如:

   chown($uidnum, $gidnum, 'file1', 'file2') == 2
      or die "can't chown file1 or file2: $!";

会把 $cnt 设置为 0,1,或 2,具体是何值取决于究竟有几个文件被修改(是以操作成功为准, 而不是以修改完成以后所有者是否不同为准)。下面是一个更典型的应用:

   chown($uidnum, $gidnum, @filename) == @filenames
      or die "can't chown @filenames: $!";

下面是一个接受一个用户名,为你找出该用户和组 ID,然后做 chown 的子过程:

   sub chown_by_name {
      my($user, @files) = @_;
      chown((getpwnam($user))[2,3], @files) == @files
         or die "can't chown @files: $!";
   }

   chown_by_name("fred", glob("*.c"));

不过,你可能不想象前一个函数那样修改组,因为 /etc/passwd 文件把每个用户和一个组关联 起来,即使根据 /etc/group 而言,该用户可以是许多从属组的成员也如此。一个变化的方法是 传递 -1 做为 GID,它的含义是不改变该文件的组。如果你把 -1 当作 UID 同时传递一个有效的 GID,那么你可以设置组而不修改所有者。

大多数系统上不会允许你修改文件的所有权,除非你是超级用户,尽管你可以把组改成你所在的 任何从属组。在不安全的系统里,这样的限制可以放松,但是这么做不是一个可移植的假设。在 POSIX 系统上,你可以用类似下面的方法检测应用的是哪种规则:

   use POSIX qw(sysconf _PC_CHOWN_RESTRICTED);
   # 只是测试我们是超级用户还是一个打开许可的系统
   if ($> == 0 || !sysconf(_PC_CHOWN_RESTRICTED) ) {
      chown($uidnum, -1, $filename)
         or die "can't chown $filename to $uidnum: $!";
   }

chr
   chr NUMBER
   chr

这个函数返回 NUMBER 在字符集中代表的字符。比如,chr(65) 在 ASCII 或 Unicode 中都是 “A”,而 chr(0x263a) 是一个 Unicode 的笑脸。如果想反向的 chr 功能,用 ord。

如果你喜欢用名字来声明你的字符,而不是用数字(比如,"\N{WHITE SMILING FACE}" 就是 Unicode 笑脸),参阅第三十一章,实用模块的 charnames。

chroot
   chroot FILENAME
   chroot

如果成功,FILENAME 成为当前进程的新的根目录——用“/”开头的路径名的起点。这个目录是 跨 exec 调用继承的,以及被所有 chroot 调用后 fork 出来的子进程继承。我们没有办法撤消 一次 chroot。出于安全原因,只有超级用户可以使用这个函数。下面是一些许多 FTP 服务器的 做法的近似模拟:

   chroot((getpwname('ftp'))[7])
      or die "Can't do anonymous ftp: $!\n";

这个函数在非 Unix 系统里可能不能运行。参阅 chroot(2)。

close
   close FILEHANDLE
   close

这个函数关闭与 FILEHANDLE 关联的文件,套接字,或者管道。(如果省略参数,那么它关闭 当前选定的文件句柄。)如果关闭成功它返回真,否则返回假。如果你准备马上就对 FILEHANDLE 做另外一次 open,那么你用不着关闭它,因为下一次 open 会替你关闭它。 (参阅 open)不过,对输入文件的明确的关闭重置行计数器($.),而 open 做的隐含关闭 不会做这件事情。

FILEHANDLE 可以是一个表达式,它的值可以用做一个间接的文件句柄(要么是一个真实文件句柄 名字,要么是一个指向任何可以解释为一个文件句柄对象的引用。)

如果该文件句柄来自一个管道打开,如果任何下层系统调用失败或者在管道另一端的程序退出值 非零,那么 close 将返回假。对于后面一种情况,close 强制 $!($OS_ERROR)为零。所以 如果一个在管道上的 close 返回了一个非零值,那么可以检查 $! 的值判断问题来自管道本身 (非零值)还是来自管道对端的程序(零值)。不管哪种情况,$?($CHILD_ERROR)都包含与 管道另一端的命令相关联的等待状态值(参阅在 system 里它的解释)。比如:

   open(OUTPUT, '| sort -rn | lpr -p')   # 输出给 sort 和 lpr
      or die "Can't start sortlpr pipe: $!";
   print OUTPUT @lines;            # 把内容打印到输出
   close OUTPUT               # 等待 sort 结束
      or warn $! ? "Syserr closing sortlpr pipe: $!"
             : "Wait status $? from sortlpr pipe";

用 dup(2) (复制)管道的方法制作的文件句柄被当作一个普通的文件句柄,所以在那个文件 句柄上的 close 不会等待子进程。不过你关闭最初的文件句柄的时候就必须等待子进程。比如:

   open(NETSTAT, "netstat -rn |")
      or die "can't run netstat: $!";
   open(STDIN, "<&NETSTAT")
      or die "can't dup to stdin: $!";

如果你关闭上面的 STDIN,那么不会有等待发生,但是如果你关闭 NETSTAT,那么就有。

如果你自己打理了一个已退出的管道子进程,那么关闭就会失败。如果你有一个自己的 $SIG{CHLD} 句柄,在管道子进程退出的时候触发,或者你故意在从 open 调用返回的进程 ID 上调用 waitpid,那么就有可能发生关闭失败的情况。

closedir
   closedir DIRHANDLE

该函数关闭一个 opendir 打开的目录并且返回该操作的成功。参阅 opendir 里的例子。 DIRHANDLE 可以是一个表达式,它的值可以用做一个间接目录句柄,通常是真实目录句柄的名字。

connect
   connect SOCKET, NAME

这个函数初始化一个与另外一个进程的连接,该进程正在一个 accept 上等待。如果成功该函数 返回真,否则返回假。NAME 应该是一个类型正确的该套接字打包了的网络地址。比如,假设 SOCK 是一个前面已经创建了的套接字:

   use  Socket;
   
   my ($remote, $port) = ("www.perl.com", 80);
   my $destaddr = sockaddr_in($port, inet_aton($remote));
   connect SOCK, $destaddr
      or die "Can't connect to $remote at port $port: $!";

为了断开一个套接字,可以使用 close 或者 shutdown 之一。参阅在第十六章里的“套接字” 一节里的例子,参阅 connect(2)。

cos
   cos EXPR
   cos

这个函数返回 EXPR 的余弦值(用弧度表示)。比如,下面的脚本将打印一个用角度计量的余弦 表:

#这是个实现角度到弧度的偷懒的方法

   $pi = atan2(1,1) * 4;
   $piover180 = $pi/180;
   
   # 打印表格。
   for ($deg = 0; $deg <= 90; $deg++) {
      printf "%3d %7.5f\n", $deg, cos($deg * $piover180);
   }

如果需要反余弦操作,你可以使用来自 Math::Trig 或者 POSIX 模块的 acos() 函数,或者 使用下面的关系:

   sub acos { atan2( sqrt(1 - $_[0] * $_[0]), $_[0] ) }

crypt
   crypt PLAINTEXT, SALT

这个函数用和 crypt(3) 完全一样的方法计算一个字串的单向散列。这个功能对于检查口令文件 中是否有极烂的口令很有用,(注:只有那些具有诚实的意图的人才可以做这些事情。)尽管你 真正想干的事情是防止人们在一开始就使用糟糕的口令。

crypt 是有意做成一个单向函数的,和打破鸡蛋炸煎鸡蛋一个道理。目前没有(已知)的办法 解密一个加了密的口,只能用穷举法的强行猜测方法。

当验证一个现有的加密字串的时候,你应该用该加密的文本做 SALT(象 crypt($plain, $crypted) eq $crypted)。这样就令你的代码和标准 crypt 兼容,并且还 可以有更多奇特的实现。

当你选择一个新的 SALT 的时候,你最少需要创建一个两字符长的随机字串,它的字符来自集合 [./0-9A-Za-z](类似 join ' ', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64])。更早的 crypt 的实现只需要 SALT 的头两个字符,不过我们现在认为那些只给出 头两个字符的代码是不能移植的。参阅你本地的 crypt(3) 手册页获取更多有趣的细节。

下面是一个例子,它可以证实哪个运行此程序的人知道他们自己的口令:

   $pwd = (getpwuid ($<))[1];   # 假设我们在 Unix 上。

   system "stty -echo";      # 或者看看 CPAN 上的 Term::ReadKey
   print "Password: ";
   chomp($word = );
   print "\n";
   system "stty echo";

   if (crypt($word, $pwd) ne $pwd) {
      die "Sorry...\n";
   } else {
      print "ok\n";
   }

当然,向任何问你索要口令的东西键入你的口令都是不明智的举动。

影子口令文件比传统的口令文件稍微更安全些,并且如果你想访问它们的话那么你就必须是超级 用户。因为很少的几个程序才能运行在这样强大的权限下,所以你可能需要让程序维护它自己独立 的认证系统,方法是把 crypt 字串保存在与 /etc/passwd 和 /etc/shadow 不同的地方。

crypt 函数不适合做大量数据的加密,更重要的是因为你不能取回数据。查看你喜爱的 CPAN 的 by-module/Crypt 和 by-module/PGP 目录寻找一些可能有用的模块。

dbmclose
   dbmclose HASH

这个函数打破在一个 DBM (数据库管理)文件和一个散列之间的绑定。dbmclose 实际上只是 一个带有合适参数的 untie 的调用,提供它只是为了保持与古老的 Perl 版本的向下兼容。

dbmopen
   dbmopen HASH, DBNAME, MODE

这个函数把一个 DBM 文件和一个散列(也就是说,一个关联性数组)捆绑在一起。(DBM 的意思 是数据库管理,由一个 C 库过程组成,它允许你通过散列算法随机地访问记录。)HASH 是散列的 名字(包括 %)。DBNAME 是数据库的名字(没有 .dir 或者 .pag 扩展)。如果该数据库并不 存在并且声明了一个有效的 MODE,那么该数据库就带着声明的 MODE 的保护(模式)创建,就象 umask 修改的那样。为了避免数据库不存在的时候的创建动作,你可以声明 MODE 为 undef, 并且如果该函数无法找到一个现有数据库,那么将返回假。在 dbmopen 之前赋予散列的值是不能 访问的。

dbmopen 函数实际上只是一个带有合适参数的 tie 的调用,提供它只是为了保持与早期的 Perl 版本的向下兼容。你可以通过直接使用 tie 接口或者通过在调用 dbmopen 之前装载合适的模块 来控制自己使用哪个 DBM 库。下面是一个例子,它可以在一些 DB_File 的版本和你的 Netscape 浏览器版本类似的系统上运行:

   use DB_File;
   dbmopen(%NS_Hist, "$ENV{HOME}/.netscape/history.dat", undef_
      or die "Can't open netscape history file: $!";

   while (($url, $when) = each %NS_Hist) {
      next unless defined($when);
      chop($url, $when);      # 删除尾部的空字节
      printf "Visited %s at %s.\n", $url,
         scalar(localtime(unpack("V",$when)));
   }

如果你没有 DBM 文件的写权限,那么你就只能读取散列变量,而不能设置它们。如果你想知道 自己是否能写,你可以用文件测试,比如 -w $file,或者尝试在一个 eval{} 里面设置一个假 散列记录,而 eval{} 会捕获例外。

如果在很大的 DBM 文件上使用,keys 和 values 这样的函数可能会返回巨大的列表值。你可能 会愿意使用 each 函数叙述特大 DBM 文件,这样就不会一次把所有东西都装到内存里。

与 DBM 文件绑定的散列和你使用的 DBM 包有同样的局限性,包括你可以放到一个桶里的数据 数量限制。如果你坚持使用短键字和数值,那么这一点很难成为问题。又见第三十二章的 DB_File 模块。

你应该记在心里的另外一件事情是许多现有的 DBM 数据库包含空结尾的键字和数值,因为它们是 以 C 程序的概念考虑问题的。Netscape 历史文件和老的 sendmail 别名文件就是例子。只需要 在取出数据的时候用 "$key\0",然后从数值里删除空。

   $alias = $aliases{"postmaster\0"};
   chop $alias;   # 删除空

目前没有内建的方法锁住一个一般性的 DBM 文件。有些人认为这是一只臭虫。GDBM_File 模块 的确试图以整个文件为粒度提供锁定的机制。如果你有问题,那么最好还是使用一个独立的锁 文件。

defined
   defined EXPR
   defined

这个函数返回一个布尔值,表明 EXPR 是否是一个已经定义的数值。你处理的大多数数据都是 已经定义的,但是一个标量如果不包含有效的字串,数字,或者引用值,那么就说它是未定义的 数值,或者简称 undef。把一个标量变量初始化为一个特定的值将定义它,并且将一直保持已 定义状态直到你给它赋一个未定义值或者在该变量上明确调用 undef 函数。

许多操作在异常条件下都返回 undef,比如文件末尾,使用一个未初始化的变量的数值,一个 操作系统错误等等。因为 undef 只是假值的一种,所以简单布尔测试并不能分别 undef,数字 零,和一个单字符字串,“0”——它们都等于假。当你正在进行的操作可能返回一个真正的 空字串时,defined 函数可以让你可以区分未定义的空字串和定义了的空字串。

下面是一个测试一个来自某散列的标量值的片段:

   print if defined $switch{D};

当象上面这样在散列元素上使用的时候,defined 只告诉你该数值是否定义,而不会告诉你该 键字在散列里是否有记录。有可能出现这种情况:有一个键字,其值是未定义的;但键字本身仍然 存在。使用 exists 判断某个散列键字是否存在。

在下面一个例子里,我们利用了有些操作在用光你的数据之后会返回未定义数值的传统:

   print "$val\n" while defined($val = pop(@ary));

而在下面这个例子里,我们使用用于检索系统用户信息的 getpwent 函数获取同样的信息.

   setpwent();
   while(defined($name = getpwent())) {
      print "<<$naem>>\n";
   }
   endpwent();

下面的例子从那些可能返回合法假值的系统调用中获取错误返回:

   die "Can't readlink $sym: $!"
      unless defined($value = readlink $sym);

你还可以用 defined 检查一个子过程是否已经被定义。这样就有可能避免把一个不存在的子过程 给破坏掉(或者是已经声明但尚未定义的子过程。):

   indir("funcname", @arglist);
   sub indir {
      my $subname = shift;
      no restrict 'refs';      #这样我们就可以使用间接地使用 subname
      if (defined &$subname) {
         &$subname(@_);   # 或者 $subname->(@_);
      }
      else {
         warn "Ignoring call to invalid function $subname";
      }
   }

在聚集(散列和数组)上使用 defined 是不允许的。(以前这么做是报告该聚集的内存是否已经 分配。)你可以使用简单的布尔测试来检查该数组或散列是否含有元素:

   if (@an_array) { print "has array elements\n" }
   if (%a_hash) { print "has hash members\n" }
又见 undef 和 exists。

delete
   delete EXPR

这个函数从指定散列或者数组删除一个元素(或者一段元素)。(如果你想删除一个文件请参阅 unlink。)被删除的元素会按照声明它们的顺序返回,不过对捆绑的变量(比如 DBM 文件)不能 保证这一点。在删除操作之后,对任何已经删除的键字或者索引调用 exists 函数都会返回假值。 (相比之下,在 undef 函数之后,exists 函数仍然返回真值,因为 undef 函数只是撤消该 元素的数值的定义,并没有删除该元素本身。)

从 %ENV 散列删除则修改环境。从一个与(可写的)DBM 文件绑定的散列删除记录将导致从 DBM 文件把该散列删除掉。

在过去的版本里,你只能从散列里删除记录,但是到了 Perl 5.6,你也可以从一个数组里删除 东西。从一个数组删除记录导致在声明位置的元素完全回复到未初始化的状态,但是这样的删除 并不弥合所造成的裂缝,因为那样做会导致所有随后的记录的位置的变化。你可以使用 splice 实现这样的目的。(不过,如果你删除了数组的最后一个元素,数组的尺寸将减一(或者更多, 取决于倒数第二位的元素(如果存在的话)的位置。))

EXPR 可以是任意复杂,前提是其最终操作是一个散列或者一个数组查找:

   # 设置一个散列数组的数组
   $dungeon[$x][$y] = \%properties;
   
   # 从散列中删除一个属性
   delete $dungeon[$x][$y]{"OCCUPIED"};

   # 一次从散列中删除三个属性
   delete @{ $dungeon[$x][$y] }{ "OCCUPIED", "DAMP", "LIGHTED" };

   # 从数组中把 %properties 的引用删除
   delete $dungeon[$x][$y];

下面的本机例子低效地删除所有 %hash 中的值:

   foreach $key (keys %hash) {
      delete $hash{$key};
   }

下面这个一样:

    delete @hash{keys %hash};

但上面两个都比给散列赋予一个空列表或者解除它的定义来得慢:

   %hash = ();      # 彻底清空 %hash
   undef %hash;      # 忘记 %hash 曾经存在过。
类似的是数组的删除:
   foreach $index (0 .. $#array) {
      delete $array[$index];
   }
和:
   delete @array[0 .. $#array];
都比下面的低效:

   @array = ();      # 彻底清空 @array
   undef @array;      # 忘记 @array 曾经存在过

die
   die LIST
   die

在 eval 之外,这个函数把 LIST 里的数值连接起来打印到 STDERR 并且带着当前的 $!(C 库 的 errno 变量)的值退出。如果 $! 为 0,它就带着 $?>>8 值(这个值是从 system,wait, 管道 close,或者 `commend` 退出的最后一个子进程的状态)退出。如果 $?>>8 为 0 ,那么 该函数带着 255 退出。

在一个 eval 里面,该函数把 $@ 变量设置为将要生成的错误信息,然后退出 eval,而 eval 返回 undef。因此 die 函数可以用于抛出命名例外,而这个命名例外可以在程序的更高层捕获。 参阅本章稍后的 eval。

如果 LIST 是一个单对象引用,那么就假设该对象是一个例外对象,并且象在 $@ 里的例外那样 毫不修改地返回。

如果 LIST 为空并且 $@ 已经包含一个字串值(通常是来自前面一次 eval),那么就在该字串 后面附加“\t ...propagated”再次使用。这样做有益于传播(抛出)例外:

   eval { ... };
   die unless $@ =~ /Expected exception/;

如果 LIST 为空并且 $@ 已经包含一个例外对象,则调用 $@->PROPAGATE 方法来判断该例外 应该如何传播。

如果 LIST 为空并且 $@ 也为空,那么使用字串“Died”。

如果 LIST 的最后一个值不是以换行符结尾(而且你不是在传递一个例外对象),则在该信息后面 附加当前脚本文件名,行号,以及输入行数(如果有),然后再附加一个换行符。提示:有时候在 你的信息上附加", stopped" 会令它更有意义,因为后面会附加 "at scriptname line 123" 这样的东西。假设你正在运行一个叫 canasta 的脚本;看看下面两种退出方法的区别:

   die "/usr/games is no good";
   die "/usr/games is no good, stopped";

它们分别输出:

   /usr/games is no good at canasta line 123.
   /usr/games is no good, stopped at canasta line 123.

如果你希望自己的错误信息报告文件名和行数,使用特殊标记 _ FILE _ 和 __ LINE __:

   die '"', __FILE__, '", line ', __LINE__, ", phooey on you!\n";

这句代码生成下面这样的东西:

   "canasta", line 38, phooey on you!

再说另外一种风格——看看下面两个相等的例子:

   die "Can't cd to spool: $!\n"   unless chdir '/usr/spool/news';

   chdir '/usr/spool/news'   or die "Can't cd to spool: $!\n";

因为重要的部分是 chdir,所以通常会用第二种形式。

又见 exit,warn,%SIG,和 Cary 模块。

do (block)
   do BLOCK

do BLOCK 形式执行在 BLOCK 里的语句序列并且返回在该块里计算的最后一个表达式的值。如果 用一个 while 或者 until 语句修饰词修饰,那么 Perl 在测试循环条件之前执行一次 BLOCK。 (在其他语句上,循环修饰词先测试循环条件。)do BLOCK 本身并不算一个循环,所以循环控制 语句 next,last,或 redo 不能用于离开或者重新运行该块。参阅第四章,语句和声明,里的 “光块”一节获取绕开的办法。

do(file)
   do FILE

do FILE 形式把 FILE 的值当作一个文件名使用,并且把该文件的内容当作一个 Perl 脚本运行。 它的主要用途是(或者最好是)从一个 Perl 子过程库中包含子过程,所以:

   do 'stat.pl';

更象:

   scalar eval `cat stat.pl`;   # 在 Windows 里是 `type stat.pl`

只不过是 do 更高效更紧凑一些,它还跟踪当前文件名用于错误信息,扩展所有在 @INC 数组里 的目录,并且如果找到该文件还会更新 %INC。(参阅第二十八章,特殊名字。)另外一个区别是 用 do FILE 执行的代码看不到在闭合范围里的词法,而在 eval FILE 里的代码却可以看到。 不过,它们都是在每次调用该文件的时候重新分析它——因为你可能不想在一个循环里用这个 语句,除非文件名本身每循环一圈都改变一次。

如果 do 无法读取该文件,那么它返回 undef 并且把 $! 设置为错误。如果 do 可以读取该 文件但无法编译它,那么它返回 undef 并且在 $@ 里设置一个错误信息。如果该文件成功编译, 那么 do 返回所计算的最后一个表达式的值。

库模块的包含(它们都有强制的 .pm 后缀)最好是用 use 和 require 操作符来干,它们也 做错误检查,并且如果有问题地话它们抛出一个例外。它们还有其他好处:它们可以避免重复 装载,有助于面向对象的编程,并且给编译器提供函数原形的提示。

但是 do FILE 在一些方面还是很有优势的,比如从程序配置文件中读取数据。可以用下面的方法 做手工的错误检查:

   # 读进 config 文件:先系统,后用户
   for $file ("/usr/share/proggie/defaults.rc",
      "$ENV{HOME}/.someprogrc")
   {
      unless ($return = do $file) {
         warn "couldn't parse $file: $@ if $@;
         warn "couldn't do $file: $!"   unless defined $return ;
         warn "couldn't run $file"      unless $return;
      }
   }

一个长时间运行的守护进程可以周期性地检查它的配置文件的时间戳记,如果该文件自上次读取 之后改变过,那么该守护进程可以用 do 重新装载该文件。这种任务用 do 要比用 require 或者 use 更加干净。

do (subroutine)
   do SUBROUTINE(LIST)

do SUBROUTINE(LIST) 是一个已经废弃了的子过程调用的方法。如果 SUBROUTINE 没有定义那么 抛出一个例外。参阅第六章,子过程。

dump
   dump LABEL
   dump

这个函数导致一次立即的核心倾倒。它的主要用途是你可以用 undump 程序(还没提供)在你的 程序开头刚刚完成所有变量初始化之后,把你倾倒的核心转换成一个可执行文件。而当这个新的 二进制文件执行的时候,它将以执行一个 goto LABEL(受所有 goto 自己的限制)开始。把它 想象成一个在重生和核心倾倒之间的一个 goto。如果省略了 LABEL,那么程序从顶部重新开始。 警告:任何在倾倒时已经打开的文件在该程序重生时将不再打开,这有可能导致 Perl 这边的 混乱。又见第十九章,命令行接口,里的 -u 命令行选项。

现在,这个函数基本上已经过时了,部分原因是想在一般情况下把核心文件转换成可执行文件是 非常困难的,还有是因为各种生成可移植的字节码和可编译的 C 代码的编译器后端逐渐超越了它。

如果你试图通过使用 dump 来加快你的程序的速度,那么请先检查一下在第二十四章,普通实践, 里的关于效率的讨论,以及第十八章,编译,里的 Perl 本机代码生成器的讨论。你可能还应该 考虑自动装载和自装载,它们至少能让你的程序看起来快一些。

29.2.30 each

这个以一次一个键字/数值对的方式遍历一个散列。如果在列表环境里调用它,each 返回一个两个 元素的列表,该列表包含散列中下一个元素的键字和数值,这样你就可以逐一遍历它们。如果在 标量环境里调用,each 只是返回散列中下一个元素的键字。如果散列已经全部读取完了,那么 返回一个空列表,如果你给这个空列表赋值,那么在标量环境中会生成一个假值。下面是典型的 用法,使用预定义的 %ENV 散列:

while(($key, $value) = each %ENV) { 
    print "$key =$value\n"; 
} 

在散列内部,它以一种看上去是随机的顺序维护它自己的记录。each 可以遍历这个序列是因为 每个散列都记得上一次返回的是哪条记录。这个序列的实际的顺序可能在将来的 Perl 版本里会 改变,但有一点可以保证,就是 keys(或者 values)函数在同一个(未修改)的散列上生成的 顺序是一样的。

每个散列都有一个遍历器,由在该程序里所有的 each,keys,和 values 函数调用共享;该遍历 器可以通过从散列里读取所有元素来重置,或者通过计算 keys %hash 或 values %hash 来 重置。如果你在遍历散列的过程中删除了元素,那么后果还没有很好的定义:记录可能会被忽略 也可能被重复。

又见 keys,values,和 sort。

29.2.31 eof

如果下一次对 FILEHANDLE 的读取返回文件结束(end-of-file)或者是 FILEHANDLE 没有打开, 那么这个函数将返回真。FILEHANDLE 可以是一个表达式,其值给出真正的文件句柄,也可以是 一个指向一个文件句柄对象或者类似的东西的引用。一个没有参数的 eof 为最后一次文件读动作 返回文件结束状态。一个带空圆括弧对 () 的 eof() 测试 ARGV 文件句柄(最常见的就是 <> 里的空文件句柄)。因此,在一个 while (<>) 循环里,一个带圆括弧的 eof() 只是检测一组 文件中的最后一个文件的文件结束。用 eof(没有圆括弧)在 while (<>) 循环里检查每个文件 的文件结束。比如,下面的代码在最后一个文件的最后一行前面插入一个划线:

   while (<>) {
      if (eof()) {
         print "-" x 30, "\n";
      }
      print;
   }

而下面这个脚本为每个输入文件重置行计数:

   # 为每个输入文件重置行计数
   while (<>) {
      next if /^\s*#/;   # 忽略注释
      print "$.\t$_";
   } continue {
      close ARGV if eof;   # 不是 eof()!
   }

类似 sed 程序里的“$”,eof 会显示行数范围。下面是一个打印从 /parrern/ 到每个输入 文件结尾之间的行的脚本:

   while (<>) {
      print if /pattern/ .. eof;
   }

这里,触发器操作符(..)为每一行进行模式匹配。在模式匹配上之前,该操作符返回假。当它 最终匹配上的时候,该操作符开始返回真,导致行的打印输出。当 eof 操作符最终返回真(在 被检查的文件的结尾),该触发器操作符被重置,并且开始为 @ARGV 里的下一个文件返回假。

警告:eof 函数读取一个字节然后用 ungetc(3) 把它退回输入流中,所以在交互的环境中没有 什么用。实际上,有经验的 Perl 程序员很少使用 eof,因为各种输入操作符的行为 while 循环里已经很礼貌了。参阅第四章里的 foreach 的讨论。

29.2.32 eval

eval 关键字在 Perl 里起两种不同的但相关的作用。这些目的是用两种形式的语法来表现的, eval BLOCK 和 eval EXPR。第一种形式捕获那些致命的运行时例外(错误),类似于 C++ 或 Java 里的 “try 块”。第二种形式在运行时实时地编译和执行一小段代码,并且也和第一种 形式一样捕获任何例外。但是第二种形式比第一种形式运行的速度慢很多,因为它每次都要分析 该字串。另外,它也更通用。不管你在那里使用,eval 都是一个在 Perl 里做全部例外处理的 好地方。

两种形式的 eval 所返回的值都是它计算的最后一个表达式的值,这一点和子过程一样。类似的, 你可以用 return 操作符从 eval 的中间返回一个数值。提供返回值的表达式是在空,标量, 或者列表环境中计算的,具体哪种环境是由 eval 本身所处的环境决定的。参阅 wantarray 获取如何判断计算环境的信息。

如果有一个可捕获的错误存在(包括任何由 die 操作符生成的),eval 返回 undef 并且把错误 信息放到 $@ 里。如果没有错误,Perl 保证把 $@ 设置为空字串,所以你稍后可以很可靠地做 错误检查。一个简单的布尔测试就足够了:

   eval { ... };      # 捕获运行时错误
   if ($@) { ... }   # 处理错误

eval BLOCK 形式是在编译时做语法检查的,所以它的效率相当高。(熟悉慢速的 eval EXPR 形式的人们可能会被这个问题搞糊涂。)因为在 BLOCK 里的代码是和周围的代码同时编译的, 所以这种形式的 eval 不能捕获语法错误。

eval EXPR 形式可以捕获语法错误是因为它在运行时分析代码。(如果分析失败,它象平常一样 在 $@ 里放分析错误。)另外,它把 EXPR 的值当作一小段 Perl 程序执行。这段代码是在当前 Perl 程序的环境中执行的,这就意味着它可以从包围的范围里看到任何词汇闭合域,并且在 eval 完成之后,任何非局部变量设置仍然有效,就象子过程调用或者格式定义一样。eval 的 代码是当作一个块看待的,所以任何在 eval 里定义的局部范围的变量都只能持续到 eval 结束。 (参阅 my 和 local。)和任何块里的代码一样,最后的分号不是必须的。

下面是一个简单的 Perl shell。它提示用户输入任意 Perl 代码字串,编译并执行该字串,并且 打印出发生的任何错误:

   print "\nEnter some Perl code: ";
   
   while () {
      eval;
      print $@;
      print "\nEnter some more Perl code: ";
   }

下面是用 Perl 表达式做大批文件改名的一个 rename 程序:

   #! /usr/bin/perl
   # rename - change filenames
   $op = shift;
   for (@ARGV) {
      $was = $_;
      eval $op;
      die if $@;
      # 下一行调用内建函数,而不是同名的脚本
      rename($was, $_) unless $was eq $_;
   }

你要这样用这个程序:

   $rename 's/\.orig$//'         *.orig
   $rename 'y/A-Z/a-z/ unless /^Make/'   *
   $rename '$_ .= ".bad"'         *.f

因为 eval 捕获那些致命错误,所以它可以用来判断某种特性(比如 fork 和 symlink)是否 实现之类的东西。

因为 eval BLOCK 是在编译时做语法检查的,所以任何语法错误都提前报告。因此,如果你的 代码不会变化,并且 eval EXPR 和 eval BLOCK 都完全符合你的要求,那么 BLOCK 形式 好一些。比如:

   # 零除零不是致命错误
   eval { $answer = $a / $b; };   warn $@ if $@;

   # 一样的东西,但是如果多次运行就没有那么高效了
   eval '$answer = $a / $b';      warn $@ if $@;

   # 一个编译时语法错误(不捕获)
   eval { $answer = };      # 错

   # 一个运行时语法错
   eval '$answer =';         # 设置 $@

这里,在 BLOCK 里的代码必须是合法的 Perl 代码,这样它才能通过编译阶段。在 EXPR 里的 代码直到运行时才检查,所以要到运行时它才导致错误的发生。

eval BLOCK 的块并不是循环,所以 next,last,或 redo 这样的循环控制语句并不能用于离开 或者重启该块。

29.2.33 exec

exec 函数结束当前程序的运行并且执行一条外部命令并且决不返回!!!如果你希望在该命令 退出之后恢复控制,那么你应该使用 system。exec 函数只有在该命令不存在以及该命令是直接 执行而没有通过你的系统的命令行 shell(下面讨论)执行的时候才失败并返回假。

如果只有一个标量参数,那么 exec 检查该参数是否 shell 的元字符。如果找到元字符,那么它 代表的所有参数都传递给系统的标准命令行解释器(在 Unix 里是 /bin/sh)。如果没有这样的 元字符,那么该参数被分裂成单词然后直接执行,出于效率考虑,这样做绕开了所有 shell 处理 的过荷。而且如果该程序没有退出,这样也给你更多错误恢复的控制。

如果在 LIST 里有多于一个参数,或者如果 LIST 是一个带有超过一个值的数组,那么就决不会 使用系统的 shell。这样也绕开了 shell 对该命令的处理。在参数中是否出现元字符并不影响 这个列表触发特性,这么做也是有安全考虑的程序的比较好的做法,因为它不会把自己暴露在潜在 的 shell 逃逸之中。

下面的例子令当前运行的 Perl 程序用 echo 程序代替自身,然后它就打印出当前的参数列表:

   exec 'echo', 'Your arguments are: ', @ARGV;

下面这个例子显示了你可以 exec 一个流水线,而不仅仅是一个程序:

   exec "sort $outfile | uniq"
      or die "Can't do sort/uniq: $!\n";

通常,exec 从不返回——就算它返回了,它也总是返回假,并且你应该检查 $! 找出什么东西 出错了。要注意的是,在老版本的 Perl 里,exec(和 system)并不刷新你的输出缓冲,所以 你需要在一个或更多个文件句柄上通过设置 $| 打开命令缓冲功能以避免在 exec 的情况下丢失 输出,或者在 system 的情况下打乱了输出顺序。在 Perl 5.6 里情况大致如此。

如果你让操作系统在一个现有的进程里运行一个新的程序(比如 Perl 的 exec 函数做的这样), 你要告诉系统要执行的程序在哪里,但是你也告诉了这个新的程序(通过它的第一个参数)是什么 程序执行了它。习惯上,你告诉它的名字只是该程序的位置的一个拷贝,但这么做不是必须的, 因为在 C 语言的级别上,有两个独立的参数。如果这个名字不是拷贝,那么你就可能看到奇怪的 结果:这个新程序认为自己是以一个和它所在的实际路径名完全不同的名字运行的。通常这样对 那些满腹狐疑的程序来说没什么问题,但有些程序的确关心自己的名字,并且根据自己的名字的 变化会有不同的性格。比如,vi 编辑器会看看自己是作为“vi”还是作为“view”调用的。 如果作为“view”,那么它就自动打开只读模式,就好象它是带着 -R 命令行选项调用的一样。

这个时候就是 exec 的可选 PATHNAME 参数起作用的地方了。从语意上来看,它放在间接对象的 位置,就好象 print 和 printf 的文件句柄一样。因此,它并不需要在后面有一个对象,因为 它实际上不是参数列表的一部分。(从某种意义上来说,Perl 与操作系统采取的方法正相反, 它认为第一个参数是最重要的,并且如果它不同那么就让你修改路径名。)比如:

    $editor = "/usr/bin/vi";
   exec $editor "view", @files   # 触发只读模式
      or die "Couldn't execute $editor: $!\n";

和任何其他间接对象一样,你也可以用一个包含任意代码的块代替上面这个保存程序名的简单 标量,这样就可以把前面这个例子简化为:

   exec { "/usr/bin/vi" } "view" @files      # 触发只读模式
      or die "Couldn't execute $editor: $!\n";

如前所述,exec 把一个离散的参数列表当作一个它应该绕开 shell 处理的标志。不过,仍然有 一个地方可能把你拌倒。exec 调用(以及 system)不能区别单个标量参数和一个只有一个 元素的列表。

   @args = ("echo surprise")   # 只有一个元素在列表里
   exec @args         # 仍然可能有 shell 逃逸
      or die "exec: $!";   # 因为 @args == 1

为了避免这种情况,你可以使用 PATHNAME 语法,明确地把第一个参数当路径名复制,这样就 强制其他的参数解释成一个列表,即使实际上只有一个元素:

    exec { $args[0] } @args   # 就算是只有一个元素的列表也安全了
      or die "can't exec @args: $!";

第一个没有花括弧的版本,运行 echo 程序,给它传递“surprise”做参数。第二个版本不是 这样——它试图运行一个字面上叫 echo surprise 的程序,但找不到(我们希望如此),然后 把 $! 设置为一个非零值以表示失败。

因为 exec 函数通常是紧跟在 fork 之后调用的,所以它假定任何原先一个 Perl 进程终止的 时候要发生的事情都被忽略。在 exec 的时候,Perl 不会调用你的 END 块,也不会调用与任何 对象相关的 DESTROY 方法。否则,你的子进程结束的时候会做那些你准备在父进程里做的清理 工作。(我们希望在现实生活中就是如此。)

因为把 exec 当作 system 用是一个非常普遍的错误,所以如果你带着流行的 -w 命令行开关 运行,或者你用了 use warnings qw(exec syntax) 用法的时候,如果 exec 后面跟着的语句 不是 die,warn,或则 exit,那么 Perl 就会警告你。如果你真的想在 exec 后面跟一些其他 的语句,你可以使用下面两种风格之一以避免警告:

   exec ('foo)   or print STDERR "couldn't exec foo: $!";
   { exec ('foo') };       print STDERR "couldn't exec foo: $!";

正如上面的第二行显示的那样,如果调用 exec 的时候是一个块里的最后一条语句,那么就可以 免于警告。

又见 system。

29.2.35 exists

如果所声明的散列键字或者数组索引在它的散列或者数组中存在,那么这个函数返回真值。它不在 乎对应的数值是真还是假,或者该值是否定义。

print "True\n"      if         $hash{$key};
print "Defined\n"   if defined $hash{$key};
print "Exists\n"    if exists  $hash{$key};

print "True\n"      if         $array[$index];
print "Defined\n"   if defined $array[$index];
print "Exists\n"    if exists  $array[$index];

一个元素只有定义后才为真,并且只有存在才能被定义,但反过来却不一定是真的。

EXPR 可以任意复杂,前提是它的最后的操作是一个散列键字或者索引查找:

   if (exists $hash{A}{B}{$key} ) { ... }

尽管最后一个元素不会只是因为它的存在性已经经过测试而存在,中间的元素却会。因此 $$hash{"A} 和 $hash{"A"}->{"B} 都将真正存在。这个功能不是 exists 函数本身的;它发生 在任何使用了箭头操作符的地方(明确地或隐含地):

   undef $ref;
   if (exists $ref->{"Some key"}) { }
   print $ref;   # 打印 HASH(0x80d3d5c)

即使 "Some key" 元素没有突然存在,前面未定义的 $ref 变量也会突然变成持有一个匿名散列 的变量。这是一个那种第一眼——甚至第二眼看上去都不是左值环境条件下的自动激活的一个有趣 的例子。这种行为在将来的版本里可能会被修补。作为绕开的一种方法,你可以嵌套你的调用:

   if ($ref            and
       exists $ref->[$x]      and
       exists $ref->[$x][$y]      and
       exists $ref->[$x][$y]      and
       exists $ref->[$x][$y]{$key}   and
       exists $ref->[$x][$y]{$key}[2] )  { ... }

如果 EXPR 是子过程的名字,如果该子过程已经定义,那么exists 函数将返回真,即使该子过程 还没有定义也如此。下面的程序将打印 “Exists”:

   sub flub;
   print "Exists\n"      if exists   &flub;
   print "Defined\n"      if defined &flub;

在一个子过程名字上使用 exists 可以用于 AUTOLOAD 子过程,这个子过程可能需要知道某个包 是否需要某个子过程的定义。该包可以通过声明一个想 flub 那样的 sub 存根来实现这个目的。

29.2.35 exit

这个函数把 EXPR 当作一个整数计算然后立即以该数值为最终的程序错误状态退出。如果省略了 EXPR,那么该函数以 0 状态退出(意思是“没有错误”)。下面是一个程序片段,它让用户通过 敲入 x 或 X 退出程序:

   $ans = 
   exit if $ans =~ /^[Xx]/;

如果别人有任何机会可以捕获所发生的任何错误,那么你就不应该用 exit 退出子过程。应该用 die,它可以用一个 eval 捕获。或者使用 Carp 模块的 die 的封装,比如 croak 或者 confess。

我们说 exit 函数立即退出,但这是一个赤裸裸的谎言。它尽可能快地退出,但是它首先调用任何 已经定义了的 END 过程做退出时处理。这些过程无法退出 exit,尽管它们可以通过设置 $? 变量改变最终的退出值。同样,任何定义了 DESTROY 方法的类都将在程序真正退出前代表它的 所有对象调用该方法。如果你确实需要忽略退出处理,那么你可以调用 POSIX 模块的 _exit 函数以避免所有 END 和析构器处理。而如果没有 POSIX 可用,你可以 exec "/bin/false" 或者类似的东西。

29.2.36 exp

这个函数返回 e 的 EXPR 次幂。要获取 e 的值,用 exp(1) 好了。对于不同基数的通用指数 运算,使用我们从 FORTRAN 偷来的 ** 操作符:

   use Math::Complex;
   print -exp(1) ** (i * pi);   # 打印 1

29.2.37. fcntl

这个函数调用你的操作系统的文件控制函数,就是那些 fcntl(2) 手册页里归档的东西。在你 调用 fcntl 之前,你可能首先要说:

   use Fcntl;

以装载正确的常量定义。

根据所用的不同的 FUNCTION,将对 SCALAR 进行读或者写。可以把一个指向 SCALAR 字串值的 指针作为真正 fcntl 调用的第三个参数传递。(如果 SCALAR 没有字串值,但的确有一个 数字值,那么该值将被直接传递,而不是传递一个指向字串值的指针。)参阅 Fcntl 模块获取 FUNCTION 比较常见的可用数值描述。

如果在一个没有实现 fcntl(2) 的系统上使用 fcntl 函数,那么它会抛出一个错误。在那些 实现了这个系统调用的系统上,你可以做诸如修改 exec 时关闭(close-on-exec)标志(如果 你不想使用 $^F($SYSTEM_FD_MAX)变量),修改非阻塞 I/O 标志,模拟 lockf(3) 函数, 以及在 I/O 等待的时候安排接收 SIGIO 信号这样的事情。

下面是一个在系统级别上把一个叫 REMOTE 的文件句柄设置为非阻塞的例子。这样,如果从一个 管道,套接字,或者串行线读取数据时,如果发现没有数据可读,就让任何输入操作马上返回, 否则的话就会阻塞住。它还让那些通常会阻塞的写操作马上带着一个失败状态返回。(你也可以 设置 $| 实现这些目的。)

   use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
   
   $flags = fcntl(REMOTE, F_GETFL, 0)
         or die "Can't get flags for the socket: $!\n";

   $flags = fcntl(REMOTE, F_SETFL, $flags| O_NONBLOCK)
         or die "Can't set flags for teh socket: $!\n";

fcntl (以及 ioctl)的返回值如下:

系统调用返回 Perl 返回
-1 undef
0 字串“0 but true”
其他任何东西 那个数字

因此 Perl 成功时返回真,而失败时返回假,但是你还是可以很容易地判断操作系统返回的 实际值:

   $retval = fcntl { ... } || -1;
   printf "fcntl actually returned %d\n", $retval;

在这里,即使是字串“0 but true”也打印出 0,这是因为 %d 格式的作用。这个字串在布尔 环境里为真,但在数字环境里为假。(它还很愉快地免于平时对数字转换的检查发出的警告。)

29.2.38 fileno

这个函数返回在一个文件句柄下面的文件描述符。如果该文件句柄没有 open,那么 fileno 返回 undef。文件描述符是一个很小的,非负整数,比如 0 或 1,分别对应 STDIN 和 STDOUT,后者 是符号。糟糕的是,操作系统可不认得你这些酷酷的符号。它只会用这样的小小的文件数字思维来 打开文件,并且尽管 Perl 通常会自动为你做转换,但是偶尔你还是需要知道实际的文件描述符。

因此,举例来说,fileno 函数对于为select 构造位图以及在实现了 syscall(2) 的情况下传递 某些晦涩的系统调用来说是非常有用的。它还可以用于检查 open 函数给你的文件描述符是不是 你想要的那个,以及判断两个文件句柄是否在使用同一个文件描述符。

   if (fileno(THIS) == fileno(THAT) ) {
      print "THIS and THAT are dups\n";
   }

如果 FILEHANDLE 是一个表达式,那么该值就会被当作一个间接的文件句柄,通常是它的名字 或者一个指向某些构成一个文件句柄对象的引用。

一个警告:在程序的整个生命期里都不要依赖 Perl 文件句柄和数字文件描述符之间的关联关系。 如果一个文件关闭以后重新打开,那么文件描述符可能改变。Perl 在保证某些文件描述符不会 因为对它们的 open 失败而丢失的时候碰到了一些麻烦,它现在只能对那些不超过当前特殊变量 $^F($SYSTEM_FD_MAX)的当前值(缺省是 2)的文件描述符保证这一点。尽管文件句柄 STDIN, STDOUT,和 STDERR 从文件描述符 0,1,和 2 开始(Unix 标准传统),但如果你非常随意地 打开和关闭它们的话,那么它们都有可能改变。只要你总是在关闭以后马上重新打开,那么你在 0,1,和 2 上不会碰到麻烦。在 Unix 系统上,基本规则是先用最小的描述符,而那个会是你 刚刚关闭的那个。

29.2.39 flock

flock 函数是 Perl 的可移植的文件锁定的接口,尽管它只是锁住整个文件,而不是记录。该 函数会把与 FILEHANDLE 关联的文件锁住,成功时返回真,失败时返回假。为了避免可能的数据 丢失现象,Perl 在锁住或者解锁文件之前刷新 FILEHANDLE。Perl 实现它的 flock 的方法可能 是 flock(2),fcntl(2),lockf(3),或者其他的什么平台相关的锁机制,但是如果所有这些都 没有,那么调用 flock 将会抛出一个例外。参阅第十六章的“文件锁定”一节。

OPERATION 是 LOCK_SH,LOCK_EX,或者 LOCK_UN 之一,可能是与 LOCK_NB 或(OR)的。 这些常量通常的值是 1,2,8,和 4,但是如果你是从 Fcntl 里分别或者用 :flock 标签成组 地输入它们的,那么你可以使用符号名字。

LOCK_SH 请求一个共享的锁,所以它常用于读取。LOCK_EX 请求一个排它的锁,所以它常用于写。 LOCK_UN 释放前面一次请求的所;关闭该文件同样也释放任何锁。如果 LOCK_NB 位和 LOCK_SH 或者 LOCK_EX 一起使用,那么 flock 会马上返回,而不是等待一个可用的锁。检查返回状态 看看你是否获得了你请求的锁。如果你不使用 LOCK_NB,那么你就有可能永远等待系统授予你 想要的锁。

flock 的另外一个不明显但很常用的方面是它的锁只是劝告性的。自由的锁更灵活,但是不能象 命令性的锁那样有保证。这就意味着用 flock 锁住的文件可能被那些没有使用 flock 的程序 修改。等待红灯的车相互之间可以和睦相处,但和不遵守红灯的车之间可就不能相容了。防卫性 驾驶。

有些 flock 的实现不能透过网络锁住东西。尽管理论上你可以使用更加系统相关的 fcntl 来做 这件事,但这么做是否(能够)可靠仍然是有怀疑的。

下面是一个用于 Unix 系统的邮箱附件箱,它使用 flock(2) 来锁住邮箱:

   use Fcntl qw/:flock/;      # 输出 LOCK_* 常量
   sub mylock {
      flock(MBOX, LOCK_EX)
         or die "can't lock mailbox: $!";
      # 预防在我们等待的时候有家伙附加
      # 以及我们的 stdio 缓冲区失准
      seek(MBOX, 0, 2)
         or die "can't seek to the end of mailbox: $!";
   }

   open(mbox, ">>/USR/SPOOL/MAIL/$ENV{'USER'}")
      or die "can't open mailbox: $!";

   mylock();
   print MBOX $msg, "\n\n";
   close MBOX
      or die "can't close mailbox: $!";

在那些支持真正的 flock(2) 系统调用的系统上,锁是在跨 fork 继承的。其他实现则没那么 走运,并且很可能在 fork 之间丢失锁。又见第三十二章的 DB_File 模块获取其他的 flock 的 例子。

29.2.40 fork

这个函数通过调用 fork(2) 系统调用,从一个进程中创建两个进程。如果它成功,该函数给 父进程返回新创建的子进程 ID,而给子进程返回 0。如果系统没有足够的资源分配一个新的 进程,那么调用失败并返回 undef。文件描述符(以及有时候还有在那些描述符上的锁)是 共享的,而所有其他的东西都是拷贝的——或者至少看起来是那样的。

在早于 5.6 版本的 Perl 里,未冲刷的缓冲区在两个进程里都是没有冲刷的,这就意味着你需要 在程序的早些时候在一个或多个文件句柄上设置 $| 以避免输出重复。

一个产生子进程然而有检查“cannot fork”错误的近乎没有毛病的方法是:

   use Errno qw(EAGAIN);
   FORK: {
      if ($pid = fork) {
         # 父进程在此
         # 在 $pid 里可以看到子进程的进程 id
      }
      elsif (defined $pid) {   # 如果定义了,$pid 在这里是 0
         # 子进程在此
         # 你可以用 getppid 在这里获取父进程的 pid
      }
      elsif ($! == EAGAIN) {
         # EAGAIN 是认为可以恢复的 fork 错误
         sleep 5;
         redo FORK;
      }
      else {
         # 奇怪的 fork 错误
         die "Can't fork: $!\n";
      }
   }

这些预防措施在那些做隐含的 fork(2) 的操作上是不必要的,比如 system,反勾号,或者把 一个进程当作一个文件句柄打开,因为 Perl 在为你做 fork 的时候碰到临时的失败会自动重新 尝试 fork。要注意记得使用 exit 结束子进程的代码,否则子进程会不小心地离开条件块并且 开始执行原来只是想让父进程执行的代码。

如果你 fork 以后再也不等待你的子进程,那么你就会积累僵死进程(那些父进程还没等待它们 的退出进程)。在一些系统上,你可以通过设置 $SIG{CHLD} 为“IGNORE”来避免这些;在 大多数系统上,你必须 wait 你的垂死的子进程。参阅 wait 函数获做这些的例子,或则后参阅 第十六章的“信号”一节获取更多关于 SIGCHLD 的信息。

如果一个派生出来的子进程继承了系统文件描述符,象 STDIN 和 STDOUT 等,它们又和一个远程 的管道或者套接字连接,那么你可能不得不在子进程里把他们重新打开到 /dev/null。这是因为 即使父进程退出,子进程仍将带着这些文件句柄的拷贝继续生存。而远端服务器(比如说,一个 CGI 脚本或者一个从远程 shell 发起的后台任务。)就会挂起,因为它仍然等待所有拷贝关闭。 重新把系统文件句柄打开成别的什么东西可以修补这个问题。

在大多数支持 fork(2) 的系统上,人们做了大量努力把它变得尽可能地高效(比如,数据页的 写时拷贝(copy-on-write)技术),而它也成了过去几十年来多任务领域的典范。但是 fork 函数可能并没有有效地(甚至可能是根本没有)在那些不象 Unix 的系统上实现。比如, Perl 5.6 甚至在 Microsoft 系统上都模拟了一个合适的 fork,但是并不能保证可以达到很好 的性能。可能用 Win32::Process 模块的时候,你的运气会好一些。

29.2.41 format

=   format NAME == =      picture line= =      value list= =      ...= .

这个函数声明一个图形行的命名序列(以及相关值)用于 write 函数。如果省略了 NAME,名字 缺省是 STDOUT,正好是 STDOUT 文件句柄的缺省格式名字。因此,和 sub 声明类似,这是一个 编译时发生的包全局声明,并且值列表里的变量应该在格式定义时是可见的。也就是说,词法范围 的变量必须在该文件的更早的位置定义,而动态范围的变量只需要在调用 write 的时候设置就 可以了。下面是一个例子(它假设我们已经计算了 $cost 和 $quantity):

   my $str = "widget";      # 词法范围的变量
   
   format Nice_Output =
   Test: @<<<<<<<< @| | | |  @>>>>>
      $str,   $%,   '$'   . int($num)
   .

   local $~ = "Nice_Output";   # 选择输出格式。
   local $num = $cost * $quantity;   #  动态范围的变量。

   write;

和文件句柄类似,格式名字是存在于一个符号表(包)里的标识符,而且是可以用包名修饰成全名的。 在一个符号表的记录的类型团里,格式存放在它们自己的名字空间里,它和文件句柄,目录句柄,标量, 散列和子过程是不同的。不过,和其他六种类型一样,一个叫做 Whatever 的格式也会被一个对 *Whatever 类型团的 local 所影响。换句话说,格式只是包含在类型团里的另外一种小东西,与其他小东西相互独立。

第七章,格式,里的“格式变量”节里包含大量它们的细节和它们的使用的例子。第二十八章描写 了内部的格式相关变量,而 English 和 IO::Handle 模块提供了一个对他们简化的访问的接口。

29.2.42 formline

这是一个 format 用的内部函数,不过你还是可以自己调用它。它总是返回真。它根据 PICTURE 的内容格式化一列数值,把输出放到格式化输出累加器,$^A(或者是 $ACCUMULATOR——如果你 使用了 English 模块)。最后,当完成一个 write 的时候,$^A 的内容写入某个文件句柄,但 你也可以自己读取 $^A 然后把 $^A 设置为 ""。一个格式通常每行表格做一个 formline,但是 formline 函数本身并不在意在 PICTURE 里嵌入了多少个新行。这意味着 ~ 和 ~~ 记号将把 整个 PICTURE 当作一行看待。因此你可能需要用多个 formline 来实现一个记录格式,就好象 格式化编译器在内部做的那样。

如果你在图形周围放双引号的事情要小心,因为一个 @ 字符可能会被拿去表示一个数组名字的开头。 参阅第六章“格式”获取使用的例子。

29.2.43 getc

这个函数从附着到 FILEHANDLE 上的输入文件返回下一个字节。在文件结尾的时候,或者碰到 I/O 错误的时候它返回 undef。如果省略了 FILEHANDLE,那么该函数从 STDIN 中读取。

这个函数有点慢,但是偶尔可以用于从键盘上读取一个字符输入(实际上是字节)——前提是你能 让你的键盘输入不经过缓冲。这个函数需要从标准 I/O 库里来的未经缓冲的输入。糟糕的是, 标准的 I/O 库还没有标准到能够提供一种可移植的方法,告诉下层操作系统供应无缓冲的键盘 输入到标准 I/O 系统。要做着件事情,你必须稍微更聪明一点,并且采取操作系统相关的做法。 在 Unix 里,你可以说:

   if ($BSD_STYLE) {
      system "stty cbreak /dev/tty 2>&1"
   } else {
      system "stty", "-icanon", "eol", "......";
   }

   $key = getc;

   if ($BSD_STYLE) {
      system "stty -cbreak /dev/tty 2>&1";
   } else {
      system "stty", "icanon", "eol", "^@";    # ASCII NUL
   }

   print "\n";

上面的代码把在终端上敲入的下一个字符(字节)放到字串 $key 里。如果你的 stty 程序有象 cbreak 这样的选项,那么你就需要 $BSD_STYLE 为真的地方的代码。否则你就需要它为假的 地方的代码。判断 stty(1) 的选项就留给读者做练习吧。

POSIX 模块用 POSIX::getattr 函数提供了一个做这件事情的一个更具移植性的版本。又见 来自离你最近的 CPAN 站点里的 Term::ReadKey 模块获取更具移植性和更灵活的方法。

29.2.44 getgrent

这些过程遍历你的 /etc/group 文件(或者是别人的 /etc/group 文件,如果这个文件来自一台 服务器什么的地方的话)。在列表环境里,getgrent 的返回值是:

   ($name, $passwd, $gid, $members)

这里 $members 包含一个空格分隔的列表,该列表就是该组成员的登录名字。要设置一个散列把 组名字转换成 GID,你可以用:

   while (($name, $passwd, $gid) = getgrent) {
      $gid{$name} = $gid;
   }

在标量环境里,getgrent 只返回组名字。标准的 User::grent 模块支持一个此函数通过名字 访问的接口。参阅 getgrent(3)。

29.2.45 getgrgid

这个函数通过组标识查找一条组文件记录。返回值在列表环境中是:

   ($name, $passwd, $gid, $members)

这里 $members 包含一个用空格分隔的列表,该列表就是该组成员的登录名字。如果你想重复做 这件事情,考虑用 getgrent 把数据缓冲到一个散列里面。

在标量环境中,getggid 只返回组名字。User::grent 模块支持此函数的一个通过名字访问的 接口。参阅 getgrgid(3)。

29.2.46 getgrnam

这个函数通过组名字查找一条组文件记录。返回值在列表环境中是:

   ($name, $passwd, $gid, $members)

这里 $members 包含一个用空格分隔的列表,该列表就是该组成员的登录名字。如果你想重复做 这件事情,考虑用 getgrent 把数据缓冲到一个散列里面。

在标量环境中,getggid 只返回组 ID。User::grent 模块支持此函数的一个通过名字访问的 接口。参阅 getgrgid(3)。

29.2.47 gethostbyaddr

这个函数把地址转换成名字(和改变地址)。ADDR 应该是一个封包的二进制网络地址,而 ADDRTYPE 实际上通常应该是 AF_INET(来自 Socket 模块)。其返回值在列表环境里是:

   ($name, $aliases, $addrtype, $length, @addrs) =
      gethostbyaddr($packed_binary_address, $addrtype);

这里 @addrs 是一个封包的二进制地址。在互联网域里,每个地址都(因历史关系)是四个字节 长,并且可以通过用下面这样的东西解包:

   ($a, $b, $c, $d) = unpack('C4', $addrs[0]);

另外,你可以给 sprintf 用 v 修饰词把它直接转换成点向量表示法:

   $dots = sprintf "%vd", $addrs[0];

Socket 模块的 inet_ntoa 函数可以用于生成可打印的版本。这个方法在我们都准备切换到 IPv6 的时候会变得很重要。

   use Socket;
   $printable_address = inet_ntoa($addrs[0]);

在标量环境里,gethostbyaddr 只返回主机名字。

要从一个点向量中生成一个 ADDR,用:

   use Socket;
   $ipaddr = inet_aton("127.0.0.1");      # localhost
   $claimed_hostname = gethostbyaddr($ipaddr, AF_INET);

有趣的是,在 Perl 5.6 里,你可以忽略 inet_aton() 并且使用新的用于版本号的 v 字串表示法操作 IP 地址:

   $ipaddr = v127.0.0.1;

参阅第十六章“套接字”一节获取更多的例子。Net::hostent 模块支持一个此函数的通过名字 使用的接口。参阅 gethostbyaddr(3)。

29.2.48 gethostbyname

这个函数把一个网络主机名翻译成它的对应地址(以及其他名字)。其返回值在列表环境里是:

   ($name, $aliases, $addrtype, $length, @addrs) =
      gethostbyname ($remote_hostname);

这里的 @addrs 是一个裸地址的列表。在互联网域,每个地址(因历史原因)是四个字节长, 可以用下面方法解包的东西:

   ($a, $b, $c, $d) = unpack('C4', $addrs[0]);

你可以用带 v 修饰词的 sprintf 把它们直接转换成向量符号:

   $dots = sprintf "%vd", $addrs[0];

在标量环境里,gethostbyname 只返回主机地址:

   use Socket;
   $ipaddr = gethostbyname($remote_host);
   printf "%s has address %s\n",
      $remote_host, inet_ntoa($ipaddr);

参阅第十六章里的“套接字”一节获取另外一种方法。Net::hostent 模块提供了一个用名字访问 这个函数的接口。又见 gethostbyname(3)。

29.2.49 gethostent

这个函数遍历你的 /etc/hosts 文件并且每次返回一条记录。gethostent 的返回值是:

   ($name, $aliases, $addrtype, $length, @addrs)

这里 @addrs 是一个裸地址的列表。在互联网域,每个地址(因历史原因)是四个字节长,可以用 下面方法解包的东西:

   ($a, $b, $c, $d) = unpack('C4', $addrs[0]);

使用 gethostent 的脚本不能认为是可移植的。如果一台机器使用一个名字服务器,它就不得不 询问互联网以满足一个获取该星球上每一台机器地址的请求。所以 gethostent 没有在这样的 机器上实现。参阅 gethostent(3) 获取其他细节。

Net::hostent 模块模块提供了一个用名字访问这个函数的接口。

29.2.50. getlogin

如果有的话,这个函数返回当前登录名。在 Unix 系统上,它是从 utmp(5) 文件里读取的。如果 它返回假,那么用 getpwuid 取代。比如:

   $login = getlogin() || (getpwuid($<)) [0] || "Intruder!!";

29.2.51 getnetbyaddr

这个函数把一个网络地址转换成对应的网络名字。在列表环境中其返回值是:

   use Socket:
   ($naem, $aliases, $addrtype, $net) = getnetbyaddr(127, AF_INET);

在标量环境中,getnetbyaddr 只返回网络名字。Net::netent 模块支持一个通过名字访问这个函数的接口。 参阅 getnetbyaddr(3)。

29.2.52 getnetbyname

这个函数把一个网络名字转换成它对应的网络地址。其返回值在列表环境里是:

   ($name, $aliases, $addrtype, $net) = getnetbynaem("loopback");

在标量环境里,getnetbyname 只返回网络地址。Net::netent 模块支持一个通过名字访问这个 函数的接口。参阅 getnetbyname(3)。

29.2.53. getnetent

这个函数遍历你的 /etc/networks 文件。其返回值在列表环境中是:

   ($name, $aliases, $addrtype, $net) = getnetent();

在标量环境里,getnetent 只返回网络名字。Net::netent 模块支持一个通过名字访问这个函数 的接口。参阅 getnetent(3)。

现在,网络名字这个概念看上去相当奇怪;大多数 IP 地址是在无命名(而且也是无法命名的) 子网里。

29.2.54. getpeername

这个函数返回该 SOCKET 连接中对端的封包地址。比如:

   use Socket;
   $hersockaddr      = getpeername SOCK;
   ($port, $heraddr)      = sockaddr_in($hersockaddr);
   $herhostname      = gethostbyaddr($heraddr, AF_INET);
   $herstraddr      = inet_ntoa($heraddr);

29.2.55. getpgrp

这个函数为声明的 PID(对当前进程用 PID=0)返回当前进程组。如果在那些没有实现 getpgrp(2) 的机器上使用,那么调用 getpgrp 将抛出一个例外。如果省略了 PID,该函数返回 当前进程的进程组(与使用 PID 为 0 时一样)。在那些用 POSIX getpgrp(2) 系统调用实现 这个操作符的系统上,必须省略 PID 或者,如果提供了,必须为 0。

29.2.56 getppid

这个函数返回父进程的进程 ID。在典型的 Unix 系统上,如果你的父进程 ID 改为 1,那就 意味着你的父进程已经退出并且你已经被 init(8) 进程收养了。

29.2.57. getpriority

这个函数返回一个进程,一个进程组或者一个用户的当前优先级。参阅 getpriority(2)。如果 在一台没有实现 getpriority(2) 的机器上调用 getpriority 将抛出一个例外。

CPAN 的 BSD::Resource 模块提供了一个更便利的接口,包括提供给 WHICH 的 PRIO_PROCESS, PRIO_PGRP,和 PRIO_USER 符号常量。尽管这几个常量通常是分别设置成 0,1,和 2,你实际 上还是不知道在 C 的黑暗的 #include 文件的领土里发生了什么事情。

WHO 的值为 0 意思是当前进程,进程组,或者用户,因此要获得当前进程的优先级,用:

   $curprio = getpriority(0, 0);

29.2.58 getprotobyname

这个函数把一个协议名字转换成它对应的数字。在列表环境里的返回值是:

   ($name, $aliases, $protocol_number)  = getprotobynaem("tcp");

如果在标量环境里调用,getprotobyname 只返回协议号。Net::proto 模块提供一个通过名字 访问这个函数的接口。参阅 getprotobyname(3)。

29.2.59 getprotobynumber

这个函数把一个协议数字转换成它对应的名字。在列表环境里的返回值是:

   ($name, $aliases, $protocol_number)  = getprotobynumber(6);

如果在标量环境里调用,getprotobynumber 只返回协议名字。Net::proto 模块提供一个通过 名字访问这个函数的接口。参阅 getprotobynumber(3)。

29.2.60 getprotoent

这些函数遍历 /etc/protocols 文件。在列表环境里,getprotoent 的返回值是:

   ($name, $aliases, $protocol_number) = getprotoent();

如果在标量环境里调用,getprotoent 只返回协议名字。Net::proto 模块提供一个通过名字 访问这个函数的接口。参阅 getprotoent(3)。

29.2.61 getpwent

这些函数概念上是遍历你的 /etc/passwd 文件,但是如果你是超级用户并且使用了影子文件, 或者用了 NIS 或 NIS+ 两者之一,那么它可能涉及到 /etc/shadown 文件。在列表环境中的 返回值是:

   ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwent();

有些机器可能使用份额(quota)和注释域做名字用途,但其他的域都是一样的。如果想设置一个 散列把登录名字转换成 UID,用:

   whiel (($name, $passwd, $uid) = getpwend()) {
      $uid{$name} = $uid;
   }

在标量环境里,getpwent 只返回用户名。User::pwent 模块支持一个通过名字访问这个函数的 接口。参阅 getpwent(3)。

29.2.62 getpwnam

这个函数把一个用户名翻译成对应的 /etc/passwd 文件的记录。其返回值在列表环境里是:

   ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwnam("daemon");

在支持影子口令的系统上,要想获取真正的口令,你就必须是超级用户。你的 C 函数库会注意你 是否有合适的权限并且打开 /etc/shadow(或者那些保存影子口令的文件)。至少,这就是它的 工作方法。如果你的 C 库太蠢而不会注意这些,那么 Perl 会试图做这些事情。

如果需要重复查找,请考虑把数据用 getpwent 缓存到一个散列里。

在标量环境里,getpwnam 只返回数字用户 ID。User::pwent 模块支持一个通过名字访问这个 函数的接口。参阅 getpwnam(3) 和 passwd(5)。

29.2.63. getpwuid

这个函数把一个数字用户 ID 转换成对应的 /etc/passwd 文件记录。起返回值在列表环境中 是:

   ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid(2);

如果需要重复查找,请考虑把数据用 getpwent 缓存到一个散列里。

在标量环境里,getpwuid 返回用户名。User::pwent 模块支持支持一个通过名字访问这个函数的接口。 参阅 getpwnam(3) 和 passwd(5)。

29.2.64. getservbyname

这个函数把一个服务(端口)名翻译成它对应的端口号,PROTO 是象“tcp”这样的协议名。其 返回值在列表环境里是:

   ($name, $aliases, $port_number, $protocol_name) = getservbyname("www", "tcp");

在标量环境里,getservbyname 只返回该服务的端口号。Net::Servent 模块支持支持一个通过 名字访问这个函数的接口。参阅 getservbyname(3)。

29.2.65. getservbyport

这个函数把一个服务(端口)号翻译成它对应的名字。PROTO 是一个象“tcp”这样的协议名字。 其返回值在列表环境中是:

   ($name, $aliases, $port_number, $protocol_name) = getservbyport(80, "tcp");

在标量环境中,getservbyport 只返回服务名。Net::Servent 模块支持支持一个通过名字访问 这个函数的接口。参阅 getservbyport(3)。

29.2.66. getservent

这个函数遍历 /etc/services 文件或者该文件的等效文件。其返回值在列表环境中是:

   ($name, $aliases, $port_number, $protocol_name) = getservent();

在标量环境中,getservent 只返回服务端口名。Net::Servent 模块支持支持一个通过名字访问 这个函数的接口。参阅 getservent(3)。

29.2.67. getsockname

这个函数返回该 SOCKET 连接的本地端的封包套接字地址。(为什么你还不知道自己的地址? 也许是因为你在 accept 之前绑定了一个指向服务器套接字的包含通配符的地址,而现在你想 知道别人是用什么接口和你连接的。或者你的套接字是你的父进程传递过来的——比如说, inetd。)

   user Socket;
   $mysockaddr = getsockname(SOCK);
   ($port, $myaddr) = sockaddr_in($mysockaddr);
   $myname = gethostbyaddr($myaddr, AF_INET);
   printf "I am %s [%vd]\n", $myname, $myaddr;

29.2.68. getsockopt

这个函数返回你请求的套接字选项,如果有错误则返回 undef。参阅 setsockopt 获取更多信息。

29.2.69 glob

这个函数把 EXPR 的值带着 shell 那样的文件名扩展返回。它是实现 <*> 操作符的内部函数。

由于历史原因,这个算法和 csh(1) 的扩展风格相匹配,而不是 Bourne shell 的。早于 5.6 版本的 Perl 使用了一个外部的处理,但 5.6 及以后的版本在内部进行聚团的工作。那些第一个 字符是点(“.”)的文件被忽略,除非这个字符是明确匹配的。一个星号(“*”)匹配任意字符 的任意序列(包括空)。一个问号(“?”)匹配任意一个字符。一个方括弧序列(“[ ... ]”) 声明一个简单的字符表,比如“[chy0-9]”。字符表可以用音调符号取反,象“*.[^oa]”,它 匹配任意非点文件,这些文件的文件名博爱喊一个点,后面跟着一个字符在文件名尾部,但这个 字符既不能是“a”也不能是“o”。一个波浪号(“~”)扩展成一个家目录,象“~/.*rc”是指 当前用户的所有 “rc”文件,或者“~jane/Mail/*”似乎所有 Jane 的邮件文件。花括弧可以 用于候补,象在“!/.{mail,ex,csh,twm,}rc”里面的是获取那些特定的 rc 文件。

如果你想聚集那些可能包含空白的文件名,你坑内需要直接使用 File::Glob 模块,因为老祖父 glob 把空白用于分隔多个模式,比如 <*.c *.h>。更多细节,请参阅第三十二章的 File::Glob。调用 glob (或者 <*> 操作符)自动 use 该模块,因此如果该模块莫名其妙地 从你的库里消失了,那么就会抛出一个例外。

当你调用 open 的时候,Perl 并不扩展通配符,包括波浪号。你需要先把结果 glob 起来。

   open(MAILRC, "~/.mailrc")      # 错:波浪号是一个 shell 的东西
      or die "can't open ~/.mailrc: $!";

   open(MAILRC, (glob("~/.mailrc"))[0])   # 先扩展波浪号
      or die "can't open ~/.mailrc: $!";

glob 函数和 Perl 的类型团的概念没有任何关系,只不过它们都用 * 代表多个项。

又见第二章里的“文件名聚集操作符”。

29.2.70. gmtime

这个函数把 time 这样的函数返回的时间转换成对应的格林威治时间(也叫做 GMT,或者 UTC, 或者在某些文化里甚至是 Zulu,不过让人奇怪的是在 Zulu 文化里不这么叫。)的一个九个元素 的列表。它的典型用法如下:

#  0    1    2     3     4    5     6     7     8
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime;

如果象这种情况一样,省略了 EXPR,那么它做 gmtime(time())。Perl 库模块 Time::Local 包含一个子过程,timegm,它可以把该列表转换回一个时间值。

所有列表元素都是数字并且是从 struct tm(这是一个 C 编程结构——别害怕) 里直接拿出来 的。因此这就意味着 $mon 的范围是 0 .. 11,而一月是月份 0,而 $wday 的范围是 0 .. 6, 星期日是 0 。你很容易记住哪些是零为基的,因为那些都是你在包含月份和日期名字的零为基的 数组里用做的脚标的东西。

比如,要获取伦敦的当前月份,你可以说:

   $london_month = (qw(Jan Feb Mar Apr May Jun
            Jul Aug Sep Oct Nov Dec))[(gmtime)[4]];

$year 是自 1900 年以来的年数;也就是说,在 2023 年,$year 是 123,而不只是 23。要 获取四位数年,只需要用: $year + 1900。要获取两位数年(比如2001里的“01”),用 sprintf("%2d", $year % 100)。

在标量环境,gmtime 返回一个基于 GMT 时间值的 ctime(3) 风格的字串。Time::gmtime 模块 支持一个通过名字访问这个函数的接口。参阅 POSIX::strftime() 获取更多更好地格式化时间 的方法。

这个标量值是与区域设置无关的,而是一个 Perl 的内建。同样参阅 Time::Local 模块和通过 POSIX 可以获取的 strftime(3) 和 mktime(3) 函数。如果需要获取类似的,但又是与区域设置 相关的时间字串,那么你要正确设置你的区域环境变量(请参考 perllocale 手册页),并 尝试:

   use POSIX qw(strftime);
   $now_string = strftime "%a %b %e %H:%M:%S %Y", gmtime;

这里的 %a 和 %b 逃逸是表示星期几和第几月的缩写形式,可能不是在所有区域设置中都是三个 字符宽。

29.2.71. goto

goto LABEL 先找到标记着 LABEL 的语句,然后从那里重新开始执行。如果没有找到 LABEL,则 抛出一个例外。它不能用于进入任何需要初始化的构造中去,比如一个子过程或者一个 foreach 循环。它也不能用于进入那些优化过的构造中。你可以用它进入在一个动态范围里的(注:这就 意味着如果它在当前过程中没有找到标签 LABEL,那么它就往回到调用当前过程的那个过程里找 标签,因此可以把你的程序变得几乎不能维护。)几乎任何地方,包括子过程外边,如果要退出 子过程的话,最好还是使用其他的构造,比如 last 或者 die。Perl 的作者自己从来没有觉得 要这么用 goto(也就是说在 Perl 里,C 则是另外一个问题了。)

在矛盾的更高层次(或者说愚蠢的更深层次),Perl 允许 goto EXPR,这里的 EXPR 可以得出 一个标签名字,而该标签位置肯定是到运行时才确定的,因为当编译该语句的时候该标签还是 未知的。这样就可以象 FORTRAN 那样计算 goto,不过如果你为可维护性做优化的话,我们并不 推荐你这么做:(注:“待定”的东西总是很有趣,所以我们在这里还是做个实验。)

   goto +("FOO", "BAR", "GLARCH")[$i];

不相关的 goto &NAME 是非常神奇的东西,它把当前运行着的子过程替换为一个命名子过程的 调用。这种构造可以在不麻烦 AUTOLOAD 子过程(它们希望装载其他子过程)的情况下假装这个 新的子过程(不是原来那个)是先调用的(只不过任何原来那个子过程对 @_ 的修改都传播到 这个替换子过程中)。在 goto 之后,甚至 caller 都不能确定是否最初的 AUTOLOAD 过程是 先调用的。

29.2.72. grep

这个函数在布尔环境里为 LIST 里的每个元素计算 EXPR 或者 BLOCK,临时地把 $_ 轮流设置为 每个元素,非常类似 foreach 构造。在列表环境中,它返回一个对该表达式为真的元素的列表。 (该操作符是按照大家喜爱的同名 Unix 程序命名的,那个程序从一个文件中抽取出匹配特定模式 的行。在 Perl 里,该表达式通常是一个模式,但并不必须是模式。)在标量环境里,grep 返回 该表达式为真的次数。

如果 @all_lines 包含代码的行,下面的例子删除注释行:

   @code_lines = grep !/^\s*#/, @all_lines;

因为 $_ 是每个列表值的隐含别名,修改 $_ 就会修改原始列表的元素。尽管这样做是允许的 并且也有用,但如果你没有心理准备,它还是可能会导致非常奇怪的结果。比如:

   @list = qw(barney fred dino wilma);
   @greplist = grep { s/^[bfd]// } @list;

@greplist 现在是“arney”,“red”,“ino”,但 @list 现在是“arney”,“red”, “ino”,“wilma”!因此,小心从事。

又见 map。下面两个语句的作用是相同的:

   @out = grep {EXPR} @in;
   @out = map { EXPR ? $_ : () } @in

29.2.73. hex

这个函数把 EXPR 当作一个十六进制字串并且返回相等的十进制值。如果前面有“ox”,那么被 忽略。要转换可能以 0,0b,或者 0x 开头的字串,请参考 oct。下面的代码把 $number 设置 为 4,294,906,560:

   $number = hex(ffff12c0");

要实现相反的功能,用 sprintf:

   sprintf "%lx", $number;      #(那里是 L 的小写,不是阿拉伯数字一)

Hex 字串只能表示整数。那些可能导致整数溢出的字串出发一个警告。

29.2.74 import

实际上没有内建的 import 函数。它只是一个普通的类方法,那些模块定义(或者继承)它用来 通过 use 操作符把名字输出到另外一个模块。参阅 use 获取细节。

29.2.75. index

这个函数在一个字串里寻找另外一个字串。它返回在 STR 里找到的第一个 SUBSTR 的位置。如果 声明了 OFFSET,表示在开始搜索之前,忽略从开头开始的多少个字符。位置是以零为基的(或者 你设置的脚标基变量 $[ 的值——不过最好别干那事)。如果没有找到字字串,该函数返回脚标基 减一,通常是 -1。要对你的字串扫描一遍,你可能会说:

   $pos = -1;
   while (($pos = index($string, $lookfor, $pos)) > -1) {
      print "Found at $pos\n";
      $pos++;
   }

29.2.76. int

这个函数返回 EXPR 的整数部分。如果你是 C 程序员,那么你很容易忘记和除法一起使用 int, 因为除法在 Perl 里是浮点数操作:

   $average_age = 939/16;      # 结果是 58.6875(在 C 里是 58)
   $average_age = int 939/16;      # 结果是 58

你不能把这个函数用做通用的圆整方法,因为它向 0 截断,而且还因为浮点数的机器表现形式有 时候可能生成不那么直观的结果。比如,int(-6.725/0.025) 结果是 -268 而不是正确的 -269; 原因是该值的样子可能更象 -268.99999999999994315658。通常,sprintf,printf,或者 POSIX::floor 和 POSXI::ceil 函数可能比 int 的效果更好些:

   $n = sprintf("%0.f", $f);      # 圆整到(不是截断)最接近的整数

29.2.77. ioctl

这个函数实现 ioctl(2) 系统调用,这个系统调用控制 I/O。要获取正确的函数定义,可能你 首先得说:

   require "sys/ioctl.ph";      # 可能是 /usr/local/lib/perl/sys/ioctl.ph

如果 sys/ioctl.ph 不存在或者没有正确的定义。你就不得不基于你的 C 头文件(比如 sys/ioctl.h)自己玩了。(Perl 发布中包括一个叫 h2ph 的脚本可以帮你做这些事情,但是 运行它不那么有用。)根据 FUNCTION 的不同,可能会读写 SCALAR——一个指向 SCALAR 的 字串值的指针将被当作实际 ioctl(2) 调用的第三个参数传递。(如果 SCALAR 没有字串值, 而是有一个数字值,那么该值将被直接传递,而不是传递指向字串值的指针。)pack 和 unpack 函数可以用于操作 ioctl 的结构值。下面的例子判断用 FIONREAD ioctl 的时候还有多少字节 可读:

   require 'sys/ioctl.ph';
      
   $size = pack("L", 0);
   ioctl(FH, FIONREAD(), $size)
      or die "Couldn't call ioctl: $!\n";
   $size = unpack("L", $size);

如果没有安装 h2ph 或者它没什么用,你可以手工 grep 包含文件或者写一个小 C 程序打印出 该值。

ioctl(和 fcntl)的返回值如下:

系统调用返回 Perl 返回
-1 undef
0 字串“0 but true”
其他任何东西 那个数字

因此 Perl 在成功时返回真,而失败时返回假,但是你还是可以很容易地判断操作系统返回的 实际值:

   $retval = ioctl(...) || -1;
   printf "ioctl actually returned %d\n", $retval;

特殊的字串“0 but true”可以免于 -w 关于不当整数转换的警告。

对 ioctl 的调用不能认为是可移植的。如果说你只是想为整个脚本关闭回现一次,更可移植的 方法是:

   system "stty -echo";   # 在大多数 Unix 系统上都能用

不要因为你能在 Perl 干某事就认为你就应该这么做。引用 Apostle Paul 的话来说,“任何 事情都是允许的——不过并非任何事情都是有益的。”

如果需要更好的移植性,你应该看看 CPAN 里的 Term::ReadKey 模块。

29.2.78. join

这个函数把 LIST 里分离的字串连接成一个字串,里面的域用 EXPR 的值分隔,并且返回该字串。 比如:

   $rec = join ':', $login, $passwd, $uid, $gid, $gcos, $home, $shell;

如果要反向功能,参阅 split。要把东西按照固定位置的域连接起来,参阅 pack。把许多东西 连接起来最高效的办法就是用空字串把他们 join 起来:

   $string = join "", @array;

和 split 不同,join 不能接受模式作为它的第一个参数,如果你这么干它会生成一个警告。

29.2.79 keys

这个函数返回指定的 HASH 里的所有键字组成的一个列表。返回的键字在外观上是随机顺序的, 但是这个顺序和 values 或者 each 函数产生的顺序是一样的(假设该散列在不同调用之间没有 被修改),它的一个副作用是重置 HASH 的遍历符。下面是一个(相当呆的)打印你的环境变量 的方法:

   @keys = keys %ENV;      # 以同样顺序的键字
   @values = values %ENV;   # 数值
   while (@keys) {
      print pop(@keys), '=', pop(@values), "\n";
   }

你可能更愿意看到键字排序的环境:

   foreach $key (sort keys %ENV) {
      print $key, '=', $ENV{$key}, "\n";
   }

你可以直接对散列的数值排序,但是如果你不把这些数值映射回键字,这么做好象没什么用。要 按照数值对散列排序,通常你需要通过提供一个基于键字访问数值的比较函数来对 keys 排序。 下面是一个对散列的数值的降序排序:

   foreach $key (sort {$hash{$b} <=> $hash{$a} } keys %hash) {
      printf "%4d %s\n", $hash{$key}, $key;
   }

在一个与某个相当大的 DBM 文件捆绑的散列上使用 keys 会生成一个相当大的列表,导致你产生 一个相当大的进程。这时候你可能更愿意使用 each 函数,它是一个一个地遍历散列,而不会把 它们一下子都吞到一个粒度非常大的列表里。

在标量环境里,keys 返回散列中元素的数量(并且重置 each 遍历器)。不过,要从一个捆绑的散列 里获取这样的信息,包括 DBM 文件,Perl 就必须遍历整个散列,所以这样做并不高效。在空环境里 调用 keys 会好一些。

如果用做左值,keys 就增大为给定散列分配的散列桶的数目。(类似于通过给 $#array 赋予 更大的数字预扩展一个数组。)如果你知道某个散列要增长,而且你知道它要增长到多大,那么 预扩展你的散列可以获得效率上的提高。如果你说:

   keys %hash = 1000;

那么 %hash 就会至少有分配给它的 1000 个桶(实际上,你得到 1024 个桶,因为它是圆整为 最近的二的指数)。你不能通过这种方法用 keys 缩小分配的桶的数量(不过如果你不小心这么 做了也不用担心,因为这样的尝试是没有作用的)。甚至你 %hash = (),这些桶也会照样存在。 如果你想在 %hash 仍然在范围里的时候释放它的存储器,那么用 undef %hash。

又见 each,values,和 sort。

29.2.80. kill

这个函数向一个列进程发送一个信号。对于 SIGNAL 而言,你既可以用一个整数也可以用一个 信号名字(前面没有“SIG”)。如果试图使用一个系统不识别的 SIGNAL 名字将会抛出一个 例外。该函数返回成功发送信号的进程数。如果 SIGNAL 是负数,该函数杀死进程组,而不是 进程。(在 SysV? 上,一个负数的进程号也可以杀死进程组,但那是不可移植的。)PID 为零的 时候向与发送者同组的所有进程发送该信号。比如:

   $cnt = kill 1, $child1, $child2;
   kill 9, @goners;
   kill 'STOP', getppid      # 这样就可以延缓我的登录 shell ...
      unless getppid == 1;      #(但不要戏弄 init(8)。)

一个为 0 的 SIGNAL 测试一个进程是否仍然存活以及你是否仍有权限给它发信号。这里并不发 信号。因此你可以用这个方法来测试一个进程是否仍然存活以及是否没有改变 UID。

   use Errno qw(ESRCH EPERM);
   if (kill 0 => $minion) {
      print "$minion is alive!\n";
   } elsif ( $! == EPERM) {         # UID 改变了
      print "$minion has escaped my control!\n";
   } elsif ( $! == ESRCH) {
      print "$minion is deceased.\n";   # 或者是僵尸
   } else {
      warn "Odd; I couldn't check on the status of $minion: $!\n";
   }

参阅第十六章的“信号”节。

29.2.81. last

last 马上退出有问题的循环,就好象 C 或者 Java 里的 break (也是在循环里使用)语句 一样。如果省略了 LABEL,那么该操作指的是最内层的闭合循环。如果有任何 continue 块, 那么将不执行。

   LINE: while () {
      last LINE if /^$/;      # 如果完成头处理以后则退出
      # 循环其他部分
   }

last 不能用于退出一个返回一个值的块,比如 eval {},sub {},或者 do {},并且也不能 用于退出一个 grep 或者 map 操作,如果打开了警告,那么如果你 last 出了一个不在你的 当前词法范围的循环,比如说一个在调用你的子过程里的循环,那么 Perl 会警告你。

一个块本身从语意上来说是等效于一个执行一次的循环的。因此 last 可以用于实现在这样的 一个块中提前退出。

又见第四章获取一个 last,next,redo,和 continue 如何运行的例子。

29.2.82. lc

这个函数返回 EXPR 的小写形式。它是实现双引号字串里 \L 逃逸的内部函数。如果 use locale 起作用,那么将会考虑你当前的 LC_CTYPE 区域设置,不过区域设置和 Unicode 之间如何相互作用仍然是一个正在进行的研究。参阅 perllocale 手册页获取更多最近的结果。

29.2.83 lcfirst

这个函数返回 EXPR 的第一个字符小写的版本。它是实现双引号字串里的 \l 逃逸的内部函数。 如果 use locale 起作用,并且我们知道如何处理区域和 Unicode 的关系,那么将会考虑你 当前的 LC_CTYPE 区域设置。

29.2.84. length

这个函数返回标量值 EXPR 的以字符记的长度。如果省略 EXPR,它返回 $_ 的长度。(但是要 小心不要让下一个东西看着象一个 EXPR 的开头,否则 Perl 的词法器将被你搞糊涂。比如, length < 10 是编译不了的。如果有疑问,请使用圆括弧。)

不要试图使用 length 寻找一个数组或者散列的大小。用 scalar @array 获取数组的尺寸, 用 scalar keys %hash 获取散列中键字/数值对的数量。(通常如果 scalar 多余则会把它 省略。)

要查看一个字串的按字符计的长度,而不是按字节计,那么你可以说:

   $blen = do { use bytes;   length $string; };

或者:

   $blen = bytes::length($string);      # 必须先用 bytes

29.2.85. link

这个函数创建一个链接到旧文件名的新文件名,该函数成功时返回真,失败时返回假。又见本章 中的 symlink。这个函数在那些非 Unix 风格的文件系统上很可能没有实现。

29.2.86. listen

这个函数告诉系统说你准备在这个 SOCKET 上接受联接,并且系统可以把等待的联接排成最长 QUEUESZE 的队列。就好象你的电话有一些呼叫在等待,最多可以让 17 个呼叫排队等待。( 够吓人的!)如果成功该函数返回真,否则返回假。

   use Socket;
   listen(PROTOSOCK, SOMAXCONN)
      or die "cannot set listen queue on PROTOSOCK: $!";

参阅 accpet,又见第十六章的“套接字”一节。参阅 listen(2)。

29.2.87. local

这个操作符并不创建一个局部变量;用 my 创建局部变量。它的作用是局部化一个现有变量;也 就是说,它令一个或多个全局变量在最内层的闭合块,eval,或者文件里拥有局部范围的数值。 如果列出的变量多于一个,那么该列表必须放在圆括弧里,因为该操作符比逗号绑定得更紧密。 所有列出的变量都必须是合法的左值,也就是说,那些你可以赋值的东西;它可以包含数组或者 散列中独立的元素。

这个操作符的工作方法是把声明的变量的当前值保存在一个隐藏的变量里并且在退出该块, 子过程,eval,或者文件的时候恢复它们。在执行 local 之后但推出该范围之前,任何子过程和 执行的格式看到的都是这个局部的,内层的数值,而不是以前的那个,外层的数值,因为该变量 尽管有一个局部化的数值,仍然是一个全局变量。这种做法的技术术语是“动态范围”。参阅 第四章的“范围声明”一节。

如果有必要,你可以给 EXPR 赋值,这样就让你可以在局部化变量的同时对它们进行初始化。如果 没有给出初始值,那么所有标量都初始化为 undef,所有数组和散列都初始化为 ()。对于普通 赋值来说,如果你用圆括弧包围左边的变量(或者如果该变量是一个数组或散列),那么右边的 表达式就将在列表环境中进行计算。否则,右边的表达式在标量环境中计算。

在任何情况下,在右边的表达式都是在局部化之前进行计算的,但是初始化是发生在局部化 之后的,所以你可以用局部化变量的非局部化值对它进行初始化。比如,下面的代码演示了如何给 一个全局数组做一次临时性的修改:

   if ($sw eq '-v') {
      # 用全局数组初始化局部数组
      local @ARGV = @ARGV;
      unshift @ARGV, 'echo';
      system @ARGV;
   }
   # @ARGV 在这里恢复原值

你还可以临时修改全局散列:

   # 临时向 %digits 散列增加一对记录
   if ($base12) {
      # (注意:我们可没说这么做是高效的!)
      local(%digits) = (%digits, T => 10, E => 11);
      parse_num();
   }

你可以使用 local 给数组或散列的独立变量赋予临时值,甚至连词法范围的都可以这么干:

   if ($protected) {
      local $SIG{INT} = 'IGNORE';
      precious();   # 在这个函数期间没有中断
   }         # 恢复原来的句柄(如果有的话)

你还可以在类型团上使用 local 来创建文件句柄而不用装载一大堆对象模块:

   local *MOTD;         # 保护任何全局的 MOTD 句柄
   my $fh = do { local *FH };   # 创建新的间接文件句柄

(对于 Perl 5.6 来说,简单的 my $fh; 就足够好了,因为如果你在一个需要文件句柄的地方 (就象 open 或者 socket 的第一个参数)给出一个未定义的变量,现在 Perl 将自动为你激活 一个新的文件句柄。)

但是一般而言,你通常会希望使用 my 而不是 local,因为 local 不是人们普遍认为的“局部” 的意思,参阅 my。

29.2.88. localtime

这个函数把 time 函数返回的值转化为一个九元素的列表,该列表包含的时间对应本地时区时间。它 的典型用法如下:

#  0    1    2     3     4    5     6     7     8
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;

如果象上例一样省略了 EXPR,那么它的实际效果是 localtime(time())。

所有列表元素都是数字,并且是直接从 struct tm 中得来。(那东西是 C 里的,用不着想它。) 因此这就意味着 $mon 的范围是 0 .. 11,而一月是月份 0,而 $wday 的范围是 0 .. 6, 星期日是 0。你很容易记住哪些是零为基的,因为那些都是你在包含月份和日期名字的零为基的 数组里用做的脚标的东西。

比如,要获取当前时间是星期几:

   $thisday = (Sun, Mon, Tue, Wed, Thu, Fri, Sat)[(localtime)[6]];

$year 是自 1900 年以来的年数;也就是说,在 2023 年,$year 是 123,而不只是 23。要 获取四位数年,只需要用:$year + 1900。要获取两位数年(比如2001里的“01”),用 sprintf("%2d", $year % 100)。

Perl 的库模块 Time::Local 包含一个子过程,timelocal,它可以做反向的转换。

在标量环境里,localtime 返回一个类 ctime(3) 的字串。比如,date(1) 命令(几乎)(注: date (1) 打印时区,但是标量 localtime 不能打印时区)可以用下面的命令模拟:

   perl -le 'print scalar localtime'

又见标准 POSIX 模块的 strftime 函数,该函数有格式化时间更细致的方法。Time::localtime 模块提供一个通过名字访问此函数的接口。

29.2.89 lock

lock 函数在一个变量,子过程或者 THING 引用的对象上加一把锁,直到该锁超出范围。为了 保证向下兼容,如果你的 Perl 版本打开了线程编译,并且你说过 use Threads,那么该函数 只是内建的。否则,Perl 就会假设它是一个用户定义的函数。参阅第十七章,线程。

29.2.90. log

这个函数返回 EXPR 的自然对数(也就是,以 e 为底)。如果 EXPR 是负数,那么它抛出一个 例外。如果要使用其他底数的对数,那么你可以利用基本代数知识:一个数的底数为 N 的对数 等于该数的自然对数除以 N 的自然对数。比如:

   sub log10 {
      my $n = shift;
      return log($n)/log(10);
   }

log 的逆运算,参阅 exp。

29.2.91. lstat

这个函数和 Perl 的 stat 函数干的事情是一样的(包括设置特殊的 _ 文件句柄),但如果该 文件名字的最后一个部件是一个符号链接,那么它就 stat 这个符号链接本身,而不是 stat 哪个符号链接指向的文件。(如果你的系统里没有实现符号链节,那么做的就是一次普通的 stat 操作。)

29.2.92. m//

这是匹配操作符,它把 PATTERN 当作一个正则表达式。该操作符是当作一个双引号引起的字串 分析,而不是当作一个函数。参阅第五章,模式匹配。

29.2.93. map

这个函数为 LIST 里的每一个元素计算 BLOCK 或者 EXPR(在本地把 $_ 设置为每个元素)并且 返回由每次计算的结果组成的列表。它在列表环境里计算 BLOCK 或者 EXPR,因此 LIST 里的 每个元素都可以映射成零个,一个,或者更多个返回值里的元素。这些东西都填充到一个列表中 去了。比如:

   @words = map { split ' ' } @lines;

把一个行组成的列表分裂成一个单词列表。但是通常在输入值和输出值之间是一一映射的:

   @chars = map chr, @nums;

把一个数字列表转换成对应的字符。而下面是一个一对二映射的例子:

   %hash = map { genkey($_) => $_ } @array;

它只是下面程序的一种有趣的写法:

   %hash = ();
   foreach $_ (@array) {
      $hash{genkey($_) } = $_;
   }

因为 $_ 是一个到该列表数值的别名(隐含引用),所以这个变量可以用于修改数组的元素。这样 做是允许并且是有用的,不过,如果 LIST 不是一个命名数组,那么它可能导致非常奇怪的后果。 这种情况下使用一个普通的 foreach 循环可能更清晰一些。又见 grep;map 和 grep 的不同 在于:map 返回一个由所有成功计算 EXPR 后得到的结果组成的列表,而 grep 返回一个由所有 对 EXPR 计算为真的 LIST 的值组成的列表。

29.2.94. mkdir

这个函数创建 FILENAME 声明的目录,赋予它的权限是数字 MASK 被当前的 umask 修改后得到 的数字。如果该操作成功,返回真;否则返回假。

如果省略 MASK,那么就假定掩码为 0777,几乎就是大多数情况下你需要的掩码。通常,用一个 权限比较宽松的 MASK (比如 0777)创建目录然后让用户用它们的 umask 修改该值要比使用 一个权限严格的 MASK 而不给用户放松权限的机会要好。如果文件或者目录会保存私人的文件 (比如邮件文件)时是个例外。

如果 mkdir(2) 系统调用不是你的 C 库内建的东西,那么 Perl 通过为每个目录调用 mkdir(1) 程序模拟它。如果你在这样的系统上创建一长串目录,那么你自己调用 mkdir 程序创建这些目录 要比启动无数子进程要高效得多。

29.2.95. msgctl

这个函数调用 System V IPC msgctl(2) 系统调用;参阅 msgctl(2) 获取更多细节。你可能需要先 use IPC::SysV 以获得正确的常量定义。如果 CMP 是 IPC_STAT,那么 ARG 必须是一个变量,它 将保存返回的 msqid_ds C 结构。返回值类似 ioctl 和 fcntl: undef 是出错, “0 but ture” 是零,否则就是实际返回的数值。

这个函数只有在那些支持 System V IPC 的系统上才能用,好象要远比支持套接字的系统少。

29.2.96. msgget

这个函数调用 System V IPC msgget(2) 系统调用。参阅 msgget(2) 获取细节。该函数返回 消息队列 ID,如果有错误则返回 undef。在调用它之前,你应该 use IPC::SysV。

这个函数只有在那些支持 System V IPC 的系统上才有。

29.2.97. msgrcv

这个函数调用 msgrcv(2) 系统调用从消息队列 ID 接收消息到 VAR 变量,最大消息尺寸是 SIZE。参阅 msgrcv(2) 获取细节。如果收到一条消息,其消息类型将会是 VAR 里的第一个 东西,而 VAR 的最大长度是 SIZE 加上消息类型的尺寸。该函数在成功时返回真,如果有错误 则返回假。在调用前,你应该 use IPC:SysV。

这个函数只有在那些支持 System V IPC 的系统上才有。

29.2.98. msgsnd

这个函数调用 msgsnd(2) 系统调用向消息队列 ID 发送消息 MSG。参阅 msgsnd(2) 获取细节。 MSG 必须带有长整型消息类型。你可以用下面方法创建一条消息:

   $msg = pack "L a*", $type, $text_of_message;

此函数成功时返回真,如果有错误返回假。在调用前,use IPC::SysV。

这个函数只有在那些支持 System V IPC 的系统上才有。

29.2.99. my

这个操作符声明一个或多个私有的变量,这些变量只存在于最内层的闭合块,子过程,eval,或者 文件里。如果列出了多于一个变量,那么该列表必须放在圆括弧里,因为该操作符比逗号的捆绑力 更强。只有简单标量或者完整的数组和散列可以这样声明。

变量名不能用包名字修饰,因为包变量都是可以通过它们的对应符号表进行全局访问的,而词法 变量与任何符号表都无关。和 local 不同的是,这个操作符与全局变量没有任何关系,它只是在 它自己的范围里(也就是私有变量存在的范围里)隐藏任何其他同名变量,使那些变量不可见。 不过,全局变量总是可以通过加了包修饰的名字,或者通过一个符号引用进行访问。

一个私有变量的范围直到它的定义之后才开始。然后该变量的范围就扩展到从那往后的闭合块, 直到该变量自己的范围结束。

不过,这就意味着你从一个私有变量的范围里调用的子过程无法看到这个私有变量,除非定义 子过程的块本身也是原文包括在那个变量的范围之内。听起来有点复杂,但是只要你碰到一回就 不再复杂了。这种情况的技术术语是词法范围,所以我们常把这些叫做词法变量。在 C 文化中, 它们有时候叫做“自动”变量,因为它们在范围的入口和出口自动分配和删除。

如果需要,你可以给 EXPR 赋值,这样你就可以初始化你的词法变量。(如果没有给出初始化 代码,所有标量都初始化成未定义值而所有数组和散列初始化为空列表。)和普通赋值一样,如果 你在左边使用圆括弧(或者如果该变量是一个数组或者散列),那么在右边的表达式就会在列表 环境中计算。否则,在右边的表达式则在标量环境中计算。比如,你可以用一个列表赋值给你的 正式的子过程参数命名,象这样:

   my ($friends, $romans, $countrymen) = @_;

但是要小心不要省略那个标识列表赋值的圆括弧,象这样:

   my $countrymen = @_;      # 对还是错?

这个赋值把数组的长度(也就是子过程的参数个数)赋予了变量,因为该数组是在标量环境中计算 的。不过,只要你使用 shift 操作符,你还是可以从使用标量赋值给正式参数赋值中获益的。 实际上,因为对象方法把对象当做第一个参数传递,许多方法子过程是通过“偷取”第一个参数 开始的:

   sub simple_as {
      my $self = shift;      # 标量赋值
      my ($a, $b, $c) = @_;   # 列表赋值
      ...
   }

如果你试图用 my sub 声明一个词法范围的子过程,那么 Perl 会带着一条说它还不没有实现 这个特性的信息退出。(当然,除非这个特性已经实现了。)

TYPE 和 ATTRIBUTES 都是可选的,同时它们也被认为上实验性的特性。下面是使用它们的声明 可能的样子:

    my Dog $spot   :ears(short)   :tail(long);

如果声明了 TYPE,那么它表明在 EXPR 里声明的是什么类型的标量,用的方法可能是直接的一个 或多个标量变量,或者间接地通过一个数组或者散列。如果 TYPE 是该类的名字,那么这些标量 就会被认为包含指向该类型对象的引用,或者是指向与该类型兼容的对象的引用。要说明的是, 派生的类被认为是兼容的。也就是说,假设 Collie 是从 Dog 派生出来的,你可能声明:

   my Dog $lassie = new Collie;

你的声明说的是,你将把 $lassie 对象一致地用做一个 Dog 对象。虽然它实际上是 Collie 对象,但是只要你只拿它做 Dog 的事情,那么就没什么问题。通过虚拟方法的作用,那些 Dog 方法的实现会好端端地在 Collie 类里,但是上面的声明只是谈谈接口,而不是实现。至少理论 上如此。

有趣的是,直到 5.6.0 为止,Perl 唯一注意 TYPE 声明的地方就是在对应的类里有用 use fields 用法声明的域。这些声明在一起就允许一个类的伪散列实现可以“展现”给类外部的 代码,因此该散列查找可以由编译器优化成数组的查找。从某种意义上来说,伪散列是这种类的 接口,因此如果允许稍微扁一点,仍然没有触动我们的理论。有关伪散列的更多信息,参阅 第八章,引用,中的“伪散列”。

将来,其他类可能会把 TYPE 解释成不同的东西。我们应该把 TYPE 声明当作一种通用的类接口, 有朝一日它可以根据类的不同以各种形式出现。实际上,TYPE 甚至可以不是一个正式的类名字。 我们为 Perl 保留了小写的类型名字,是因为我们想出来的扩展类型接口的一个办法是允许可选的 低层类型声明,比如 int,num,str,和 ref。这些声明不是为了把 Perl 变成强类型语言; 而是为了给编译器一些优化提示,告诉它可以假设该变量的存储在大多数时候都是声明的类型。 而标量的语意很大程度上仍然是原来的语意——你仍然可以拿两个 str 标量相加,或者打印一个 int 标量,就和你熟悉的多态的标量是一样的。但是如果有 int 声明,Perl 可能会决定只存储 整数值而不再捕获结果字串当作当前值。使用 int 的循环变量的循环可能运行得更快些,尤其是 在那些编译成 C 的代码里。而且,数字数组可以更紧凑地存储。不过,它也有其有缺陷的一面, 如果我们可以写象下面这样的声明的时候,内建的 vec 函数甚至可能都会过时:

   my bit @bitstring;

ATTRIBUTES 声明甚至更是实验性的。我们除了保留该语法和做内部接口的原形以外,还没干什么 事情;参阅第三十一章里的 use attributes 用法获取更多信息。我们将实现的第一个属性可能 是 constant:

   my num $PI   :   constant = atan2(1,1) * 4;

不过还有许多其他的可能性,比如为数组和散列建立缺省值,或者令变量在相互合作的解释器之间 共享。和类型接口类似,属性接口也应该被认为是一个通用接口,一种可以发明新的语法和语意的 工作台。我们不知道 Perl 在下一个十年会怎样进化。我们只知道我们可以通过预先的规划让它 变得对我们更简单些。

又见 local,our,和第四章里的“范围声明”。

29.2.100. new

实际上没有内建的 new 函数。它只是一个普通的构造器方法(也就是说,一个用户定义的 子过程),由 CLASSNAME 类(也就是它的包)定义或者继承过来,好让你构造类型为 CLASSNAME 的对象。许多构造器是用“new”命名的,但只是习惯,这样才好诱导 C++ 程序员 明白它们在干什么事情。要记住阅读有问题的类的文档,这样你就知道如何调用它的构造器; 比如,在 Tk 窗口集中构造列表窗口的构造器就是调用 Listbox()。参阅第十二章。

29.2.101. next

next 操作符类似 C 里的 continue 语句:它启动 LABEL 指明的下一圈循环:

   LINE: while () {
      next LINE if /^#/;      # 抛弃注释
      ...
   }

如果有一个此例中有一个 continue 块,那么它会马上在调用 next 之后执行。如果省略了 LABEL,该操作符指向闭合循环的最内层。

一个块本身在语意上等效于一个只执行一次的循环。因此,next 将提前退出这样的块(通过 continue 块,如果有的话)。

next 不能用于退出有返回值的块,比如 eval {},sub {},或者 do {},并且不能用于退出 一个 grep 或者map 操作。如果打开了警告,Perl 将警告你。如果你 next 出一个不在你当前的 词法范围的循环外边,比如一个调用你的子过程的循环什么的。参阅第四章里的“循环语句”一节。

29.2.102. no

参阅 use 操作符,它是 no 的反向操作符。大多数标准模块不会逆输入,把 no 当作一个 空操作,不过这正是它想要的。这个时候用法模块更具强制性。如果找不到 MODULE,那么抛出 一个例外。

29.2.103. oct

这个函数把 EXPR 当作一个八进制字串并且返回相等的十进制值。如果 EXPR 碰巧以“0x”开头, 那么它就会被当作一个十六进制字串看待。如果 EXPR 以“0b”开头,那么它就解释成一个 二进制数的字串。下面的代码将把任何以标准的 C 或 C++ 符号写的十进制,二进制,八进制, 和十六进制输入字串转换成数字:


$val = oct $val if $val =~ /^0/;

要实现相反的功能,使用对应格式的 sprintf:


$perms = (stat("filename"))[2] & 07777; $oct_perms = sprintf "%lo", $perms;

oct 函数常用于这样的场合,比如你需要把一个“644”这样的字串转换成一个文件模式等等。 尽管 Perl 会根据需要自动把字串转换成数字,但是这种自动转换是以 10 为权的。

29.2.103 open

open 函数把一个内部的 FILEHANDLE 与一个 EXPR 或 LIST 给出的外部文件关联起来。你可以 用一个,两个,或者三个参数调用它(或者更多参数——如果第三个参数是一条命令,而且你运行 的 Perl 至少是 5.6.1)。如果出现了三个或者更多个参数,那么第二个参数声明这个文件打开 的访问模式 MODE,而第三个参数(LIST)声明实际要打开的文件或者要执行的命令——具体 是什么取决于模式。如果是一条命令,而且你想直接调用该命令而不调用 shell(象 system 或者 exec 那样),那么你还可以提供额外的参数。或者该命令可以作为一个参数提供 (第三个),这个时候是否调用 shell 取决于该命令是否包含 shell 元字符。(如果这些参数 是普通文件,不要使用超过三个参数的形式;那样没有作用。)如果无法识别 MODE,那么 open 抛出一个例外。

如果只提供了两个参数,那么就假设模式和文件名/命令一起组合在第二个参数里。(并且如果你 没有在第二个参数里声明模式,而只是一个文件名,那么该文件就会以只读方式打开,安全第一。 )

如果只有一个参数,和 FILEHANDLE 同名的包标量变量必须包含文件名和可选的模式:

   $LOG = ">logfile";      # $LOG 不能是定义过的 my!
      open LOG or die "Can't open logfile: $!";

不过别干这种事。它不合风格。别记着它。

在操作成功的时候 open 返回真,否则返回 undef。如果 open 打开一个到子进程的管道,那么 它的返回值将是那个新进程的进程 ID。和所有的系统调用一样,一定要记得检查 open 的 返回值,看看它是否运转正常。不过我们不是 C 也不是 Java,所以如果 or 操作符能用的时候 不要使用 if 语句。你还可以使用 ||,不过如果你用 ||,那么在 open 上加圆括弧。如果你 省略了函数调用上的圆括弧,把它变成一个列表操作符,那么要注意在该列表后面用“or die” 而不是“|| die”,因为 || 的优先级比象 open 这样的操作符高,因此 || 会绑定你的最后 一个参数,而不是整个 open:

   open LOG, ">logfile" || die "Can't create logfile: $!";   # 错
   open LOG, ">logfile" or die "Can't create logfile: $!";   # 对

上面的代码看起来太紧密了,不过通常你都会用一些空白来告诉你的眼睛该列表操作符在哪里 终结:

      
   open LOG, ">logfile"
      or die "Can't create logfile: $!";

正如本例显示的那样,该 FILEHANDLE 参数通常只是一个简单的标识符(通常是大写),但是它 也可以是一个表达式,该表达式的值提供一个指向实际文件句柄的引用。(该引用可以是一个指向 文件句柄名字的符号引用,也可以是一个指向任何可以解释成一个文件句柄的对象的硬引用。) 这种文件句柄叫做间接文件句柄,并且任意拿一个 FILEHANDLE 做其第一个参数的函数都可以象 操作直接文件句柄那样操作间接文件句柄。不过 open 有一个特殊的地方,就是如果你给它一个 未定义的变量做间接文件句柄,那么 Perl 会自动为你定义那个变量,也就是自动把它激活,使 它包含一个合适的文件句柄引用。这样做的一个好处就是如果没有谁再引用它,那么该文件句柄将 被自动关闭,一般是在该变量超出了范围之后:

   {
      my $fh;         #(未初始化)
      open($fh, ">logfile")   # $fh 被自动激活
         or die "Can't create logfile: $!";
      ...         # 干点别的
   }            # $fh 在这里关闭

my $fh 声明可以在保证可读性的前提下集成到 open 里:

   open my $fh, ">logfile" or die ...

你在这里看到的文件名字前面的 > 符号就是一个模式的例子。从历史来看,首先出现的是两个 参数的 open 的形式。最近新增加的三个参数的形式让你把模式和文件名分隔开,这样的好处 就是避免在它们之间任何可能的混淆。在随后的例子里,我们知道用户不是想打开一个碰巧是以 “>”开头的文件名。我们可以确信他们说的是一个 MODE “>”,这个模式是打开名字是 EXPR 的文件用于写,如果该文件不存在则创建之,如果存在则先把它截断成零长度:

   open(LOG, ">", "logfile") or die "Can't create logfile: $!";

在上面的短一些的形式(两个参数的那个)里,文件名和模式在同一个字串里。该字串是用类似 典型 shell 处理文件和管道重定向的方法分析的。首先,删除字串里任何前导的和后跟的空白。 然后根据需要在字串两端搜索那些声明该文件应该如何打开的字符。在文件名和空格之间是允许 空白的。

表示如何打开一个文件的模式是类 shell 的重定向符号。在表 29-1 里有一个这样的符号的 列表。(如果要用某种此表没有提到的组合模式访问文件,那么请参阅低层的 sysopen 函数。)

表 29-1

模式 读访问 写访问 只附加 不存在时创建 删除现有的
< PATH Y N N N N
> PATH N Y N Y Y
>> PATH N Y Y Y N
+< PATH Y Y N N N
+> PATH Y Y N Y Y
+>> PATH Y Y Y Y N
| COMMAND N Y n/a n/a n/a
COMMAND | Y N n/a n/a n/a

如果模式是“<”或者什么都没有,那么则打开一个现有的文件做输入。如果模式是“>”,那么 该文件打开用于输入,会清空现有文件并且创建不存在的文件。如果模式是“>>”,那么根据需要 创建该文件并且为附加数据而打开,并且所有输出都自动放到文件结尾。如果因为你使用了“>” 或“>>”这样的模式创建了一个新的文件,而且该文件原先并不存在,那么访问该文件的权限将 取决于该进程当前的 umask,并且遵守该函数(umask)描述的规则。

下面是几个常见的例子:

open(INFO,      "datafile")  || die("can't open datafile: $!");
open(INFO,    "< datafile")  || die("can't open datafile: $!");
open(RESULTS, "> runstats")  || die("can't open runstats: $!");
open(LOG,    ">> logfile ")  || die("can't open logfile:  $!");

如果你喜欢标点少的版本,你可以写:

open INFO,      "datafile"   or die "can't open datafile: $!";
open INFO,    "< datafile"   or die "can't open datafile: $!";
open RESULTS, "> runstats"   or die "can't open runstats: $!";
open LOG,    ">> logfile "   or die "can't open logfile:  $!";

如果打开用于读取,那么特殊的文件名“-”指的是 STDIN。如果用于写而打开,那么这个特殊的 文件名指的是 STDOUT。通常,它们可以分别声明为“<-”和“>-”:

   open(INPUT, "-") or die;      # 重新打开标准输入用于读取
   open(INPUT, "<-") or die;      # 同样的事情,不过是明确声明
   open(OUTPUT, ">-") or die;       # 重新打开标准输出用于写

这样,用户就可以使用一个带文件名的程序,该程序可以使用标准输入或者标准输出,但程序的 作者并不需要写特殊的代码来为此做准备。

你还可以在任何这三种模式前面加一个“+”以请求同时的读和写。不过,该文件是清空还是创建, 以及是否它必须已经存在仍然是由你选用的大于号或者小于号来决定的。这就意味着“+<”几乎 总是会读/写更新,而不确定的“+>”模式会在你能从文件中读取任何东西之前先清空该文件。 (只有在你只想重新读取你刚刚写进去的东西的时候使用这个模式。)

   open(DBASE, "+< database")
      or die "can't open existing database in update mode: $!";

你可以把一个打开了准备更新的文件当作一个随机访问的数据库,并且使用 seek 移动到特定的 字节数处,但是普通文本文件里记录是变长的性质,通常会让你不可能利用读写模式更新这样的 文件。参阅第十九章里的 -i 命令行选项获取一种更新的不同的方法。

如果 EXPR 里的前导字符是一个管道符号,open 启动一个新的进程并且把一个只写的文件句柄 联接到该命令。这样你就可以写到那个句柄,并且你写的东西将在那个命令的标准输入里显示。 比如:

   open(PRINTER, "| lpr -Plp1")   or die "cna't fork: $!";
   print PRINTER "stuff\n";
   close(PRINTER)         or die "lpr/close failed: $?/$!";

如果 EXPR 的后跟的字符是一个管道符号,open 还是会启动一个新的进程,不过这次是用一个 只读的文件句柄与之相联。这样就允许把该命令写到它的标准输出的东西在你的句柄里显示出来 用于读取。比如:

   open(NET, "netstat -i -n |")   or die "can't fork: $!";
   while () { ... }
   close(NET)         or die "can't close netstat: $!/$?";

明确地关闭任何任何管道文件句柄都导致父进程等待子进程完成并且在 $?($CHILD_ERROR)里 返回状态码。我们也可以让 close 设置 $!($OS_ERROR)。参阅 close 和 system 里的例子 获取如何解释这些错误代码的信息。

任何包含 shell 元字符(比如通配符或 I/O 重定向字符)的管道命令都会传递给你的系统规范 shell (在 Unix 里是 /bin/sh),所以那些与管道相关的构造可以先处理。如果没有发现 元字符,Perl 就自己启动新进程,而不调用 shell。

你还可以使用三个参数的形式来启动管道。使用该风格的和前面几个管道打开等效的代码是:

   open(PRINTER, "|-", "lpr -Plp1")      or die "can't fork: $!";
   open(NET, "-|", "netstat -i -n")      or die "can't fork: $!";

在这里,第二个参数里的负号代表在第三个参数里的命令。这些命令碰巧不会调用 shell,但是 如果你想保证不会调用 shell,你可以在新版本的 Perl 里说:

   open(PRINTER, "|-", "lpr", "-Plp1")   or die "can't fork: $!";
   open(PRINTER, "-|", "netstat", "-i", "-n")   or die "can't fork: $!";

如果你使用两个参数的形式打开一个管道读进或写出这个特殊的命令“-”,(注:或者你可以把 它当作在上面的三个参数形式中没有写命令。)那么先会隐含到做一个 fork。(在那些不能 fork 的系统上,这样会抛出一个例外。在 Perl 5.6 以前,Microsoft 系统不支持 fork。) 在本例中,负号代表你新的子进程,它是父进程的一个拷贝。如果在父进程里看,从这个派生式的 open 返回的返回值是子进程的 ID,而从子进程里看是 0,而如果 fork 失败则返回 undef—— 这个时候,没有子进程存在。比如:

   defined($pid = open(FROM_CHILD, "-|"))
      or die "can't fork: $!";

   if ($pid) {
      @parent_lines = ;   # 父进程代码
   }
   else {
      print STDOUT @child_lines;      # 子进程代码
   }

这个文件句柄的行为对于父进程来说是正常的,但对于子进程,父进程的输入(或输出)是取自 (或者送到)子进程的 STDOUT(或者 STDIN)的。子进程看不到父进程的文件句柄的打开。 (用 PID 0 就方便标出。)通常,如果你想对管道彼端的命令是如何执行的做更多的控制(比如 你运行着 setuid),或者你不象对 shell 脚本扫描查找元字符,那么你就会愿意用这个构造 取代普通的管道 open。下面的管道打开基本上是相同的:

   open FH,      "| tr   'a-z'   'A-Z'";   # 管道输出给 shell 命令
   open FH, "|-",      'tr',   'a-z',   'A-Z';   # 管道输出给光命令
   open FH, "|-" or exec    'tr',    'a-z',   'A-Z';   # 管道输出给子进程

以及这些;

   open FH,      "cat    -n   'file' |";   # 从 shell 命令管道取来 
   open FH, "-|",      'cat',   '-n',   'file';   # 从光命令管道取来
   open FH, "-|", or exec   'cat',   '-n',   'file' or die;   # 从子进程取来

有关派生打开的更灵活的使用的信息,参阅第十六章的“自言自语”一节和第二十三章,安全性, 里的“清理你的环境”一节。

如果用 open 开始一条命令,你必须选择输入还是输入:“cmd|”是读取,“|cmd”是写出。你 不能用 open 打开一个同时用管道定向输入和输出的命令,象下面这样(目前)非法的符号, “|cmd|”,写的那样。不过,标准的 IPC::Open2 和 IPC::Open3 库过程给你一个非常接近的 等价物。有关双头管道的细节,请参阅第十六章里的“双响通讯”一节。

你还可以象在 Bourne shell 里的传统一样,声明一个以 >& 开头的 EXPR,这种情况下该字串 其他的部分解释成一个将要用 dup2(2) 系统调用(注:目前这个方法不能用于通过自动激活文件 句柄引用的类型团 I/O 对象,不过你总是可以用 fileno 取出文件描述符并且复制那个东西。) 复制的文件句柄的名字(或者文件描述符,如果它是数字)。你可以在 >,>>,<,+>,+>>,和 +< 后面使用 &。(声明的模式应该与最初的文件句柄的模式相匹配。)

你想这么做的一个原因可能是因为你已经有一个打开的文件句柄,并且想做另外一个句柄,而该 句柄是前面一个的真正的复制品。

   open(SAVEOUT, ">&SAVEERR") or die "couldn't dup SAVEERR: $!";
   open(MHCONTEXT, "<&4")     or die "couldn't dup fd4: $!";

这意味着如果一个函数期待一个文件名,但你不想给它一个文件名,因为你已经有一个打开的文件 了,那么你只要把文件句柄前面带一个与号传递给他好了。不过,你最好用一个全称的句柄,以妨 该函数偏巧在另外一个包里:

   somefunction("&main::LOGFILE");

另外一个“dup”文件句柄的理由是临时重定向一个现有的文件句柄而不用丢失最初那个的目的地。 下面是一个保存,重定向,并恢复 STDOUT 和 STDERR 的脚本:

   #! /usr/bin/perl
   open SAVEOUT, ">&STDOUT";
   open SAVEERR, ">&STDERR";

   open STDOUT, ">foo.out"    or die "Can't redirect stdout";
   open STDERR, ">&STDOUT"    or die "Can't dup stdout";

   select STDERR; $| = 1;      # 允许自动冲刷
   select STDOUT;    $| = 1;      # 允许自动冲刷

   print STDOUT "stdout 1\n";   # 这些 I/O 流也传播到
   print STDERR "stderr 1\n";   # 子进程

   system("some command");   # 使用新的stdout/stderr
   
   close STDOUT;
   close STDERR;

   open STDOUT, ">&SAVEOUT";
   open STDERR, ">&SAVEERR";

   print STDOUT   "stdout 2\n";
   print STDERR   "stderr 2\n";

如果该文件句柄或描述符号是前导一个 &=,而不是单单一个 &,那么这次不是创建一个完全新的 文件描述符,而是 Perl 把 FILEHANDLE 作成一个现有的描述符的别名,用的是 C 库调用 fdopen(3)。这样稍微更节约系统资源一些,尽管现在人们已经不太关心这个了。

   $fd = $ENV{"MHCONTEXTFD"};
   open(MHCONTEXT, "<&=fdnum")
      or die "couldn't fdopen descriptor $fdnum: $!";

文件句柄 STDIN,STDOUT,和 STDERR 在跨 exec 中总是保持打开状态。缺省时,其他文件句柄 不是这样的。在那些支持 fcntl 函数的系统上,你可以为一个文件句柄修改 exec 时关闭的标志。

   use Fcntl qw(F_GETFE F_SETFD);
   $flags = fcntl(FH, F_SETFD, 0)
      or die "Can't clear close-on-exec flag on FH: $!\n";

又见第二十八章里的特殊变量 $^F($SYSTEM_FD_MAX)。

对于一个或者两个参数的 open 形式,当你拿一个字串变量做文件名的时候必须小心,因为该变量 可能包含任何古怪的字符(特别是当文件名是通过互联网获取的时候,这时它包含许多古怪的 字符。)如果你不仔细,那么该文件名的一部分可能解释成一个 MODE 字串,或者一个可忽略的 空白,一个复制声明,或者一个负号。下面是一个历史上很有趣的隔离自己的方法:

   $path =~ s#^(\s)#./$!#;
   open(FH, "< $path\0") or die "can't open $path: $!";

不过这个方法仍然在许多方面有缺陷。你应该使用三个参数的 open 清晰地打开任何文件名,而 又不用担心有任何安全问题:

   open(FH, "<", $pach) or die "can't open $path: $!";

另一方面,如果你想要的是一个真正的 C 风格的 open(2) 系统调用,以及还有它的所有问题和 警告,那么可以使用 sysopen:

   use Fcntl;
   sysopen(FH, $path, O_RDONLY) or die "can't open $path: $!";

如果你在那些区分文本和二进制文件的系统上运行,你可能需要把你的文件句柄置于二进制模式 ——或者干脆别这么干,因为这个模式可能破坏你的文件。在这样的系统上,如果你在文本模式 上操作二进制文件,或者在二进制模式上操作一个文本文件,那么你可能不会喜欢看到的结果。

那些需要 binmode 函数的系统和那些不需要该函数的系统的区别是在文本文件使用的格式上。 那些不需要它的系统用一个字符结束每一行,这个字符对应 C 里面的换行符,\n。Unix 和 Mac 落在这个范围里。VMS,MVS,MS-XXX,以及 S&M 操作系统以及其他变种认为在文本文件上的 I/O 和二进制文件上的是不同的,所以它们需要 binmode。

或者其等价物。在 Perl 5.6 里,你可以在 open 函数里声明二进制模式而不用单独调用 binmode。作为 MODE 参数的一部分(但是只在三个参数的形式里),你可以声明各种输入和输出 纪律。要实现和 binmode 一样的功能,使用三个参数的 open 形式,并且在其他 MODE 字符 后面放上一条纪律 :raw:

   open(FH, "<:raw", $path) or die "can't open $path: $!";

因为这是一种非常新的特性,所以到你阅读到这些的时候可能已经比我们写这些的时候有更多纪律 了。不过,我们可以合理地推测所有纪律可能是表 29-2 的部分或全部:

表29-2 I/O 纪律

纪律 含义
:raw 二进制模式;不做处理
:text 缺省文本处理
:def use open 的缺省声明
:latin1 文件应该是 ISO-8859-1
:ctype 文件应该是 LC_CTYPE
:utf8 文件应该是 UTF-8
:utf16 文件应该是 UTF-16
:utf32 文件应该是 UTF-32
:uni 直观 Unicode(UTF-*)
:any 直观 Unicode/Latin1/LC_CTYPE
:xml 使用文件中声明的编码
:crlf 直观换行符
:para 段落模式
:slurp 吞噬模式

你应该可以堆叠那些堆叠在一起有意义的纪律,所以,你可以说:

   open(FH, "<:para:crlf:uni", $path) or die "can't open $path: $!";
   while ($para = )   { ... }

这样将设立纪律如下:

如果你想设置缺省打开模式(:def)来做一些 :text 以外的事情,你可以用 open 用法在你的 文件的顶端声明它:

   use open IN => ":any",   OUT => ":utf8";

实际上,如果哪一天这就是缺省的 :text 纪律,那就太好了。因为它完美地体现了“严于律己, 宽以待人”的概念。

29.2.105 opendir

这个函数打开一个叫 EXPR 的目录给 readdir,telldir,seekdir,rewinddir,和 closedir 处理。如果成功该函数返回真。目录句柄有自己的名字空间,和文件句柄是分离的。

29.2.106 ord

这个函数返回 EXPR 的第一个字符的数字值(ASCII,Latin-1,或者 Unicode)。返回值总是无 符号的。如果你需要有符号值,使用 unpack('c', EXPR)。如果你希望字串中的所有字符都转换 成一列数字,使用 unpack('U*', EXPR)。

29.2.107 our

*our TYPE EXPR : ATTRIBUTES

一个 our 声明一个或多个在当前闭合块,文件或者 eval 里有效的全局变量。也就是说,our 和 my 声明在可视性判断上有着一样的规则,只不过不创建一个新的私有变量;它仅仅是允许对 现有包全局变量的无限制的访问。如果列出了多于一个数值,那么该列表必须放在圆括弧里。

使用 out 声明的一个主要用途就是隐藏变量,使之免于 use strict "vars" 声明的影响;因为 该变量伪装成了一个 my 变量,所以就允许你使用声明了的全局变量而不必使用带着包名的全称。 不过,和 my 声明一样,它只能在 our 声明的词法范围里生效。在这方面,它和 use vars 不一样,后者会影响整个包,而不仅仅是词法范围。

our 和 my 类似的地方还有就是你可以带着 TYPE 和 ATTRIBUTE 声明变量。下面是语法:

   our Dog $spot   :ears(short)   :tail(long);

到我们写这些的时候,它的含义还不是非常清楚。属性可以影响 $spot 的全局的或者局部的含义。 一方面,它又很象用属性的 my 变量,把当前 $spot 的局部外观封装起来而又不干涉该全局量 在其他地方的外观。另一方面,如果一个模块把 $spot 声明为 Dog,而另外一个模块把 $spot 声明为 Cat,那么最后你就可能会有喵喵叫的狗和汪汪叫的猫。这是一个需要研究的课题,当然 这只不过是我们不知道该说些什么的有趣的说法而已。(除了一方面,就是我们的确知道在该变量 指向一个伪散列的时候,我们应该如何处理 TYPE 声明——参阅第十二章的“管理实例数据”。)

our 和 my 相似的另外一个地方就是可视性。一个 our 声明一个全局变量,该变量将在其整个 词法范围都可见,甚至是跨包的边界。该变量位于哪个包是用声明的位置判断的,而不是在使用的 位置。这意味着下面的行为是有问题的并且是注定要失败的:

   
   package Foo;
   our $bar;      # 对于剩下的词法范围而言,$bar 是 $Foo::bar 
   $bar = 582;

   package Bar;
   print $bar;      # 打印 582,就好象“our”是“my”一样

不过,my 会创建一个新的,私有的变量而 our 暴露一个现有的全局变量,这个区别非常重要, 特别是在赋值的时候。如果你把一个运行时赋值和一个 our 声明结合起来,那么该全局变量的 值不会在 our 超出范围之后消失。如果希望它消失,你应该使用 local:

   ($x, $y) = ("one", "two");
   print "before block, x is $x, y is $y\n";
   {
      our $x = 10;
      local our $y = 20;
      print "in block, x is $x, y is $y\n";
   }
   print "past block, x is $x, y is $y\n";

打印出:

   before block, x is one, y is two
   in block x is 10, y is 20
   past block, x is 10, y is two

在同一个词法范围里多个 our 声明是允许的,条件是它们在不同的包里。如果它们碰巧在同一个包 里,Perl 会应你之邀发出警告。

   use warnings;
   package Foo;
   our $bar;      # 为剩余的词法范围声明 $Foo::bar 
   $bar = 20;

   package Bar;
   our $bar = 30;      # 为剩余的词法范围声明 $Foo::bar
   print $bar;      # 打印 30

   our $bar;      # 发出警告

又见 local,my,和第四章的“范围声明”一节。

29.2.108. pack

*pack TEMPLATE, LIST

这个函数接收一个普通 Perl 数值的 LIST 并且根据 TEMPLATE 把它们转换成一个字节串并且返回 该字串。参数列表在必要时会被填充或者截除。也就是说,如果你提供的参数比 TEMPLATE 要求的 少,pack 假设缺的都是空值。如果你提供的参数比 TEMPLATE 要求的多,那么多余的参数被忽略。 在 TEMPLATE 里无法识别的格式元素将会抛出一个例外。

这个模板把该字串的结构描述成一个域序列。每个域都由一个字符代表,描述该值的类型和其编码。 比如,一个格式字符 N 声明一个四个字节的无符号整数,字节序是大头在前。

域是以模板中给出的顺序包装的。比如,如果要把一个一字节的无符号整数和一个单精度浮点数值 包装到一个字串里,你要说:

   $string = pack("CF", 244, 3.14);

返回的字串的第一个字节的值是 244。剩下的字节是 3.14 作为单精度浮点数的编码。浮点数的 具体编码方式取决于你的计算机的硬件。

有些包装时要考虑的重要事情是:

表 29-3 列出了格式字符以及它们的含义。(其他字符也可能在格式中出现;它们在稍后介绍。)

表29-3,pack/unpack 的摸板字符

字符 含义
a 一个填充空的字节串
A 一个填充空格的字节串
b 一个位串,在每个字节里位的顺序都是升序
B 一个位串,在每个字节里位的顺序都是降序
c 一个有符号 char(8位整数)值
C 一个无符号 char(8位整数)值;关于 Unicode 参阅 U
d 本机格式的双精度浮点数
f 本机格式的单精度浮点数
h 一个十六进制串,低四位在前
H 一个十六进制串,高四位在前
i 一个有符号整数值,本机格式
I 一个无符号整数值,本机格式
l 一个有符号长整形,总是 32 位
L 一个无符号长整形,总是 32 位
n 一个 16位短整形,“网络”字节序(大头在前)
N 一个 32 位短整形,“网络”字节序(大头在前)
p 一个指向空结尾的字串的指针
P 一个指向定长字串的指针
q 一个有符号四倍(64位整数)值
Q 一个无符号四倍(64位整数)值
s 一个有符号短整数值,总是 16 位
S 一个无符号短整数值,总是 16 位
u 一个无编码的字串
U 一个 Unicode 字符数字
v 一个“VAX”字节序(小头在前)的 16 位短整数
V 一个“VAX”字节序(小头在前)的 32 位短整数
w 一个 BER 压缩的整数
x 一个空字节(向前忽略一个字节)
X 备份一个字节
Z 一个空结束的(和空填充的)字节串
@ 用空字节填充绝对位置

你可以在你的 TEMPLATE 里自由地使用空白和注释。注释以惯用的 # 符号开头并且延伸到 TEMPLATE 里第一个换行符(如果存在)。

每个字母后面都可以跟着一个数字,表示 count(计数),解释成某种形式的重复计数或者长度, 具体情况取决于格式。除了a,A,b,B,h,H,P,和 Z 之外,所有格式的 count 都是重复次数, 因此 pack 从 LIST 里吃进那么多数值。如果 count 是一个 * 表示剩下的所有东西。

a,A 和 Z 格式只吃进一个数值,但是把它当作一个长度为 count 的字节串打包,并根据需要 填充空或者空格。在解包的时候,A 抽去结尾的空格和空,Z 抽去第一个空后面的所有东西,而 a 原封不动地返回文本数据。打包的时候,a 和 Z 是相同的。

与之类似,b 和 B 格式打包一个长度为 count 的位串。输入域里的每个字节都以每个输入字节的 最低位(也就是 ord($byte) % 2)为基础生成结果中的 1 个位。方便一点来说,这意味着字节 0 和 1 生成位 0 和 1。从输入字串的开头开始,每 8 个字节转换成一个字节的输出。如果输入字串 的长度不能被 8 整除,那么余下的部分用 0 补齐。类似,在 uppack 的时候,任何额外的位都被 忽略。如果输入字串比需要的长,那么多出来的部分被忽略。count 如果是 * 意思是使用输入域的 所有字节。在解包的时候,这些位转换成一个 0 和 1 组成的字串。

h 和 H 格式打包一个由 count 个半字节(4 位组,常用于代表十六进制位。)组成的字串。

p 格式打包一个指向一个空结尾的字串。你有责任确保该字串不是一个临时数值(因为临时值可能 在你能使用打包的结果之前很可能被释放掉)。P 格式打包一个指向一个结构的指针,该结构的 大小由 count 指明。如果对应的 p 或 P 的值是 undef,则创建一个空指针。

/ 字符允许对这样一个字串进行打包或解包:这个打了包的结构包含一个字节数以及后面跟着字串 本身。你可以这样写 length-item/string-item。length-item 可以是任意 pack 模板字符, 并且描述长度值是如何打包的。最常用的可能是那些整数打包的东西,比如 n(用于打包 Java 字串),w(用于打包 ASN.1 或 SNMP)以及 N(用于 Sun XDR)。string-item 目前必须是 A*,a*,或者 Z*。对于 unpack 而言,字串的长度是从 length-item 里获取的,但是如果你放 * 进去,那么它将被忽略。

   unpack 'C/a',   "\04Gurusamy";      # 生成 'Guru'
   uppack 'a3/A* A*', '077 Bond J ';      # 生成 (' Bond', 'J')
   pack 'n/a* w/a*', 'hell', ','world';      #  生成 "hello, world"

length-item 不会从 unpack 明确地返回。向 length-item 字母加一个 count 不一定能干有用 的事,除非该字母是 A,a,或 Z。用带 length-item 的 a 或 Z 进行打包可能会引入空(\0) 字符,这时候,Perl 在会认为它不是合法数字字串。

整数格式 s,S,l,和 L 的后面可以紧跟一个 !,表示本机的短整数或者长整数,而不是各自准确 的 16 位和 32 位。现在,这是一个在许多 64 位平台上的问题,在这些平台上本地 C 编译器看到 本机短整数和长整数可能和上面的这些值不同。(i! 和 I! 也可以用,不过只不过是为了保持 完整;它们与 i 和 I 相同。)

你可以通过 Config 模块获取制作你的 Perl 的平台上的本机的 short,int,long 和 long long 的实际长度:

   use Config;
   print $Config{shortsize},      "\n";
   print $Config{intsize},      "\n";
   print $Config{longsize},      "\n";
   print $Config{longlongsize},   "\n";

这里只是说 Configure 知道一个 long long 的大小,但着并不意味着你就能用 q 和 Q。(有些 系统能有,不过你用的系统很可能还没有。)

长度大于一个字节的整数格式(s,S,i,I,l,和 L)都是天生不能在不同处理器之间移植的, 因为它们要遵从本机字节序和位权重序的规则。如果你需要可移植的整数,那么使用格式 n,N, v,和 V;因为它们是字节权重序并且是尺寸已知的。

浮点数只以本机格式存在。因为浮点格式的千差万别而且缺乏一种标准的“网络”表现形式,所以 没有做任何浮点数交换的工具。这意味着在一台机器上打包了的浮点数数据可能不能在另外一台上 读。甚至如果两台机器都使用 IEEE 浮点数算法的话,这都仍然是一个问题,因为与权重相关的 内存表现形式不是 IEEE 规范的一部分。

Perl 在内部使用双精度数进行所有浮点数计算,所以从 double 转换成 float,然后又转换成 float 会损失精度。这就意味着 unpack("f", pack("f", $foo)) 可能不等于 $foo。

你有责任为其他程序考虑任何对齐或填充的问题,尤其是那些带着 C 编译器自己的异质概念的 C struct 的程序,C 编译器在不同的体系结构上对 C struct 如何布局有着巨大的差别。你可能 需要在打包时增加足够的 x 来弥补这个问题。比如,一个 C 声明:

   struct foo {
      unsigned char c;
      float f;
   };

可以写成一个“C x f”格式,一个“C x3 f”格式,或者甚至是“f C”格式——而且这只是其中 一部分。pack 和 unpack 函数把它们的输入和输出当作平面的字节序列处理,因为它们不知道 这些字节要去哪儿,或者从哪儿来。

让我们看一些例子,下面的第一部分把数字值包装成字节:

   $out = pack "CCCC", 65, 66, 67, 68;      # $out 等于"ABCD"
   $out = pack "C4", 65, 66, 67, 68;         # 一样的东西

下面的对 Unicode 的循环字母做同样的事情:

   $foo = pack("U4", 0x24b6, 0x24b7, 0x24b8, 0x24b9);

下面的做类似的事情,增加了一些空:

   $out = pack "CCxxCC", 65, 66, 67, 68;      # $out 等于 "AB\0\0CD"

打包你的短整数并不意味着你就可移植了:

   $out = pack "s2", 1, 2;         # 在小头在前的机器上是 "\1\0\2\0" 
                     # 在大头在前的机器上是 "\0\1\0\2" 

在二进制和十六进制包装上,count 指的是位或者半字节的数量,而不是生成的字节数量:

   $out = pack "B32", "...(略)";
   $out = pack "H8", "5065726c";         # 都生成“Perl”

a 域里的长度只应用于一个字串:

   $out = pack "a4", "abcd", "x", "y", "z";      # "abcd"

要绕开这个限制,使用多倍声明:

   $out = pack "aaaa",    "abcd", "x", "y", "z";   # "axyz"
   $out = pack "a" x 4,   "abcd", "x", "y", "z";   # "axyz"

a 格式做空填充:

   $out = pack "a14", "abcdefg";         # " abcdefg\0\0\0\0\0\0"

这个模板打包一个 C 的 struct tm 记录(至少在一些系统上如此):

   $out = pack "i9pl", gmtime(), $tz, $toff;

通常,同样的模板也可以在 unpack 函数里使用,尽管一些模板的动作不太一样,特别是 a,A, 和 Z。

如果你想把定长文本域连接到一起,可以用 TEMPLATE 是多个 a 或者 A 的 pack:

   
   $string = pack("A10" x 10, @data);

如果你想用一个分隔符连接变长文本域,那么可以用 join 函数:

   $string = join(" and ", @data);
   $string = join ("", @data);         #  空分隔符

尽管我们所有的例子都使用文本字串做模板,但我们没有理由不让你使用来自磁盘文件的模板。 你可以基于这个函数做一个完整的关系型数据库。(我们不会想知道那样能证明你什么东西。)

29.2.109. package

它并不是一个真正的函数,只是一个声明,表示剩余的最内层的闭合范围属于它指明的符号表或者 名字空间。(因此 package 声明的范围和 my 或 our 声明的范围是一样的。)在它的范围里, 该声明令编译器把所有唯一的全局标识符都解析为到这个声明的包的符号表中寻找。

一个 package 声明只影响全局变量——包括那些你用了 local 的变量——而不包括你用 my 创建 的词法变量。它只影响没有修饰的全局变量;那些带有自身包名字修饰的全局变量忽略当前所声明的 包。用 our 声明的全局变量是没有修饰的,因此它们要受当前包影响,但是只是在声明的那个点才 受影响,然后它的行为就好象 my 变量一样。也就是说,对于它们的词法范围的剩余部分,our 变量是在声明的地方“钉进”正在使用中的包中去的,甚至随后的包声明干涉进来也如此。

通常,你会把 package 声明作为一个文件里的要被 rquire 或者 use 操作符包括的第一个东西, 但是你可以把 package 放在任何语句也可以合法放置的地方。当我们创建一个传统的或面向对象的 模块文件的时候,我们习惯上把包的名字命名成和文件的名字相同,以此来避免混淆。(习惯上还 喜欢把这样的包名字命名成以大写字母开头,因为小写字母模块通常会被解释成用法模块。)

你可以在多于一个地方切换到一个给定的模块中;它只是影响编译器对该块的剩余部分使用哪个 符号表的决策。(如果编译器在同一个层次看到另外一个 package 声明,那么新的声明覆盖前面的 那个。)Perl 假设你的主程序以一个不可见的 package main 声明开始。

你可以通过用包名字和双引号修饰对应标识符的方法来引用其它包的变量,子过程,句柄,和格式 等:$Package::Variable。如果包名字是空,那么就假设是主包(main package)。也就是说, $::sail 等效于 $main::sail,也等效于 $main'sail,在那些老一些的代码里还会看到这样的 标识符。

下面是一个例子:

   package main;      $sail = "hale and hearty";
   package Mizzen;   $sail = "tattered";
   package Whatever;
   print "My main sail is $main::sail.\n";
   print "My mizzen sail is $Mizzen::sail.\n";

它打印出:

   My main sail is hale and hearty.
   My mizzen sail is tattered.

一个包的符号表是存储在一个散列里的,它的名字是以双冒号结尾的。比如,主包的符号表叫 %main::。因此现存的包符号 *main::sail 也可以通过 $main::{"sail"} 来访问。

如果省略了 NAMESPACE,那么就没有当前包存在,因此所有标识符必须是全称或者声明为词法范围。 它比 use strict 更严格,因为它的范围还扩展到函数名字。

参阅第十章,包,获取有关包的更多信息。参阅本章早些的 my 获取其他范围相关的问题的信息。

29.2.110 pipe

和对应的系统调用类似,这个函数打开一对相互联接的管道——参阅 pipe(2)。通常这个函数在 fork 之前调用,然后管道的读端关闭 WRITEHANDLE,而写端关闭 READHANDLE。(否则这个管道 在写端关闭它以后不会告诉读端 EOF。)如果你设立了一个管道化的进程的循环,那么除非你非常 细心,否则很有可能发生死锁。另外,请注意 Perl 的管道使用标准的 I/O 缓冲,所以你可能需要 在你的 WRITEHANDLE 上设置 $|($OUTPUT_AUTOFLUSH)以保证在每次输出操作之后冲刷数据, 具体情况取决于应用——参阅 select (输出文件句柄)。

(和 open 一样,如果两个文件句柄都没有定义,那么它们将被自动激活。)

下面是一个小例子:

   pipe(README, WRITEME);
   unless ($pid = fork) {   #    子进程
      defined $pid or die "can't fork: $!";
      close(README);
      for $i (1..5) { print WRITEME "line $i\n" }
      exit;
   }
   $SIG{CHLD} = sub { waitpid($pid, 0) };
   close(WRITEME);
   @strings = ;
   close(README);
   print "Got:\n", @strings;

请注意这里写入端是如何关闭读端以及读取端是如何关闭写入端的。你不能使用一条管道进行双向 交流。要么使用两个不同的管道,要么使用 socketpair 系统调用。参阅第六章里的“管道” 一节。

29.2.111. pop

这个函数把一个数组当作一个堆栈对待——它弹出(删除)并返回数组的最后一个值,把数组缩短 一个元素长度。如果省略了 ARRAY,那么该函数在子过程或者格式的词法范围里弹出 @_;在文件 范围(通常是主程序),或者是用 eval STRING,BEGIN{},CHECK{},INIT{},和 END{} 构造 建立的词法范围里,它弹出 @ARGV。它和下面的东西有同样的效果:

   $tmp = $ARRAY[$#ARRAY--];

或者:

   
   $tmp = splice @ARRAY, -1;

如果在数组里没有元素,那么 pop 返回 undef。(不过,如果你的数组包含 undef 数值,那么 可不要依赖这个特性猜测数组是否为空!)又见 poshi 和 shift。如果你想弹出超过一个元素, 那么使用 splice。

pop 要求它的第一个参数是一个数组,而不是一个列表。如果你只想一个列表的最后一个元素, 那么用:

   ( LIST )[-1]

29.2.112 pos

这个函数返回上一次 m//g 对 SCALAR 搜索在 SCALAR 中留下来的位置。它返回在最后一个匹配 字符后面的字符的偏移量。(也就是说,它等效于 length($`) + length($&)。)这个位置是 下一次在该字串上 m//g 开始的偏移量。请注意字串开头的偏移量是 0。比如:

   $graffito = "fee fie foe foo";
   while ($graffito =~ m/e/g) {
      print pos $graffito, "\n";
   }

打印 2,3,7,和 11,就是每个跟在“e”后面的字符的偏移量。pos 函数可以赋予一个数值以 告诉下一次 m//g 开始的位置:

   $graffito = "fee fie foe foo";
   pos $graffito = 4;   # 忽略 fee,开始 fie
   while ($graffito =~ m/e/g) {
      print pos $graffito, "\n";
   }

这样只打印出 7 和 11。正则表达式断言 \G 只匹配当前 pos 为被搜索字串声明的位置。参阅 第五章的“位置”一节。

29.2.113 print

这个函数打印一个字串或者一列用逗号分隔的字串。如果设置了 $\($OUTPUT_RECORD_SEPARATOR) 变量,那么它将在列表结尾隐含地打印出来。如果成功,该函数返回真,否则返回假。FILEHANDLE 可以是一个标量变量的名字(未代换的),这个时候该变量要么包含实际文件句柄的名字或者包含 一个指向某种文件句柄对象的引用。和任何其他间接对象一样,FILEHANDLE 也可以是一个返回 这种数值的块:

   print { $OK ? "STDOUT" : "STDERR" } "stuff\n";
   print { $iohandle[$i] } "stuff\n";

如果 FILEHANDLE 是一个变量而下一个记号是一个项,那么它可能就会错误地解释成一个操作符, 除非你在它们中间放一个 + 或者在参数周围放上圆括弧。比如:

   print $a - 2;   # 向缺省文件句柄(通常是 STDOUT)打印 $a - 2
   print $a (-2);   # 向在 $a 中声明的文件句柄中打印 -2
   print $a -2;   #  也打印 -2(古怪的分析规则 :-))

如果省略了 FILEHANDLE,那么该函数打印到当前选择的输出文件句柄,初始时是 STDOUT。要把 缺省的输出文件句柄设置为 STDOUT 之外的东西,请使用 select FILEHANDLE 操作。(注:从此 以后,STDOUT 就不再是 print 的缺省输出文件句柄了。它只是缺省的缺省文件句柄。)如果还 省略了 LIST,那么该函数打印 $_。因为 print 接受 LIST,所以在 LIST 里的任何东西都在 列表环境中计算。因此,当你说:

   print OUT ;

它不会从标准输入中打印出下一行,而是来自标准输入的所有剩余的行知道文件结尾 (end-of-fiel),因为那些东西是 在列表环境中返回的东西。如果你需要另外的结果, 请说:

   print OUT scalar ;

同样,请记住“如果它看起来象函数,那它就是函数”的规则,要注意不要在 print 关键字后面 跟一个左圆括弧,除非你想用对应的右圆括弧结束 print 的参数——在中间插入一个 + 或者在 所有参数周围放上圆括弧:

   print (1+2)*3,   "\n";      # 错
   print +(1+2)*3,   "\n";      #  对
   print ((1+2)*3,   "\n");      # 对

29.2.114 printf

这个函数向 FILEHANDLE 输出一条格式化的字串或者如果省略的时候,则是向当前选定的输出文件 句柄打印,输出文件句柄初始时是 STDOUT。在 LIST 里的第一个项必须是一个字串,该字串声明 如何格式化其他的项。它和 C 库的 printf(3) 和 fprintf(3) 函数类似。该函数等效于:

   print FILEHANDLE sprintf FORMAT, LIST

只不过上面的句子中没有附加 $\($OUTPUT_RECORD_SEPARATOR)。如果 use locale 起作用, 那么用于表示格式化的浮点数的小数点的字符将受到 LC_NUMERIC 的区域设置的影响。

只有当一个非法的引用类型用做 FILEHANDLE 参数的时候才会抛出一个例外。不识别的格式都丝毫 未动地传递出去。如果打开了警告,那么两种情况都会触发警告。

参阅本章其他地方的 print 和 sprintf 函数。sprintf 的描述中包括格式声明列表的描述。 我们本可以在这里复制一份,不过这本书已经是生态杀手了。

如果你省略了 FORMAT 和 LIST,则使用 $_——但是在那些情况里,你应该使用 print。如果简单 的 print 就可以用,不要使用 printf。print 函数更加高效并且也少一些出错机会。

29.2.115. prototype

把一个函数的原形当作字串返回(如果该函数没有原型返回,undef)。FUNCTION 是一个指向你 想知道原型的函数的引用或者名字。

如果 FUNCTION 是一个以 CORE:: 开头的字串,那么其他就会当作一个 Perl 内建函数的名字, 如果没有这样的内建函数,那么就抛出一个例外。如果这个内建函数不是可覆盖的(象 qw//)或者 是它的参数不能用一个原型表达(比如 system),那么此函数就返回 undef,因为该内建函数的 行为并不象 Perl 函数那样。否则,返回描述等效原型的字串。

29.2.116 push

这个函数把 ARRAY 当作一个堆栈并且把 LIST 的值推进 ARRAY 尾部。ARRAY 的长度增加 LIST 的长度。该函数返回新长度。push 函数和下面的代码有一样的效果:

   foreach $value (listfunc()) {
      $array[++$#array] = $value;
   }

或者:

   splice @array, @array, 0, listfunc();

但是这个函数更高效些(不管对你还是你的计算机)。你可以拿 push 和 shift 一起使用,制作 一个时间上相当高效的移动寄存器或者队列:

   for(;;) {
      push @array, shift @array;
   }

又见 pop 和 unshift。

29.2.117. q/STRING/

通用引号。参阅第二章里的“选择自己的引号”有关 qx// 的状态注解,参阅 readpipe。有关 qr// 的状态注解,参阅 m//。又见第五章“内部控制”。

29.2.118. quotemeta

这个函数返回 EXPR 的值,并且把所有非字母数字的字符都前缀反斜杠。(也就是说,字串里所有 不匹配 /[A-Za-z_0-9]/ 的字符都会在前面前缀反斜杠,不管区域设置是什么。)它是在代换环境 中内部实现 \Q 逃逸的函数(包括双引号的字串,反勾号,以及模式。)

29.2.119 rand

这个函数范围一个伪随机的浮点数数字,该数字大于等于 0 而小于 EXPR 的值。(EXPR 应该是 正。)如果省略了 EXPR,该函数返回一个在 0 和 1 之间的浮点数(包括 0,但是不包括 1)。 除非已经调用了 srand,否则 rand 自动调用 srand。又见 srand。

要获取一个整数值,比如说给一个退出的角色赋值,你可以把它和 int 组合在一起,比如:

   $roll = int(rand 6) + 1;      # $roll 现在是一个介于 1 和 6 之间的数字

因为 Perl 使用你自己的 C 库的伪随机数函数,比如说 random(3) 或者 drand48(3)等,所以 我们不能保证生成的数值的分布质量。如果你需要更强壮的随机,比如说用于加密用途,你可能 应该参考一下 random(4) 的文档(如果你的系统有 /dev/random 或者 /dev/urandom 设备), CPAN 模块 Math::TrulyRandom,或者一本关于伪随机数的计算生成的好教科书,比如 Knuth 的 第二卷。

29.2.120 read

这个函数试图从声明的 FILEHANDLE 中读取 LENGTH 字节的数据到 SCALAR 变量中。该函数返回 读取的字节的数量,到文件结尾时返回 0。它在出错的时候返回 undef。SCALAR 将增长或者缩短 到实际读取的长度。如果声明了 OFFSET,则决定该变量从那里开始输出字节,这样你就可以在一个 字串中间读取数据。

要从文件句柄 FROM 中拷贝数据到文件句柄 TO,你可以说:

   while(read(FROM, $buf, 16384)) {
      print TO $buf;
   }

read 的反操作只是一个简单的 print,它已经知道你想写的字串长度并且可以写任意长度的字串。 不要误用 write,因为它只用语 format。

Perl 的 read 函数是用标准 I/O 的 fread(3) 函数实现的,所以实际的 read(2) 系统调用可能 会读取超过 LENGTH 字节的数据填进缓冲区,而且 fread(3) 可能会做多次 read(2) 系统调用以 填充缓冲区。要获得更好的控制,用 sysread 声明真正的系统调用。除非你想自找麻烦,否则请 不要混合 read 和 sysread。不管你使用哪个,都要注意如果你从一个包含 Unicode 或者任何 其他多字节编码的文件里读取,那么缓冲区的边界可能落在字符的中间。

29.2.121. readdir

这个函数从一个用 opendir 打开的目录句柄读取目录记录(它们就是文件名)。在标量环境中, 这个函数返回下一个目录记录(如果存在的话);否则,它返回 undef。在列表环境中,它返回 在该目录中所有剩下的记录,如果剩下没有记录了,那么这个返回可能是一个空列表。比如:

   opendir(THISDIR, ".") or die "serious dainbramage: $!";
   @allfiles = readdir THISDIR;
   closedir THISDIR;
   print "@allfiles\n";

上面的代码在一行里打印出当前目录的所有文件。如果你想避免“.”和“..”记录,使用下面的 咒语中的一条(你认为最不好念的那条):

   @allfiles = grep { $_ ne '.' and $_ ne '..' } readdir THISDIR;
   @allfiles = grep { not /^[.][.]?\z/ } readdir THISDIR;
   @allfiles = grep { not /^\.{1,2}\z/ } readdir THISDIR;
   @allfiles = grep !/^\.\.?\z/, readdir, THISDIR;

为了避免所有 .* 文件(象 ls 程序):

   @allfiles = grep !/^\./, readdir THISDIR;

只拿出文本文件,说:

   @textfiles = grep -T, readdir THISDIR;

不过我们再看看最后一个例子,因为如果 readdir 的结果不在当前目录里,那么我们需要在它的 结果上把目录部分粘回去——象这样:

   opendir(THATDIR, $path) or die "can't opendir $path: $!";
   @dotfile = grep { /^\./ && -f } map { "$path/$_" } readdir(THATDIR);
   closedir THATDIR;

29.2.122 readline

这个函数是在内部实现 操作符的函数,但是你可以直接使用它。该函数从 FILEHANDLE 中读取下一条记录,FILEHANDLE 可以是一个文件句柄名称或者一个间接的文件句柄 表达式,该表达式要么返回实际文件句柄的名字要么返回一个指向任何类似文件句柄的东西的引用 (比如一个类型团)。(早于 5.6 版本的 Perl 只接受一个类型团。)在标量环境里,每次调用 读取并返回下一条记录,直到到达文件结尾,这个时候后继的调用将返回 undef。在列表环境中, readline 读取记录直到文件结束,然后返回一个记录列表。这里说的“记录”,我们通常的意思是 一行文本,但是通过改变 $/($INPUT_RECORD_SEPARATOR)的值可以让这个操作符用不同的方法 给文本“分段”。还有,有些输入纪律,比如 :para(段落模式)将以块的方式返回记录而不是行。 设置 :slurp 纪律(或者解除 $/ 的定义)可以令这样的一块就是整个文件。

在标量环境中读取文件的时候,如果你碰巧读进了一个空文件,readline 第一次返回 "",然后在 后继的每次调用都返回 undef。如果从一个 ARGV 文件句柄中读取数据,那么每个文件都返回一个 块(同样,空文件返回 ""),当全部读取完成的时候再读取就返回 undef。

操作符在第二章的“输入操作符”中有更详细的讨论。

   $line = ;
   $line = readline(STDIN);      # 和上面一样
   $line = readline(*STDIN);      # 和上面一样
   $line = readline(\*STDIN);      # 和上面一样

   open my $fh, "<&=STDIN" or die;
   bless $fh => 'AnyOldClass';
   $line = readline($fh);      # 和上面一样

29.2.123 readlink

这个函数返回一个符号链接指向的文件名。EXPR 应该计算出一个文件名,而且它的最后一个组成 部分应该是一个符号链接。如果它不是符号链接,或者如果符号链接没有在文件系统里实现,或者 如果发生某种系统错误,则返回 undef,并且你应该检查在 $! 里的错误代码。

请注意这个返回的符号链接可能是相对于你声明的位置。比如,你可能说:

   readlink "/usr/local/src/express/yourself.h"

而 readlink 可能会返回:

   ../express.1.23/includes/yourself.h

除非你的当前目录碰巧是 /usr/local/src/express,否则这个东西可能不能直接当文件名使用。

29.2.124. readpipe

它是实现 qx// 引起构造(也称之为反勾号操作符)的内部函数。偶尔它和很有用,比如说你需要 一种特殊的声明你的 EXPR 的方法,而这种方法如果使用引起的形式的话并不方便。请注意的是 我们以后可能改变它的接口以支持 LIST 参数,这样它就更象 exec 函数,所以,不要假设它会 继续为 EXPR 提供标量环境。你应该自己给他 scalar,或者试着使用 LIST 形式。天知道到你 阅读到这些内容的时候是不是就可以用了。

29.2.125 recv

这个函数接收套接字上的一条信息。它试图从声明的 SOCKET 文件句柄中接收 LENGTH 个字节的 数据到变量 SCALAR 中。该函数返回发送者的地址,或者如果有错误的话是 undef。SCALAR 将 根据所读取的数据增长或者缩短到数据长度。该函数接受与 recv(2) 相同的标志。参阅第十六章的 “套接字”一节。

29.2.126 redo

redo 操作符在不经重新计算条件的情况下重新开始一个循环块。如果存在任何 continue 块也 不会执行它们。如果省略了 LABEL,那么该操作符指向最内层的闭合循环。该操作符通常用于那些 希望欺骗自己输入的程序:

# 一个把那些用反斜杠续起来的行接起来的循环

   while() {
      if (s/\\\n$// && defined($nextline =)) {
         $_ .= $nextline;
         redo;
      }
      print;      # 或者别的什么...
   }

redo 不能用于退出一个有返回值的块,比如 eval {},sub {},或者 do {},并且不能用于退出 一个 grep 或者 map 操作。在打开了警告的时候,如果你 redo 一个不在你的当前词法范围的 循环,那么 Perl 会警告你。

一个块本身等效于一个只执行一次的循环。因此在这样的块里的 redo 将很有效地把它变成一个 循环构造。参阅第四章里的“循环控制”。

29.2.127 ref

如果 EXPR 是一个引用,那么 ref 操作符返回一个真值,否则返回一个假。返回的值取决于该引用 所引用的东西的类型。内建的类型包括:

   SCALAR
   ARRAY
   HASH
   CODE
   GLOB
   REF
   LVALUE
   IO::Handle

如果这个引用的对象已经赐福到了一个包中,那么返回的就是该包的名字。你可以把 ref 当作一种 “类型是”操作符。

   if (ref($r) eq "HASH") {
      print "r is a reference to a hash.\n";
   }
   elsif (ref($r) eq "Hump") {      # 烦人——见下文
      print "r is a reference to a Hump object.\n"
   }
   elsif (not ref $r) {
      print "r is not a reference at all.\n";
   }

我们认为测试你的对象的类名字是否等于任意特定的类名字是非常糟糕的 OO 风格,因为一个派生 类会有不同的名字,但是应该允许访问基类的方法。这种情况最好用下面的 UNIVERSAL 方法:

   if ($r->isa("Hump") ){
      print "r is a reference to a Hump object, or subclass.\n"
   }

最好是根本别测试,因为除非 OO 机制在一开始就觉得合适,否则它不会给你的方法发送对象。 参阅第八章和第十二章获取更多细节。又见第三十一章里的 use attributes 用法里的 reftype 函数。

29.2.128 rename

这个函数修改一个文件的名字。成功时返回真,否则返回假。它(通常)不能跨文件系统运行, 不过在 Unix 系统上 mv 通常可以做这件事情。如果一个名叫 NEWNAME 的文件已经存在,那么它 将被删除。非 Unix 系统上可能还有其他限制。

参阅标准的 File::Copy 模块获取跨文件系统的重命名。

29.2.129 require

这个函数断言在其参数上的某种依赖性。

如果该参数是一个字串,require 装载并执行那些在放在独立文件内的 Perl 代码,该文件的名字 是该字串给出的。这种情况类似在一个文件上做 do,只不过是 require 会检查一下,看看该 库文件是否已经装载了,而且,如果它碰到任何问题都会抛出一个例外。(因此它可以用于表达 文件依赖性而不用担心重复编译。)和它的表亲 do 和 use 类似,require 知道如何搜索保存在 @INC 数组里的包含文件并且在成功的时候更新 %INC。参阅第二十八章。

该文件必须返回真作为最后的值以表示任何初始化代码的成功执行,因此我们习惯上用 1 结束这种 文件;除非你确信它返回真。

如果 require 的参数是一个版本号,比如 5.6.2 这样;require 实际上就是要求当前正在运行的 Perl 版本必须至少是那个版本。(Perl 还接受一个浮点数,比如 5.005_03,这样就和老版本的 Perl 兼容,但是现在我们不鼓励使用那种形式,因为来自其他文化的家伙们不理解它。)因此, 一个需要 Perl 5.6 的脚本可以把下面这个做其第一行:

   require 5.6.0;         # 或者 require v5.6.0

这样早期的 Perl 版本就会退出。不过,和所有 require 一样,这些事情是在运行时发生的。你 可能更愿意说 use 5.6.0 以获得编译时间的检查。又见第二十八章里的 $PERL_VERSION。

如果 require 的参数是一个光包的名字(参阅 package),rquire 假设它有一个自动的 .pm 后缀,这样就令它容易装载标准的模块。这个特性类似 use,只不过是发生在运行时间而不是 编译时间,并且没有调用 import 模块。比如,要在不往当前包增加任何符号的情况下把 Socket.pm 拉进来,你可以说:

   require Socket;         # 而不是 "use Socket;"

不过,你可以用下面的代码获得相同效果,而且如果找不到 Socket.pm 的话还可以得到编译时 警告:

   use Socket ();

在一个光名字上使用 require 还把任何包名字里的 :: 替换成你的系统的目录分隔符,通常是 /。 换句话说,如果你试验下面的代码:

   require Foo::Bar;      # 很好的空名

那么 require 函数在 @INC 数组里声明的目录里寻找 Foo/Bar.pm 文件。但是如果你试验这些:

   $class = 'Foo::Bar';
   require $class;      # $class 不是一个光名

或者:

   require "Foo::Bar";      # 引号文本不是光名

那么 require 函数将在 @INC 数组里寻找 Foo::Bar 文件并且回抱怨没有在那里找到 Foo::Bar。 如果这样,你可以用:

   eval "require $class";

又见 do FILE,use 命令,use lib 用法,以及标准的 FindBin? 模块。

29.2.130 reset

这个函数通用于(或者滥用于)循环的顶端或者在循环尾部的一个 continue 块里,用于清理全局 变量或者重置 ?? 搜索,这样它们就又可以运转了。表达式解释成一个单字符的列表(连字符可以 用做表示范围)。所有以这些字符之一开头的标量变量,数组,以及散列都恢复到它们最早的状态。 如果省略了该表达式,那么匹配一次的搜索(?PATTERN?)被重置,重新进行匹配。该函数只为当前 包重置变量或搜索。它总是返回真。

要重置所有“X”变量,你可以说:

   reset 'X';

要重置所有小写变量,说:

   reset 'a-z';

最后,只重置 ?? 搜索,说:

   reset;

我们不推荐你在 main 包里重置“A-Z”,因为你会把你的所有 ARGV,INC,ENV,和 SIG 数组 和散列都摧毁。

词法变量(由 my 创建)不受影响。reset 的使用已经模糊地废弃了,因为它很容易清空整个名字空间, 而且 ?? 操作符本身也是模糊地废弃了。

又见来自标准 Symbol 的 delete_package() 函数,以及在第二十三章的“安全隔仓”里记录的 所有安全隔仓的内容。

29.2.131. return

这个操作符令当前子过程(或者是 eval 或 do FILE)马上带着声明的数值返回。试图在这些地方 之外使用 return 将抛出一个例外。还要注意 eval 不能代表调用它的子过程做 return。

EXPR 可能会在列表,标量,或者空环境中计算,具体是哪种环境由如何使用该返回值决定,而这个 情况可能每次执行都会不一样。也就是说,你提供的表达式将在子过程调用的环境中计算。如果 子过程是在标量环境中调用的,那么 EXPR 也在标量环境中计算。如果该子过程是在列表环境中 调用的,那么 EXPR 也是在列表环境中计算并且返回一个数值列表。没有参数的 return 在标量 环境中返回标量值 undef,在列表环境中返回一个空列表 (),而(自然)在空环境中什么也 不返回。子过程调用的环境可以在子过程里用(错误命名的) wantarray 函数来判断。

29.2.132 reverse

在列表环境里,这个函数返回一个数值列表,该列表包含反序排列的 LIST 的元素。该函数可以 用于创建递减序列:

   for (reverse 1 .. 10) { ... }

因为如果散列作为 LIST 传递,那么它就会平面化为一个列表,所以 reverse 还可以用于反转 一个散列,假设其值是唯一的:

%barfoo = reverse %foobar;

在标量环境里,该函数连接 LIST 的所有元素,然后返回这个连接出来的字串的一个字符一个字符 的反序,

一条小提示:反转一个由用户定义函数排序的列表的时候,可能更容易通过先以相反方向对该列表 排序来实现。

29.2.133. rewinddir

这个函数为操作 DIRHANDLE 的 readdir 过程把当前位置设置到目录的开头。该函数可能无法在 所有支持 readdir 的系统上使用,如果系统没有实现 rewinddir,那么它会退出。它成功的时候 返回真,否则返回假。

29.2.134. rindex

这个函数运行起来和 index 很相象,只不过它返回在 STR 里最后发生的 SUBSTR 的位置(反向的 index)。如果没有找到 SUBSTR,那么该函数返回 $[-1。因为 $[ 现在实际上总是 0,而该函数 实际上总是返回 -1。如果声明了 POSITION,那么它就是可以返回的最右端的位置。要想从后向前 扫描一遍你的字串,说:

   $pos = length $string;
   while (($pos = rindex $string, $lookfor, $pos) >= 0) {
      print "Found at $pos\n";
      $pos--;
   }

29.2.135. rmdir

如果 FILENAME 声明的目录是空的,那么此函数删除该目录。如果该函数成功,它返回真;否则, 返回假。如果你想先删除该目录的内容,而又因为什么原因不想调用 shell 里的 rm -r,(比如 说没有 shell,或者没有 rm 命令,因为你没办法获得 PPT。)那么可以看看 File::Path 模块。

29.2.136 s///

替换操作符。参阅第五章里的“模式匹配操作符”。

29.2.137. scalar

这个伪函数可以用于 LIST 里,当在列表环境中计算会生成一个不同的结果的时候,强迫 EXPR 在 标量环境中计算。比如:

   my ($nextvar) = scalar ;

避免 在做赋值之前从标准输入把所有的行都读了进来,因为给一个列表(甚至是一个 my 列表)赋值都会产生一个列表环境。(在这里例子里如果没有 scalar,那么来自 的 第一行仍然会赋予 $nextvar,但是随后的行将会被读取并抛弃,因为我们赋值的目标列表只能接受 一个标量数值。)

当然,简单些而且没有那么混乱的方法是不使用圆括弧,这样就把标量环境改变成了列表环境:

   my $nextvar = ;

因为 print 函数是一个 LIST 操作符,所以如果你想把 @ARRAY 的长度打印出来,那么你不得不 说:

   print "Length is ", scalar(@ARRAY), "\n";

Perl 里没有与 scalar 对应的 “list”函数,因为实际上我们从来不需要强迫在列表环境里 计算。这是因为任何需要 LIST 的操作已经给他的列表参数免费提供了一个列表环境。

因为 scalar 是单目操作符,如果你不小心给 EXPR 使用了圆括弧的列表,那么这个东西的行为 就象一个标量逗号表达式一样,在空环境中计算除最后一个列表元素之外的所有其他元素,然后 返回在标量环境中计算的最后一个元素。你很少想要这样的东西。下面的一个语句:

   print uc(scalar(&foo, $bar)), $baz;

在道义上是等效于下面两个的:

   &foo;
   print(uc($bar), $baz);

参阅第二章获取关于逗号操作符的更多细节。参阅第六章的“原型”获取关于单目操作符更多的 信息。

29.2.138 seek

这个函数为 FILEHANDLE 定位文件指针,就好象用于标准 I/O 的 fseek(3) 调用一样。文件里的 第一个位置是在偏移量 0 处,而不是 1 处。同样,偏移量指的是字节位置,而不是行数。通常, 因为行的长度是变化的,所以我们不可能不检查到该点之间的所有文件内容就能访问某一行,除非 你的所有行数都已知是特定的长度,或者你已经做了一个把行数转换成字节数的索引。(同样的 限制也适用于有着变长字符编码的字符位置:操作系统不知道什么是字符,它们只知道字节。)

FILEHANDLE 可以是一个表达式,其值给出实际的文件句柄的名字或者是一个指向任何类似文件 句柄对象的引用。该函数成功时返回真,失败时返回假。为了方便,该函数可以从各种文件位置 计算偏移量。WHENCE 的值声明你的 OFFSET 使用文件的哪个偏移量做它的开始位置:0,文件 开头;1 文件的当前位置;2,文件结尾。如果 WHENCE 的值是 1 或 2。那么 OFFSET 可以为 负值。如果你喜欢用 WHENCE 的符号值,你可以用来自 IO::Seekable 或者 POSIX 模块的 SEEK_SET,SEEK_CUR,和 SEEK_END,或者在 Perl 5.6 里的 Fcntl 模块。

如果你想为 sysread 或者 syswrite 定位文件,那么不要使用 seek;标准 I/O 缓冲技术会令 seek 对文件在系统位置上的作用变得不可预料而且也不能移植。应该用 sysseek。

因为 ANSI C 的规则和严格,在一些系统上,如果你在在读取和写出之间做切换,那么你必须做 一次搜寻。这样做的效果就好象调用标准 I/O 库的 clearerr(3) 函数。你可以用 WHENCE 为 1 (SEEK_CUR)和 OFFSET 为 0 实现这个目的而又不会移动文件位置:

   seek(TEST, 0, 1);

这个函数的一个有趣的用途是允许你跟随文件的增长,比如:

   for (;;) {
      while () {
         grok($_);         # 处理当前行
      }
      sleep 15;
      seek LOG, 0, 1;         # 重置 end-of-file 错误。
   }

最后一个 seek 在不移动指针的情况下清理文件结束错误。因你的 C 库的标准 I/O 实现的标准 程度的不同而异,你可能需要一些更象下面这样的东西:

   for (;;) {
      for ($curpos = tell FILE;   ;   $curpos = tell FILE) {
         grok($_);         # 处理当前行
      }
      sleep $for_a_while;
      seek FILE, $curpos, 0;      # 重置 end-of-file 错误。
   }

类似的策略可以用于在一个数组里记住 seek 每一行的地址。

29.2.139 seekdir

这个函数为下一次 readdir 对 DIRHANDLE 的调用设置当前位置。POS 必须是 telldir 返回的 值。这个函数与对应的系统库过程在可能的目录压缩问题上有相同的注意事项。该函数可能不是在 所有实现了 readdir 的系统上都实现了。而且如果 readdir 没有实现,它也肯定没实现。

29.2.140 select (输出文件句柄)

由于历史原因,Perl 里有完全互不相关的两个 select 操作符。参阅下一节获取另外一个的描述。 这个版本的 select 操作符返回当前选定的输出操作符,并且,如果你提供了 FILEHANDLE,那么 把它设置为当前缺省输出操作符。这样做有两个效果:首先,一个没有文件句柄的 write 或者 print 将缺省输出到这个 FILEHANDLE。其次,与输出相关的特殊变量将指向这个文件句柄。 比如,如果你想为多个输出文件句柄设置了相同的页顶格式,那么你可能需要做这些:

   select REPORT1;
   $^ = 'MyTop';
   select REPROT2;
   $^ = 'MyTop';

但请注意这样就把 REPORT2 当作当前选定的文件句柄了。这种做法可以说是反社会的做法,因为 它可能会真的把一些其他过程的 print 或者 write 语句搞坏。写的好的库过程会在退出的时候把 当前选定文件句柄设置为和进入过程时相同的那个。为了支持这个,FILEHANLDE 可以是一个 表达式,该表达式的值给出实际文件句柄的名字。因此,你可以用下面的代码保存并恢复当前 选顶的文件句柄:

   my $oldfh = select STDER; $| = 1; select $oldfh;

或者使用惯用的但有点模糊的方法:

   select((select(STDERR), $| = 1)[0])

这个例子是这样运转的:制作一个由 select(STDERR) (副作用是选定了 STDERR)的返回值和 $|=1 (它总是 1)组成的列表,但同时,又作为副作用设置了现在选定的 STDERR 的自动冲刷。 该列表的第一个元素(前面那个选定的文件句柄)现在用做外层 select 的一个参数。古怪吧? 这些足够让你知道 List 比较危险了。

你还可以使用标准的 SelectSaver? 模块在退出范围的时候自动恢复前面一个 select。

不过,虽然我们给你解释了上面的全部东西,我们还是可以指出在当今的情况下,你很少需要使用 这种形式的 select,因为你象设置的大多数特殊变量都有面向对象的封装方法帮你做这些事情。 所以,你不用直接设置 $|,而是:

   use IO::Handle;      # 糟糕的是,这可不是个*小*模块。
   STDOUT->autoflush(1);

而前面的格式化例子可以这样编码:

   use IO::Handle;
   REPORT1->format_top_name("MyTop");
   REPORT2->format_top_name("MyTop");

29.2.141 select (准备文件描述符)

四个参数的 select 和前面那个 select 完全无关。这个操作符用于发现你的文件描述符中那个 (如果有的话)已经准备好做输入或者输出了,或者报告一个例外条件。(这样就避免你做轮询。 )它用你声明的位掩码调用 select(2) 系统调用,你可以用 fileno 和 vec 构造这个位掩码, 象这样:

   $rin = $win = $ein = "";
   vec($rin, fileno(STDIN), 1) = 1;
   vec($win, fileno(STDIN), 1) = 1;
   $ein = $rin | $win;

如果你想在许多文件句柄上 select,你可能会写这样的子过程:

   sub fhbits {
      my @fhlist = @_;
      my $bits;
      for (@fhlist) {
         vec($bits, fileno($_), 1) = 1;
      }
      return $bits;
   }
   $rin = fhbits(qw(STDIN TTY MYSOCK));

如果你想重复使用同样的位掩码(这样做更高效),常用的惯用法是:

   ($nfound, $timeleft) = 
      select($rout=$rin, $wout=$win, $eout=$ein, $timeout);

或者阻塞住直到任意文件描述符准备好:

   $nfound = select($rout=$rin, $wout=$win, $eout=$ein, undef);

如你所见,在标量环境中调用 select 只返回 $nfound,找到的准备好的文件描述符数量。

可以用 $wout=$win 技巧是因为一个赋值语句的数值是它自身的左边,因此 $wout 先被赋值删除, 然后又被 select 删除,而 $win 不会变化。

这些参数任何一个都可以是 undef,这个时候它们被忽略。如果 TIMEOUT 不是 undef,那么就是 以秒计,而且可以是分数。(超时时间为 0 的效果就是轮询。)不是所有实现都能返回 $timeleft。如果不能返回 $timeleft,那么它总是返回等于你提供的 $timeout 的 $timeleft。

标准的 IO::Select 模块提供了 select 的更加友善的接口,主要是因为它为你做了所有你能用的 位掩码。

select 的一个应用就是实现比 sleep 分辨率更好的睡眠。要实现这个目的,声明所有位映射为 undef。因此,如果要睡眠(至少)4.75 秒钟,用:

   select undef, undef, undef, 4.75;

(在一些非 Unix 系统上,三个 undef 的形式可能不能用,你可能需要为一个有效的描述符至少 伪装一个位掩码,而那个描述符可以是从来准备不好的。)

我们不应该把缓冲的 I/O(比如 read 或 )和 select 混在一起用,除了 POSIX 允许 的以外,而且就算 POSIX 允许,也只能在真正的 POSIX 系统上使用。这时应该用 sysread。

29.2.142 semctl

这个函数调用 System V IPC 函数 semctl(2)。你可能得先说 use IPC::SysV 以获取正确的 常量定义。如果 CMD 是 IPC_STAT 或者 GETALL,那么 ARG 必须是一个它可以保存返回的 semid_ds 结构或信号灯数值数组的变量。和 ioctl 和 fcntl 一样,返回值用 undef 代表 错误,“0 but true”代表零,其他情况则返回实际数值。

又见 IPC::Semaphore 模块。这个函数只有在那些支持 System V IPC 的机器上才能用。

29.2.143 semget

这个函数调用 System V IPC 系统调用 semget(2)。在调用之前,你应该 use IPC::SysV 以 获取正确的常量定义。该函数返回信号灯 ID,或者如果有错误返回 undef。

又见 IPC::Semaphore 模块。这个函数只能在那些支持 System V IPC 的机器上用。

29.2.144 semop

这个函数调用 System V IPC 系统调用 semop(2) 以执行信号灯操作,比如发信号和等待等等。 在调用之前,你应该使用 use IPC::SysV 以获取正确的常量定义。

OPSTRING 必须是一个 semop 结构的打包的数组。你可以通过说 pack("s*", $semnum, $semop, $semflag)做每一个 semop 结构。信号灯操作的数量是由 OPSTRING 的长度隐含的。该函数在 成功的时候返回真,在失败的时候返回假。

下面的代码在等待信号灯 id 为 $semid 的信号灯 $semnum:

   $semop = pack "s*", $semnum, -1, 0;
   semop $semid, $semop or die "Semaphore trouble: $!\n";

要给信号灯发出信号,只需要把 -1 换成 1 就可以了。

参阅第十六章的“System V IPC”一节。又见 IPC::Semaphore 模块。这个函数只有支持 Systerm V IPC 的机器上可以用。

29.2.145 send

这个函数在套接字上发送一条信息。它和同名系统调用接收相同的标志——参阅 send(2)。在 未联接的套接字上,你必须声明一个要发送的目的 TO,这样就会令 Perl 的 send 象 sendto(2) 那样运行。C 的系统调用 sendmsg(2) 目前没有在标准的 Perl 里实现。send 函数 在成功时返回发送的字节数,失败时返回 undef。

(有些非 Unix 系统错误地把套接字当作与普通文件描述符不同的东西对待,结果就是你必须总是 在套接字上 send 和 recv,而不能使用方便的标准 I/O 操作符。)

我们中至少有一个人会常犯的错误就是把 Perl 的 send 和 C 的 send 和写混淆起来:

   send SOCK, $buffer, length $buffer      # 错

这行代码会莫名其妙地失败,具体情况取决于字串长度和系统需要的 FLAG 位之间的关系。参阅 第十六章中的“消息传递”一节。

29.2.146. setpgrp

这个函数为指定的 PID(对当前进程使用 PID 等于 0)设置当前进程组(PGRP)。如果在那些 没有实现 setpgrp(2) 的系统上调用 setpgrp 将会抛出一个例外。注意:有些系统上会总是忽略 你提供的参数并总是做 setpgrp(0, $$)。幸运的是,这些就是我们最常用的参数。如果省略了 参数,它们缺省是 0, 0。BSD 4.2 版本的 setpgrp 不接受任何参数,但在 BSD 4.4 里,它是 setpgid 函数的同义词。如果需要更好的移植性(从某种角度来看),直接使用 POSIX 模块里的 setpgid 函数。如果你实际上想干的事是把你的脚本作成守护进程,那么请考虑使用 POSIX::setsid() 函数。请注意 POSIX 版本的 getpgrp 并不接受参数,所以只有 setpgrp(0, 0) 是真正可以移植的。

29.2.147. setpriority

这个函数为 WHICH 和 WHO 里声明的一个进程,进程组,或者一个用户设置当前 PRIORITY,参阅 setpriority(2)。在那些没有实现 setpriority(2) 的机器上调用 setpriority 将抛出一个 例外。要把你的程序“nice”下四个单位(和用 nice(1) 处理你的程序一样),用:

   setpriority 0, 0, getpriority(0, 0) + 4;

一个给定的优先级的解释可能会因不同的系统而异。有些权限可能是那些非特权用户所不能使用的。

又见 CPAN 的 BSD::Resource 模块。

29.2.148. setsockopt

这个函数设置你需要的套接字选项。出错时该函数返回 undef。LEVEL 表示你的调用瞄准的是 哪一个协议层。或者就是 SOL_SOCKET,指向在所有层之上的套接字本身。如果你不想传递参数, 那么可以把 OPTVAL 声明为 undef。在套接字上一个常用的选项是 SO_REUSEADDR,这样才能绕开 因为前一个在该端口的 TCP 联接仍然认为固执地认为它在关闭的时候,我们不能绑定特定的地址的 问题。它看起来象这样:

   use Socket;
   socket(SOCK, ...) or die "Can't make socket: $!\n";
   setsocket(SOCK, SOL_SOCKET, SO_REUSEADDR, 1)
      or warn "Can't do setdosockotp: $!\n";

参阅 setsockopt(2) 获取其他可能数值。

29.2.149 shift

这个函数把数组的第一个值移出并且返回它,然后把数组长度减一并且把所有的东西都顺移。 如果在数组中不再存在元素,它返回 undef。

如果省略了 ARRAY,那么该函数在子过程和格式的词法范围里移动 @_;它在文件范围(通常是主程 序)或者在由 eval STRING,BEGIN { },CHECK { },INIT { },和 END {} 这样的构造里面的 词法范围里移动 @ARGV。

子过程通常以拷贝它们的参数到词法变量里开始,而 shift 可以用于这个目的:

   sub marine {
      my $fathoms = shift;      # 深度
      my $fishies   = shift;   # 鱼的数量
      my $o2           = shift;   # 氧气问题
      # ...
   }

shift 还可以用于在你的程序前面处理参数:

while (defined($_ = shift)) {
        /^[^-]/     && do { unshift @ARGV, $_; last };
        /^-w/       && do { $WARN = 1;         next };
        /^-r/       && do { $RECURSE = 1;      next };
        die "Unknown argument $_\n";
}

你还可以考虑使用 Getopt::Std 和 Getopt::Long 模块来处理程序参数。

又见 unshift,push,pop,和 splice。shift 和 unshift 函数在数组左边做的事情和 pop 和 push 在数组右边干的事情是一样的。

29.2.150 shmctl

这个函数调用 System V IPC 系统调用 shmctl(2)。在调用之前,你应该 use IPC::SysV 以 获取正确的常量定义。

如果 CMD 是 IPC_STAT,那么 ARG 必须是一个将要保存返回的 shmid_ds 结构的变量。跟 ioctl 和 fcntl 一样,该函数错误时返回 undef,“0 but true”表示零,其他情况下返回 实际返回值。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.151 shmget

这个函数调用 System V IPC 系统调用 shmget(2)。该函数返回共享内存段的 ID,如果有错误 则返回 undef。在调用之前,先 use SysV?::IPC。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.152 shmread

这个函数从共享内存段 ID 的位置 POS 处开始读取 SIZE 大小的数据(方法是附着在该内存段 上,拷贝出数据,然后与该内存段分离。)。VAR 必须是一个将保存读取出的数据的变量。如果 成功,该函数返回真,如果失败返回假。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.153 shmwrite

这个函数向共享内存段 ID 的位置 POS 处开始写入 SIZE 大小的数据(方法是附着在该内存段 上,拷贝入数据,然后与该内存段分离。)。如果 STRING 太长,那么只写入 SIZE 字节;如果 STRING 太短,那么在后面补空直到 SIZE 字节。如果成功,该函数返回真,如果有错误,返回假。

该函数只能在那些支持 System V IPC 的机器上用。(你可能都读烦了——我们已经写烦了。)

29.2.154 shutdown

这个函数以 HOW 声明的方式关闭一个套接字联接。如果 HOW 为 0,那么不再允许进一步的接收。 如果 HOW 为 1,那么不再允许进一步的发送。如果 HOW 为 2,那么任何事情都不允许。

   
   shutdown(SOCK, 0);   # 不许再读
   shutdown(SOCK, 1);   # 不许再写
   shutdown(SOCK, 2);   # 不许再 I/O

如果你想告诉对端你完成数据写出了,但还没有完成数据读取,或者反过来,在这些情况下它都 非常有用。而且它还是一种更执着的关闭方式,因为同时还关闭任何这些文件描述符在派生出的 进程中的的拷贝。

让我们想象有一个服务器想读取它的客户端的请求,直到文件结尾,然后发送一个回答。如果 客户端调用 close,那么该套接字现在将不能用于 I/O,因此不会有回答能送回来。因此,客户端 应该使用 shutdown 以半关闭这次联接:

   print SERVER "my request\n";   # 发送一些数据
   shutdown(SERVER, 1);         # 发送完毕,没有更多要发的东西了
   $answer = ;         # 但你还可以读

(如果你找到这里是为了找到关闭你的系统的办法,那么你就要执行一个外部的程序干这件事。 参阅 system。)

29.2.155. sin

抱歉,这个操作符什么罪都没犯(译注:英文“sin”也有“罪恶”的含义)。它只是返回 EXPR (用弧度表示)的正弦。

如果需要正弦的逆操作,你可以使用 Math::Trig 或者 POSIX 模块的 asin 函数,或者用下面的 关系:

   sub asin { atan2($_[0], sqrt(1 - $_[0] * $_[0])) }

29.2.156 sleep

这个函数令脚本睡眠 EXPR 秒,如果没有 EXPR 则是永久睡眠,并且返回睡眠的秒数。 你可以 通过给该进程发送一个 SIGALRM 的方法来中断睡眠。在一些老式系统里,它可能比你要求的描述 整整少睡一秒,具体情况取决于它是如何计算秒的。大多数现代的系统都是睡足秒数。不过,在 这些系统上它们很有可能睡眠的时间要长一些,因为在一台繁忙的多任务系统上,你的系统可能 无法马上得到调度。如果可能,select (等待文件描述符)调用可以给你更好的分辨率。你还 可以用 syscall 调用一些 Unix 系统支持的 getitimer(2) 和 setitimer(2) 过程。你不应该 混合 alarm 和 sleep 调用,因为 sleep 通常是用 alarm 实现的。

又见 POSIXE 模块的 sigpause 函数。

29.2.157 socket

这个函数打开一个指定类型的套接字,并且把它附着在 SOCKET 文件句柄上。DOMAIN,TYPE,和 PROTOCOL 都是和 socket(2) 一样的声明。如果没有定义 SOCKET,那么 Perl 将自动激活它。 在使用这个函数之前,你的程序应该包含下面这行:

   use Socket;

它给你正确的常量。该函数成功时返回真,参阅在第十六章里的“套接字”节里的例子。

在那些支持对文件的 exec 时关闭(close-on-exec)的系统上,该标记将为新打开的文件描述符 设置,就象 $^F 判定的那样。参阅第二十八章里的 $^F($SYSTEM_FD_MAX)。

29.2.158 socketpair

这个函数在声明的域中创建一个指定类型的匿名套接字对。DOMAIN,TYPE,和 PROTOCOL 都和 socketpair(2) 里声明的一样。如果两个套接字参数都没有声明,那么它们自动激活。该函数成功 时返回真,失败时返回假。在那些没有实现 socketpair(2) 的系统上,调用这个函数会抛出一个 例外。

这个函数的通常用法是在 fork 之前使用。生成的进程中有一个关闭 SOCKET1,而另外一个关闭 SOCKET2。你可以双向使用这些套接字,而不象 pipe 函数创建的文件句柄那样是单向的。有些 系统用 socketpair 的方式定义 pipe,这时候调用 pipe(Rdr, Wtr) 相当于:

   use Socket;
   socketpair(Rdr, Wtr, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
   shutdown(Rdr, 1);      # 不允许读者写
   shutdown(Wtr, 0);      # 不允许写者读

在那些支持对文件的 exec 时关闭(close-on-exec)的系统上,该标记将为新打开的文件描述符 设置,就象 $^F 判定的那样。参阅第二十八章里的 $^F($SYSTEM_FD_MAX)。又见在第十六章里 的“双向通讯”一节尾部的例子。

29.2.159 sort

这个函数对 LIST 进行排序并返回排好序的列表值。缺省时,它以标准字串比较顺序排序(未定义 数值排在已定义空字串前面,而空字串又在其他任何东西前面)。如果 use locale 用法起作用, 那么 sort LIST 根据当前的区域集的数值对 LIST 排序。

如果给出了 USERSUB,那么它就是一个返回小于,等于,或者大于 0 的整数的子过程名字,具体 返回什么取决于列表中的元素应该如何排序。(很便利的 <=> 和 cmp 操作符可以用于执行三向 数字和字串比较。)如果给出了 USERSUB,但该函数未定义,那么 sort 抛出一个例外。

为了提高效率,绕开了通常用于子过程的调用代码,这样就有了下面的结果:这个子过程不能是 递归子过程(你也不能用一个循环控制操作符退出该块或者过程),并且将要接受比较的两个元素 不是通过 @_ 传递进子过程的,而是通过临时设置 sort 编译所在的包的全局变量 $a 和 $b( 参阅后面的例子)。变量 $a 和 $b 是真实值的别名,所以不要在子过程中修改它们。

子过程要求的动作是比较。如果它返回的结果是不一致的(比如,有时候说 $x[1] 小于 $x[2], 而有时候说的正相反),那么结果就不会良好。(这也是你不能修改 $a 和 $b 的另外一个原因。)

USERSUB 可以是标量变量名字(未代换),这时,它的值要么是引用实际子过程的符号引用,要么 是硬引用。(符号名更好些,即使用了 use strict 'refs' 用法也如此。)在 USERSUB 的 位置,你可以提供一个 BLOCK 用做一个匿名内联排序子过程。

要做一次普通的数字排序,你可以说:

   sub numerically { $a <=> $b }
   @sortedbynumber = sort numerically 53, 29,11,32, 7;

要以降序排序,你可以简单地在 sort 后面应用 reverse,或者你可以在排序过程里把 $a 和 $b 反过来:

sub numerically { $a <=> $b }
@sortedbynumber = sort numerically 53,29,11,32,7;

   @descending = reverse sort numerically 53,29,11,32,7;

   sub reverse_numerically { $b <=> $a }
   @descending = sort reverse_numerically 53,29,11,32,7;

要对字串进行大小写不敏感的排序,在比较之前用 lc 处理 $a 和 $b:

    @unsorted = qw/sparrow Ostrich LARK catbird blueJAY/;
    @sorted = sort { lc($a) cmp lc($b) } @unsorted;

(在 Unicode 里,用 lc 做大小写规范化要比用 uc 好,因为有些语言里抬头体和大写是不一样 的。不过它对普通的 ASCII 排序没有什么影响,并且如果你想让 Unicode 能正确排序,那么你的 规范化过程可能要比 lc 更别致一些。)

对散列按照数值排序是 sort 函数的常用法之一。比如,如果 %sales_amount 散列记录部门销售 情况,那么在排序过程里做一次散列查找就可以让我们将散列键字根据它们的数值排序:

# 从销售额最高的部门到最低的部门

    sub bysales { $sales_amount{$b} <=> $sales_amount{$a} }

    for $dept (sort bysales keys %sale_amount) {
        print "$dept => $sales_amount{$dept}\n";
    }

你可以通过使用 || 或者 or 操作符级连多个比较的方法进行额外层次的排序。这种方法相当漂亮, 因为比较操作符通常在相等的时候返回 0,这样就令它们能落到下一个比较。下面,散列键字首先 根据它们相关的销售额排序,然后在根据键字本身进行排序(以处理有两个或多个部门销售额 相同的情况):

   sub by_sales_then_dept {
      $sales_amount{$b} <=> $sales_amount{$a}
         ||
      $a cmp $b
   }

   for $dept (sort by_sales_then_dept keys %sale_amount) {
      print "$dept => $sales_smount{$dept}\n";
   }

假设 @recs 是一个散列引用的数组,而这里每个散列包含象 FIRSTNAME,LASTNAME,AGE, HEIGHT,和 SALARY 这样的域。下面的过程把那些记录中的人们按照下面的顺序排列:先是财富, 然后是身高,然后是年龄(越小越靠前),最后是名字的字母顺序:

sub prospects {
    $b->{SALARY}    <=>  $a->{SALARY}
        ||
    $b->{HEIGHT}    <=>  $a->{HEIGHT}
        ||
    $a->{AGE}       <=>  $b->{AGE}
        ||
    $a->{LASTNAME}  cmp  $b->{LASTNAME}
        ||
    $a->{FIRSTNAME} cmp  $b->{FIRSTNAME}
}

@sorted = sort prospects @recs;

任何可以从 $a 和 $b 中得到的有用信息都可以在一个排序过程中比较的基础来用。比如,如果 多行文本要根据特定域来排序,那么可以在排序过程中使用 split 以获取该域:

   @sorted_lines = sort {
      @a_fields = split /:/, $a;      # 冒号分隔的域
      @b_fields = split /:/, $b;
         
      $a_fields[3] <=> $b_fields[3]   # 对第四个域进行能够数字排序,然后
         ||
      $a_fields[0] cmp $b_fields[0]   # 对第一个域进行能够字串排序,然后
         ||
      $b_fields[2] <=> $a_fields[2]   # 对第而个域进行行能够数字反向排序
      ...            # 等等
   } @lines;

不过,因为 sort 使用给 $a 和 $b 的不同的数值对多次运行排序过程,所以前面的例子将会比对 每一行都做多余的重新分裂。

为了避免发生象为了比较数据域导致的多次的行分裂带来的开销,我们可以在排序之前对每个值进行 一次操作,然后把生成的信息保存起来。下面,我们创建了一个匿名数组以捕获每一行以及该行的 分裂结果:

   @temp = map { [$_, split /:/] } @lines;

然后,我们对数组引用排序:

   @temp = sort {
      @a_fields = @$a[1..$#$a];
      @b_fields = @$b[1..$#$b];

      $a_fields[3] <=> $b_fields[3]   # 对第四个域进行能够数字排序,然后
         ||
      $a_fields[0] cmp $b_fields[0]   # 对第一个域进行能够字串排序,然后
         ||
      $b_fields[2] <=> $a_fields[2]   # 对第而个域进行行能够数字反向排序
      ...               # 等等
   } @temp;

在这个数组引用排完序之后,我们就可以从这个匿名数组里检索原始行了:

   @sorted_lines = map {$_->[0] } @temp;

概而括之,这个 map-sort-map 技巧,就是我们通常称之为 Schwartzian 变换的东西,可以用 一个语句实现:

   @sorted_lines = map { $_->[0] }
         sort {
            @a_fields = @$a[1..$#$a];
            @b_fields = @$b[1..$#$b];

            $a_fields[3] <=> $b_fields[3]
               ||
            $a_fields[0] <=> $b_fields[0]
               ||
            $b_fields[2] <=> $b_fields[2]
            ...
         }
         map { [$_, split /:/]} @lines;

不要把 $a 和 $b 定义成词法变量(用 my)。它们都是包全局变量(如果它们可以免于 use strict 对普通全局变量的限制)。不过你的确需要保证你的排序过程是在同一个包里的, 或者用调用者的包名字修饰 $a 和 $b。

我们已经说过,在 Perl 5.6 里你可以用标准的参数传递方法(以及不一样的是,用 XS 子过程做 排序子过程)写排序子过程,前提是你用一个 ($$) 的原型声明了这个排序子过程。并且如果你是 这么用的,那么实际上你是可以把 $a 和 $b 声明为词法变量的:

   sub numerically ($$) {
      my ($a, $b) = @_;
      $a <=> $b;
   }

将来,当完整的原型都实现了以后,你就可以只用说:

      
   sub numerically ($a, $b) { $a <=> $b}

然后我们或多或少就能回到开始的地方。

29.2.160 splice

这个函数从一个 ARRAY 中删除 OFFSET 和 LENGTH 指明的元素,并且,如果给出了LIST,则用 LIST 的元素替换它。如果 OFFSET 是负数,那么该函数从数组的后面向前数,但如果该值会伸到 数组开头的前面,那么就会抛出一个例外。在列表环境中,splice 返回从该数组中删除的元素。 在标量环境中,它返回最后删除的元素,而如果没有的话返回 undef。如果新元素的数量不等于 旧元素的数量,那么该数组根据需要伸缩,并且元素的位置根据衔接后的情况进行改变。如果省略 了 LENGTH,那么该函数从数组里删除从 OFFSET 开始的所有东西。如果省略了 OFFSET,那么该 数组在读取的时候清空。下面的等式成立(假设 $[ 为 0):

直接方法 splice 等效
push(@a, $x, $y) splice(@a, @a, 0, $x, $y)
pop(@a) splice(@a, -1)
shift(@a) splice(@a, 0, 1)
unshift(@a, $x, $y) splice(@a, 0, 0, $x, $y)
$a[$x] = $y splice(@a, $x, 1, $y)
(@a, @a = ()) splice(@a)

splice 函数还可以方便地用于切开传递给子过程的参数列表。比如,假设列表长度在列表之前传递:

   sub list_eq {            # 比较两个列表值
      my @a = splice(@_, 0, shift);
      my @b = splice(@_, 0, shift);
      return 0 unless @a == @b;   #  长度相同?
      while(@a) {
         return 0 if pop(@a) ne pop(@b);
      }
      return 1;
   }
   if (list_eq($len, @foo[1..$len], scalar(@bar), @bar)) { ... }

不过,拿数组引用来干这事更清晰一些。

29.2.161 spit

这个函数扫描字串中 EXPR 给出的分隔符,并且把该字串劈成一个子字串列表,在列表环境中返回 生成的列表值,或者在标量环境中返回子字串的数量。(注:标量环境同时还令 split 把它的 结果写到 @_,不过这个用法现在废弃了。)分隔符是用重复的模式匹配进行判断的,用的是 PATTERN 里给出的正则表达式,因此分隔符可以是任意大小,并且不一定在每次匹配都是一样的 字串。(分隔符不象平常那样返回;我们在本节稍后讨论例外情况。)如果 PATTERN 完全不能匹配 该字串,那么 split 把原始字串当作子字串返回。如果它匹配了一次,那么你就得到两个子字串, 以此类推。你可以在 PATTERN 里使用正则表达式修饰词,比如 /PATTERN/i,/PATTERN/x,等等。 如果你以模式 /^/ 进行分裂,那么就假设是 //m 修饰词。

如果声明了 LIMIT 并且是正的,该函数分裂成不超过那么多的域(当然如果它用光了分隔符,那么 是可以分裂成比较少的子字串的)。如果 LIMIT 是负数,那就把它当作声明了任意大的 LIMIT。 如果省略了 LIMIT 或者是零,那么将从结果中删除结尾的空域(那些潜在的 pop 用户应该好好 记住)。如果省略了 EXPR,那么该函数就分裂 $_ 字串。如果还省略了 PATTERN 或者它是一个 文本空格,“ ”,那么该函数对空格进行操作,/\s+/,但是忽任何开头的空格。

可以分裂任意长度的字串:

   @chars  = split //,    $word;
   @fields  = split /:/,   $line;
   @words = split " ",   $paragraph;
   @lines = split /^/,   $buffer;

一个可以匹配空串或者其他的一些比空串长的字串的模式(比如,一个由任意一个字符加上 * 或者 ? 修饰的模式)将把 EXPR 的值分裂成独立的字符,只要它匹配字符之间的空串;非空匹配会象 通常的情况那样忽略匹配过的分隔符字符。(换句话来说,一个模式不会在一个点匹配多过一次, 即使它和一个零宽匹配也如此。)比如:

   print join ':', split / */, 'hi there';

生成输出“h:i:t:h:e:r:e”。空白消失了是因为它作为分隔符一部分匹配。举一个小例子,空模式 // 简单 地分裂成独立的字符,而空格并不消失。(对于正常模式匹配而言,// 模式会在上一次成功匹配处重复, 但是 split 的模式免受此过。)

LIMIT 参数只分裂字串的一部分:

   ($login, $passwd, $remainder) = split /:/, $_, 3;

我们鼓励你把你的字串分裂成这样的列表名字,这样你的代码就有了自文档的特性。(可以用于 出错检查,请注意如果字串里比三个域少,那么 $remainder 将会是未定义。)当给一个列表赋值 的时候,如果省略了 LIMIT,那么 Perl 提供一个 LIMIT,其数值比列表中的变量数量大一,以此 避免不必要的工作。对于上面的分裂,LIMIT 缺省时是 4,而 $remainder 将只收到第三个域,而 不是所有剩下的域。在时间要求很严格的应用里,避免分裂成比我们需要的更多的域是一个好习惯。 (强大的语言的问题就是,它给你强大的功能的是以花费在时间上的愚蠢为代价的。)

我们早先说过分隔符不会被返回,但是如果 PATTERN 包含圆括弧,那么每一对圆括弧匹配的子字串 都会包括在结果列表中,分散在那些平常返回的域之中。下面是一个简单的例子:

   split /([-,])/, "1-10,20";

生成列表:

   (1, '-', 10, ',', 20)

如果有更多圆括弧,那么为每个圆括弧对返回一个域,即使有些圆括弧对没有匹配也如此,这种情况 下,为那些位置返回未定义数值。因此,如果你说:

   split /(-)|(,)/, "1-10,20";

那么结果是:

   (1, '-', undef, 10, undef, 20);

/PATTERN 参数的位置可以放这么一个表达式,该声明在运行时生成不同的模式。和普通模式一样, 如果想只做一次运行时编译,那么用 /$varable/o。

有一个特殊的情况,如果该表达式是一个空格(“ ”),那么该函数会象没有参数的 split 那样 在空格上把字串分裂开。因此 split(" ") 可以用于模拟 awk 的缺省行为。相反,split(/ /) 将给你和前导空格一样多的空的初始化域。(除了这个特殊的例子以外,如果你提供的是一个字串 而不是一个正则表达式,那么它还是会被解释成一个正则表达式。)你可以用这个属性把开头和 结尾的空白删除,并且把中间的空白都压缩成一个空白:

   $string = join(' ', split(' ', $string));

下面的例子把一个 RFC 822 消息头分裂成一个包含 $head{Date},$head{Subject},等等的 散列。它使用了给一个散列赋予一个配对列表的技巧,理由是域和分隔符交错。它利用圆括弧把 每个分隔符的一部分当作返回列表值的一部分返回。因为 split 模式保证把返回的东西利用包含 圆括弧的好处按照配对的形式返回,所以散列赋值就可以保证收到一个包含键字/数值对的列表, 这里每个键字就是一个头域的名字。(糟糕的是,这个技巧会丢失有着相同域的多个行的信息, 比如 Received-By 行。啊,哦...)

   $header =~ s/\n\s+/ /g;      # 融合连续行
   %head = ('FRONTSTUFF', split /^(\S*?):\s*/m, $header);

下面的例子处理整个 Unix passwd(5) 文件。你可以忽略 chomp,这个时候 $shell 的结尾将有 换行符。

   open PASSWD, '/etc/passwd';
   while () {
      chomp;      # 删除结尾的换行符
      ($login, $passwd, $uid, $gid, $gcos, $home, $shell) = 
         split /:/;
      ...
   }

下面是一个如何处理每个输入文件里的每一行中的每个词,创建一个单词频率散列的例子:

   while (<>) {
      foreach $word (split) {
         $count{$word}++;
      }
   }

split 的逆操作由 join 执行(只不过 join 只能在所有域之间用同样的分隔符连接)。要用固定 位置的域分解字串,请使用 unpack。

29.2.162. sprintf

这个函数返回一个格式化字串,格式化习惯是 C 的库函数 sprintf 的是 printf 习惯。参阅你的 系统的 sprintf(3) 或 printf (3) 获取一些通用原则的解释。FORMAT 包含一个带有嵌入的域 指示符的文本,LIST 里的元素就是逐一替换到这些域中去的。

Perl 做自己的 sprintf 格式化——它模拟 C 函数 sprintf,但是它没有用 C 的 sprintf。 (注:除了浮点数以外,并且就算是浮点数也只允许标准的修饰词。)结果是,任何你本地的 sprintf(3) 函数的扩展都不能在 Perl 里使用。

Perl 的 sprintf 允许全局使用的已知转化在 表29-4 中列出。

表29-4。sprintf 的格式

含义
%% 一个百分号
%c 一个带有给定数字的字符
%s 一个字串
%d 一个有符号整数,十进制
%u 一个无符号整数,十进制
%o 一个无符号整数,八进制
%x 一个无符号整数,十六进制
%e 一个浮点数,科学记数法表示
%f 一个浮点数,用固定的小数点表示
%g 一个浮点数,以 %e 或 %f 表示

另外,Perl 允许下列广泛支持的转换:

含义
%x 类似 %x,但使用大写字符
%E 类似 %e,但使用大写的“E”
%G 类似 %g,但是带一个大写的“E”(如果正确的话)
%b 一个无符号整数,二进制
%p 一个指针(输出十六进制的 Perl 值的地址)
%n 特殊:把到目前为止输出的字符数放到参数列表中的下一个变量里

最后,为了向下兼容(我们的意思就是“向下”),Perl 允许下列不必要的但广泛支持的转换:

含义
%i %d 的同义词
%D %ld 的同义词
%U %lu 的同义词
%O %lo 的同义词
%F %f 的同义词

Perl 允许下列众所周知的标志出现在 % 和转换字符之间:

域 | 含义|

space 用空格前缀正数
+ 用加号前缀正数
- 在域内左对齐
- 用零而不是空格进行右对齐
# 给非零八进制前缀“0”,给非零十六进制前缀“0x”
number 最小域宽度
.number “精度”:浮点数的小数点后面的位数字串最大长度。整数最小长度
l | | 把整数解释成 C 类型的 long 或者 unsigned long|
h 把整数解释成 C 类型的 short 或者 unsigned short(如果没有提供标志,那么把整数解释成 C 类型 int 或者 unsigned)

还有两个 Perl 相关的标志

含义
V 把整数解释成 Perl 标准的整数类型
v 把字串解释成一个整数向量,输出成用点分隔的数字,或者是用任意参数列表里前面带 * 的字串分隔

如果你的 Perl 理解“四倍数”(64位整数),不管是该平台本机支持还是因为你指明 Perl 带着 该功能编译,那么字符 d u o x X b i D U O 打印64位整数,并且它们前面可以选择前缀 ll, L,或则 q。比如,%lld %16LX %qo。

如果 Perl 理解“long double”(要求该平台支持 long double),那么你可以在 e f g E F G 标志前面增加可选的 ll 或者 L。比如,%llf %Lg。

在标志里可以出现数字的位置,都可以用一个星号(“*”)代替,这时候 Perl 使用参数列表里的 下一个项作为给出的数字(也就是说,当作域宽度或者精度)。如果通过“*”获取的域宽度是负数, 那么它和“-”标志有一样的效果:左对齐。

v 标志可以用于显示任意字串里的序数值:

   sprintf "version is v%vd\n", $^V;      # Perl 的版本
   sprintf "address is %vd\n", %addr;   # IPv4 地址
   sprintf "address is %*vX\n", ":", $addr;   # IPv6 地址
   sprintf "bits are %*vb\n", " ", $bits;   # 随机的位串

29.2.163 sqrt

这个函数返回 EXPR 的平方根。如果需要其他的根,比如立方根,你可以使用 ** 操作符求那个 数字的分数幂。不要试图在着两种方法里使用负数,因为它有一些稍微有些复杂的问题(并且抛出 一个例外)。但是有一个模块可以处理这些事情:

   use Main::Complex;
   print sqrt(-2);      # 打印出 1.4142135623731i

29.2.164. srand

这个函数为 rand 操作符设置随机数种子。如果省略了 EXPR,那么它使用一个内核提供的半随机的 数值(如果内核支持 /dev/urandom 设备)或者是一个基于当前时间和进程号以及一些其他东西的 数值。通常我们完全没有必要调用 srand,因为如果你没有明确调用它,那么它也会在第一次调用 rand 操作符时隐含调用。不过,在早于 Perl 5.004 的版本里不是这样的,所以如果你的脚本 需要在老 Perl 版本上运行,那么你就应该调用 srand。

那些经常被调用的程序(比如 CGI 脚本),如果只是简单地用 time ^ $$ 做种子的话,那么很 容易惨遭下面的数学性质的攻击,那就是:有三分之一的机会 a^b == (a+1)^(b+1)。所以不要 这么干。应该用下面的代码:

   srand( time() ^ ($$ + ($$ << 15)) );

如果用于加密目的,那么你需要用比缺省的种子生成更随机的算法。有些系统上有 /dev/random 设备就比较合适,否则,拿一个或多个会迅速改变操作系统状态的程序的输出,压缩以后进行 校验和计算是常用的方法。比如:

   srand (time ^ $$ ^ unpack "%32L*", `ps wwaxl | gzip`);

如果你特别关心这些问题,那么请参阅 CPAN 上的 Math::TrulyRandom 模块。

不要在你的程序里多次调用 srand,除非你知道你在干什么并且知道为什么这么做。这个函数的 目的是给 rand 函数种子,这样 rand 就可以在你每次运行你的程序的时候生成不同的序列。只 需要在你的程序开始做一次,否则你就不能从 rand 中获得随机的数值!

29.2.165. stat

在标量环境里,这个函数返回一个布尔值,该值表示调用是否成功。在列表环境里,它返回一个 13 个元素的列表,给出一个文件的统计信息,该文件要么是通过 FILEHANDLE 打开,要么是用 EXPR 命名。它的典型应用如下:

   ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
      $atime, $mtime, $ctime, $blksize, $blocks)
         = stat $filename;

不是在所有文件系统类型上都支持这些域;不支持的域返回 0。表 29-5 列出了各个域的含义。

表 29-5。stat 返回的域


索引 含义
0 $dev 文件系统的设备号
1 $ino i 节点号码
2 $mode 文件模式(类型和权限)
3 $nlink 指向该文件的(硬)链接数量
4 $uid 文件所有者的数字用户 ID
5 $gid 文件所属组的数字组 ID
6 $rdev 设备标识符(只用于特殊文件)
7 $size 文件的总尺寸,以字节计
8 $atime 自纪元以来以秒计的上一次访问时间
9 $mtime 自纪元以来以秒计的上一次修改时间
10 $ctime 自纪元以来以秒计的上一次i节点改变的时间(不是创建时间!)
11 $blksize 选定的用于文件系统 I/O 的块尺寸
12 $blocks 实际分配的块数量

$dev 和 $ino 放在一起,在同一个系统里唯一地标识一个文件。$blksize 和 $blocks 很可能 只在 BSD 衍生出的文件系统里有。如果有 $block 域,那么它是以 512 字节的块汇报的。 $blocks*512 的值可能和 $size 差距相当大,因为有些文件包含未分配的块,或者说“洞”, 它们没有在 $blocks 中计算。

如果传递给 stat 一个特殊的文件句柄,该句柄里包含下划线,那么就不会做实际的 stat(2) 调用,而是返回上一次 stat,lstat,或者基于 stat 的文件测试操作符(比如 -r,-w,和 -x) 的 stat 结构。

因为模式包含文件类型及其权限,所以如果你想看真正的权限,那么你应该屏蔽掉文件类型部分, 并且在 printf 或者 sprintf 里用“%o”:

   $mode = (stat($filehane))[2];
   printf "Permissions are %04o\n", $mode &07777;

File::stat 模块提供一个方便的通过名字访问的机制:

   use File:;stat;
   $sb = stat($filename);
   printf "File is %s, size is %s, perm %04o, mtime %s\n",
      $filename, $sb->size, $sb->mode & 07777,
      scalar localtime $sb->mtime;

你还可以从 Fcntl 模块里输入各种不同模式位的符号定义。参阅联机文档获取更多细节。

提示:如果你只需要文件尺寸,那么可以用 -s 文件测试操作符,它直接返回以字节计的文件大小。 另外还有返回以天计的文件年龄的文件测试。

29.2.166. study

这个函数花了一些额外的时间用在研究 SCALAR 上,预测在进行下一次修改之前要做的匹配次数。 这么做也许能但也许不能节约时间,具体情况取决于你正在搜索的模式的天性和数量,以及待搜索 字串中字符出现频率的分布——你可能需要比较有它和没它的运行时间来看看哪个运行得快一些。 那些扫描许多常量字串(包括在更复杂的模式里的常量部分)的循环会从 study 中得到最大好处。 如果你的所有模式匹配都是前面有锚符号的常量字串,那么 study 一点忙都帮不上,因为那里没有 扫描。你每次只能拥有一个 study——如果你研究另外一个标量的话,那么前面那个就“没有研究” 了。

study 运转的方法是:做一个包含待搜索字串中的每个字符的链表,因此,打个比方,我们就知道了 所有的“k”字符的位置。从每个搜索字串里选出最少见的字符,这是基于从一些 C 程序和英文文本 构造出来的频率统计表的。只对那些包含这个最少见字符的位置进行检查。

比如,下面是一个循环,它在每个包含特定模式的行前面插入一个生成索引的记录:

   while(<>) {
      study;
      print ".IX foo\n"      if /\bfoo\b/;
      print ".IX bar\n"      if /\bbar\b/;
      print ".IX blurfl\n"      if /\bblurfl\b/;
      ...
      print;
   }

为了搜索 /\bfoo\b/,只查看 $_ 里的那些包含“f”的位置,因为“f”比“o”少见。除了在 病态的情况下,这样做是很有优势的。唯一的问题是它是否能节约比先制作链表花的时间更多的 时间。

如果你必须搜索那些你直到运行时才知道的字串,那么你可以把整个循环作成一个字串然后 eval 它以避免每次都要重新编译你的模式。再通过设置 $/ 把整个文件输入成一条记录,把这些组合 起来可以非常快,通常比那些专业程序,象 fgrep(1) 什么的都快。下面的程序扫描一个文件列表 (@files),搜索一个单词列表(@words),并且把那些包含大小写无关匹配的文件名字打印出来:

   $search = 'while (<>) { study;';
   foreach $word (@words) {
      $search .= "++\$seen{\$ARGV} if /\\b$word\\b/i;\n";
   }
   $search .= "}";
   @ARGV = @files;
   undef $/;      # 吃进整个文件
   eval $search;      # 这里运行程序
   die $@ if $@;      # 处理 eval 失败
   $/ = "\n";      # 恢复正常的输入终止符
   foreach $file (sort keys(%seen)) {
      print "$file\n";
   }

既然我们有 qr// 操作符,那么上面的编译完的运行时 eval 看上去没有必要。下面的做相同的 事情:

   @pats = ();
   foreach $word (@words) {
      push @pats, qr/\b${word}\b/i;
   }
   @ARGV = @files;
   undef $/;      # 吃进每个完整的文件
   while (<>) {
      for $pat (@pats) {
         $seen{$ARGV}++ if /$pat/;
      }
   }
   $/ = "\n";      # 恢复正常的输入终止符
   foreach $file (sort keys(%seen)) {
      print "$file\n";
   }

29.2.167 sub

子过程声明和定义的语法看起来挺复杂的,但是在实践上实际相当简单。所有东西都是基于下面 语法的:

   sub NAME PROTO ATTRS BLOCK

所有的四个域都是可选的;唯一的限制就是如果这些域的确存在的话那么它们必须以这些顺序出现, 并且你必须至少使用 NAME 或者 BLOCK 之一。目前,我们会忽略 PROTO 和 ATTRS;它们只是 基本语法的修饰词。NAME 和 BLOCK 都是保证正确重要部分:

在上面三种情况中的任何一种里,PROTO 和 ATTRS 之一或者全部都可以在 NAME 之后和/或 BLOCK 之前出现。原型是一个放在圆括弧里的字符列表,它告诉分析器如何对待该函数的参数。 属性是用一个冒号引入的,它告诉分析器有关这个函数的额外的信息。下面是一个包含四个域的 典型的定义:

   sub numstrcmp ($$) : locked {
      my ($a, %b) = @_;
      return $a <=> $b || $a cmp %b;
   }

有关属性列表和它们的操作的细节,请参阅第三十一章里的 attributes 用法。又见第六章和第八章的“匿名子过程”。

29.2.168. substr

这个函数从 EXPR 给出的字串中抽取一个子字串然后返回它。这个子字串是从字串前面 OFFSET 个 字符的位置开始抽取的。(注意:如果你曾经修改了 $[,那么字串的开头就不再是 0 了,不过 因为你没有修改过 $[,所以它的开头还是 0。)如果 OFFSET 是负数,那么子字串是从字串后面 数这么多偏移量位置开始的。如果省略了 LENGTH,那么把从该位置到字串结尾的东西都抽取出来。 如果 LENGTH 是负数,那么该长度是当作在字串尾部剩余那么多字符来理解的。否则,LENGTH 表示要抽取的子字串的长度,通常就是你想要的东西。

你可以把 substr 当作一个左值(可以给之赋值的东西)来用,这个时候 EXPR 也必须是一个合法 的左值。如果你给你的子字串赋予比它短的东西,那么该字串将收缩,而如果你给它赋予比它长的 东西,那么它会变长。要想保持该字串长度一致,你可能需要用 sprintf 或者 x 操作符填充或者 截断你的数值。如果你试图给一个跨过该字串尾部的未分配区域赋值,那么 substr 就会抛出一个 例外。

在 $_ 的当前值前面增加字串“Larry”,用:

   substr($var, 0, 0) = "Larry";

替换 $_ 的第一个字符为“Moe”,用:

   substr($var, 0, 1) = "Moe";

最后,把 $var 的最后一个字符换成“Curly”,用:

   substr($var, -1) = "Curly";

把 substr 当作左值使用的另外一个方面就是声明 REPLACEMENT 字串作为其第四个参数。这样就 允许你替换 EXPR 的某部分并且返回在一次操作之前的东西,就好象你用 splice 实现的功能那样。 下面一个例子也是把 $var 的最后一个字符替换成“Curly”,并且把那个被替换的字符放到 $oldstr 里:

   $oldstr = substr($var, -1, 1, "Curly");

你不一定只是在赋值语句中使用 substr 作为左值。下面的代码把任何空格替换成句点,但是只 替换字串中的最后十个字符:

   substr($var, -10) =~ s/ /./g;

29.2.169. symlink

这个函数创建一个新的文件,该文件是是指向一个旧文件的符号链接。此函数成功时返回真,否则 返回假。在那些不支持符号链接的系统上,它在运行时抛出一个例外。要想检查这个例外,你可以 用 eval 捕获所有可能的错误:

   $can_symlink = eval  { symlink("", ""); 1 };

或者使用 Config 模块。要注意的是如果你提供了一个相对符号链接,那么它会被解释成相对于 该符号链接本身的路径,而不是相对于你的当前工作目录。

又见本章早些时候的 link 和 readlink。

29.2.170 syscall

这个函数调用列表的第一个元素声明的系统调用(意思就是一次系统调用,而不是一个 shell 命令 ),同时把列表中其他元素作为参数传递给系统调用。(现在,许多这些调用可以通过 POSIX 模块 更容易地使用。)如果 syscall(2) 未实现,那么该函数抛出一个例外。

这些参数按照下面的方式解释:如果一个给定的参数是数字,那么该参数就作为一个 C 整数传递。 如果不是,那么就传递一个指向该字串值的指针。你有责任确保这个字串足够长,以便能够接收 任何可能写到它里面去的结果;否则,你就等着核心倾倒吧。你不能拿一个字串文本(或者其他 只读的字串)当作给 syscall 的参数,因为 Perl 已经假定任何字串指针都是可写的。如果你的 整数参数不是文本并且在数字环境里从不会被解释,那么你可能需要给它们加 0 以强迫它们看起来 象数字。

syscall 返回被调用的系统调用返回的任何数值。在 C 传统里,如果你那个系统调用失败,那么 syscall 返回 -1 并且设置 $!(errno)。有些系统调用在成功的时候合理地返回 -1。操作这样 的的调用的正确方法是在调用之前赋值给 $!=0;然后如果 syscall 返回 -1 的话检查 $! 的值。

不是所有的系统调用可以用这个方法访问。比如,Perl 支持最多给你的系统调用传递 14 个参数。 通常这么多已经足够了,但是,对于那些返回多个数值的系统调用就有问题了。比如 syscall($SYS_pipe):它返回创建的管道的读端文件号码。我们没有办法检查另外一端的文件 号码。你可以用 pipe 避免这个例子里的问题。要解决一般性的问题,写直接访问系统调用的 XSUB(外部过程模块,一种 C 写的程序)。然后把你的新模块放到 CPAN,使之流行开来。

下面的子过程以浮点数返回当前时间,而不是象 time 那样返回整数秒。(它只能在那些支持 gettimeofday(2) 系统调用的机器上用。)

   sub finetime() {
      package main;      # 用于下一个 require
      require 'syscall.ph';
      # 预先把缓冲区设置为 32 位长...
      my $tv = pack("LL", ());
      syscall(&SYS)gettimeofday, $tv, undef) >= 0
         or die "gettimeofday: $!";
      my($seconds, $microseconds) = unpack("LL", $tv);
      return $seconds + ($microseconde/ 1_000_000);
   }

假设 Perl 不支持 setgroups(2) 系统调用,(注:尽管它可以通过 $( 支持),但是你的核心 支持。那么你就可以用下面的方法获得它的支持:

   require 'syscall.ph';
   syscall(&SYS_setgroups, scalar @newgids, pack("i*", @newgids))
      or die "setgroups: $!";

你可能需要按照 Perl 安装指导里描述的那样运行 h2ph,检查 syscall.ph 是否存在。有些系统 可能要求使用 "II" 模板。更头疼的是,syscall 假设 C 类型 int,long,和 char* 的尺寸是 相等的。希望你不要把 syscall 当作移植性的体现。

参阅 CPAN 里的 Time::HiRes 模块获取一个有着更好的分辨率的时间装置的更严格的方法。

29.2.171 sysopen

sysopen 函数打开 FILENAME 给出文件名的文件,并且把它和 FILEHANDLE 关联起来。如果 FILEHANDLE 是一个表达式,那么它的值用做该文件句柄的名字或者引用。如果 FILEHANDLE 是一个未定义值的变量,那么 Perl 将会为你创建一个值。如果调用成功,那么返回值是真,否则 是假。

这个函数是你的系统 open(2) 系统调用后面跟着一个 fdopen(2) 库调用的接口。因此,在这儿你 需要略微把自己想象成一个 C 程序员。MODE 参数的可能数值和标志位可以通过 Fcntl 模块获得。 因为不同的系统支持不同的标志位,所以不要指望你的系统里能够用上所有这些标志位。参阅你的 open(2) 手册页或者它本地的等价物获取细节。当然,下面的标志在那些带有合理 C 库的系统里 是存在的:

标志 含义
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读和写
O_CREAT 如果文件不存在则创建之
O_EXCL 如果文件已经存在则失败
O_APPEND 附加到文件上
O_TRUNC 截断该文件
O_NONBLOCK 非阻塞访问

不过,还有许多其他选项。下面是一些不那么常见的标志:

标志 含义
O_NDELAY O_NONBLOCK 的老式同义词
O_SYNC 写块时直到数据已经从物理上写入下层的硬件以后(才返回)可能还会看到 O_ASYNC,O_DSYNC 和 O_RSYNC。
O_EXLOCK 带着 LOCK_EX 的 flock(只是劝告性的)
O_SHLOCK 带着 LOCK_SH 的 flock(只是劝告性的)
O_DIRECTORY 如果该文件不是目录则失败
O_NOFOLLOW 如果路径中最后一个部件是符号链接则失败
O_BINARY 为 Microsoft 系统 binmode 该句柄。有时候还会有一个 O_TEXT存在以获取相反的行为
O_LARGEFILE 有些系统需要这个标志来标识超过 2 GB 的文件
O_NOCTTY 如果你还没有控制终端,那么打开一个终端文件不会令该终端成为这个进程的控制终端,通常不再需要了。

O_EXCL 标志不是用于锁定的:在这里,排它意味着如果该文件已经存在,那么 sysopen 失败。

如果 FILENAME 命名的文件还不存在,并且 MODE 包括 O_CREAT 标志,那么 sysopen 将 在被你的当前 umask 修改后的参数 MASK 决定的权限范围内(或者如果省略这个参数时缺省 是 0666)创建该文件。这样的缺省是有道理的,参阅 unmask 里的记录获得一个解释。

用 open 和 sysopen 打开的文件句柄可以交互地使用。你不必因为碰巧用 sysopen 打开了文件 而使用 sysread 和它的朋友们来操作文件,而如果你用 open 打开它也不意味着你就不能用 sysread 等函数。open 和 sysopen 都可以做一些对方做不了的事情。普通的 open 可以打开 管道,派生进程,设置纪律,复制文件句柄,以及把一个文件描述符号码转换成一个文件句柄。 它还忽略文件名开头和结尾的空白,并且把“-”当作一个特殊的文件名。但是如果你要打开的是 一个真正的文件,那么 sysopen 就可以做 open 能做的任何事情。

下面的例子显示了对两个函数的等效调用。我们为了清晰起见省略了 or die $! 检查,不过你自己 的程序里可是一定要检查这些值的呦。我们将把我们限制于只检查那些实际上在所有操作系统里都 可以用的标志。这个活只是用位操作符 | 把传递给 MODE 参数的数值 OR (或)在一起而已。

而下面的事情是你可以用 sysopen 干的,但是却不能用普通的 open 干:

在第三十二章描述的 FileHandle? 模块提供了一套打开文件的面向对象的同义词(以及一点点新的 功能)。我们很欢迎你在任何用 open,sysopen,pipe,socket,或者 accept 创建的句柄上 调用 FileHandle? 方法(注:实际上是 IO::File 或者 IO::Handle 方法),就算你不用该模块 初始化这些句柄也可以。

29.2.172 sysread

这个函数试图使用低层系统调用 read(2) 从你声明的 FILEHANDLE 里读取 LENGTH 字节到变量 SCALAR 中。该函数返回读取的字节数量,或者在 EOF 时返回 0。(注:在 Perl 里没有 syseof 函数,但这样是对的,因为 eof 在设备文件(比如说终端)上运转的并不怎么正确。用 sysread 并且检查返回值是否为 0 来判断你是否读完了。)出错时,sysread 函数返回 undef。 SCALAR 将会根据实际读取的长度伸缩。如果声明了 OFFSET,那么它指明应该从字串里的哪个位置 开始读取字节,这样你就可以在一个用做缓冲区的字串中间读取。要获取使用 OFFSET 的例子, 请参阅 syswrite。如果 LENGTH 为负数或者 OFFSET 指向了该字串的外边,那么就会抛出一个 例外。

你应该准备处理那些标准 I/O 通常会为你处理的问题(比如中断了的系统调用)。因为它绕开了 标准的 I/O,所以不要把 sysread 和其他类型的读取,print ,printf,write,seek,tell, 或者 eof 在同一个文件句柄上混合使用,除非你准备承受极其希奇古怪(和/或痛苦)的东西。 同样,请注意,如果你从一个包含 Unicode 或者任何其他多字节编码的文件里读取数据,那么 缓冲区的边界有可能落在一个字符的中间。

29.2.173 sysseek

这个函数使用系统调用 lseek(2) 设置 FILEHANDLE 的系统位置。它绕开了标准 I/O,因此把它 和读(除了 sysread 以外),print,printf,write,seek,tell,或者 eof 混合起来使用将 会导致混乱。FILEHANDLE 可以是一个表达式,该表达式的值给出文件句柄的名字。WHENCE 的值为 0 时设置句柄新位置为 POSITION ,1 时设置为当前位置加 POSITION,2 时设置为 EOF 加 POSITION(通常为负数)。你可以用来自标准 IO::Seekable 和 POSIX 模块或者——Perl 5.6 里的 Fcntl 模块里面的 SEEK_SET,SEEK_CUR 和 SEEK_END 作为 WHENCE 的值,而 Fcntl 模块可能更容易移植和更方便一些。

成功时返回新位置,失败时返回 undef。位置零是以特殊字串“0 but true”返回的,该字串可以 直接当数字使用而不会导致警告。

29.2.174 system

这个函数为你执行任何系统里的程序并返回该程序的退出状态——而不是它的输出。要捕获命令行 上的输出,你应该用反勾号或者 qx//。system 函数的运转非常类似 exec,只不过 system 先做 一个 fork,然后在 exec 之后等待执行的程序的结束。也就是说它为你运行这个程序并且在它 完成之后返回,而 exec 用新的程序代替你运行的程序,所以如果替换成功的话它从不返回。

参数的处理因参数的数目的不同而不同,就象在 exec 里描述的那样,包括判断是否调用 shell 以及你是否用声明另外一个 PATHNAME 的方法使用了该函数其他的名称。

因为 system 和反勾号阻塞 SIGINT 和 SIGQUIT,所以向那些正在这样运行的程序发送这些信号 之一(比如通过一个 Control-C)时并不会中断你的主程序。但是你运行的另外一个程序的确收到 这个信号。请检查 system 的返回值,判断你运行的程序是否正常退出。

   @args = ("command", "arg1", "arg2");
   system(@args) == 0
      or die "system @args failed: $?"

返回值是和该函数通过 wait(2) 系统调用返回的一样的退出状态。在传统的语意里,要获取实际的 退出值,要除以 256 或者右移 8 位。这是因为低 8 位里有一些其他的东西。(实际上是其他的 两些东西。)最低七位标识杀死该进程的信号号码(如果有的话),而第八位标识该进程是否倾倒 了核心。你可以通过 $?($CHILD_ERROR)来检查所有失效可能性,包括信号和核心倾倒:

   $exit_value = $? >> 8;
   $exit_value = $? & 127;   # 或者 0x7f, 0177, 0b0111_1111
   $dumped_core = $? & 128;   #  或者 0x80, 0200, 0b1000_0000

如果该程序是通过系统 shell (注:定义为 /bin/sh 或者任何在你的平台上有意义的东西,但 不是那些用户碰巧在某个时候用到的 shell。)运行的,这可能是因为你只有一个参数而且该参数 里面有 shell 元字符,那么通常返回码受那个 shell 的怪癖和功能的影响。换句话说,在这种 情况下,你可能无法获取我们前面描述了详细信息。

29.2.175 syswrite

这个函数试图用 write(2) 系统调用向你声明的 FILEHANDLE 里写入从变量 SCALAR 里获取的 LENGTH 字节的数据。该函数返回实际写入的字节数,或者是出错时返回 undef。如果声明了 OFFSET,那么 它指明从字串里的哪个位置开始写。(比如,你可能用一个字串做一个缓冲区,这时你就需要这个 功能了,或者你需要从一个部分写中恢复过来。)负数 OFFSET 表示写应该从该字串的后面向前数 这么多个字节。如果 SCALAR 是空的,那么唯一允许的 OFFSET 是 0。如果 LENGTH 为负数或者 OFFSET 指向了字串的外面,那么就会抛出一个例外。

要从文件句柄 FROM 中拷贝数据到文件句柄 TO,你可以用下面这样的东西:

   use Errno qw/EINTR/;
   $blksize = (stat FROM)[11] || 16384;      # 选定的块大小?
   while ($len = sysread FROM, $buf, $blksize) {
      if (!defined $len) {
         next if $! == EINTR;
         die "System read error: $!\n"
      }
      $offset = 0;
      while ($len) {            # 处理部分写问题
         $written = syswrite TO, $buf, $len, $offset;
         die "System write error: $!\n" unless defined $written;
         $offset   += $written;
         $len   -= $written;
      }
   }

你必须准备处理标准 I/O 通常会为你处理的问题,比如部分写。因为 syswrite 绕开了 C 标准 I/O 库,所以不要把它的调用和读(除了 sysread 以外),写(象 print,printf,或者 write),或者其他 stdio 函数,比如 seek,tell,或者 eof 混合在一起用,除非你想自找 麻烦。

29.2.176 tell

这个函数返回 FILEHANDLE 的当前文件位置(以零为基的字节数)。该值通常可以在程序中稍后的 时候传递给 seek 函数以找回当前位置。FILEHANDLE 可以是一个给出实际文件句柄的表达式, 或者一个指向文件对象的引用。如果省略 FILEHANDLE,那么该函数返回最后一个读取的文件的 位置。只有普通文件的文件位置才有意义。设备,管道,和套接字都没有文件位置。

没有 systell 函数,你可以用 sysseek(FH, 0, 1) 来实现同样的功能。参阅 seek 获取一个 如何使用 tell 的例子。

29.2.177. telldir

这个函数返回在 DIRHANDLE 上的 readdir 的当前位置。而这个返回值可以给 seekdir 用于访问 一个目录里的某个特定的位置。该函数和对应的系统库过程在关于可能的目录压缩问题上有这一样的 注意事项。该函数可能不是在所有实现了 readdir 的地方有实现了,即使该平台实现了它,你也 不能计算它的返回值。因为该返回值只是一个晦涩的数值,只对 seekdir 有意义。

29.2.177. tie

此函数把一个变量和一个类绑定在一起,而该类提供了该变量的实现。VARIABLE 是要绑定的变量 (标量,数组,或者散列)或者类型团(代表一个文件句柄)。CLASSNAME 是实现一个正确类型的 类名字。

任何额外的参数都传递给该类的合适的构造方法,可能是 TIESCALAR,TIEARRAY,TIEHASH 或者 TIEHANDLE 之一。(如果没有找到合适的方法,则抛出一个例外。)通常,那些都是可能被传递给 象 dbm_open(2) 这样的 C 函数的参数,但是它们的含义是和包相关的。构造器返回的对象随后被 tie 函数返回,而如果你想在 CLASSNAME 里访问其他方法,那么这个对象就很有用了。(该对象 也可以通过 tied 函数访问。)因此,一个把散列与一个 ISAM 绑定的实现可以提供一些额外的 方法用于顺序地跨过一个键字的集合(ISAM 里的“S”就是 sequentially,顺序的意思),因为 你典型的 DBM 实现是不能做这些事情的。

象 keys 和 values 这样的函数在和 DBM 这样的大对象一起使用的时候可能返回非常巨大的数值 列表。你可能会更愿意使用 each 函数来遍历这样的列表。比如:

   use NDBM_File;
   tie %ALIASES, "NDBM_File", "/etc/aliases", 1, 0
      or die "Can't open aliases: $!\n";
   while (($key, $val) = each %ALIASES) {
      print $key, ' = ', $val, "\n";
   }
   untie %ALIASES;

一个实现散列的类应该提供下列方法:

TIEHASH CLASS, LIST
FETCH SELF, KEY
STORE SELF, KEY, VALUE
DELETE SELF, KEY
CLEAR SELF
EXISTS SELF, KEY
FIRSTKEY SELF
NEXTKEY SELF, LASTKEY
DESTROY SELF

一个实现普通数组的类应该提供下列方法:

    TIEARRAY CLASS, LIST
    FETCH SELF, SUBSCRIPT
    STORE SELF, SUBSCRIPT, VALUE
    FETCHSIZE SELF
    STORESIZE SELF, COUNT
    CLEAR SELF
    PUSH SELF, LIST
    POP SELF
    SHIFT SELF
    UNSHIFT SELF, LIST
    SPLICE SELF, OFFSET, LENGTH, LIST
    EXTEND SELF, COUNT
    DESTROY SELF

一个实现标量的类应该提供下列方法:

    TIESCALAR CLASS, LIST
    FETCH SELF,
    STORE SELF, VALUE
    DESTROY SELF

一个实现文件句柄的类应该提供下列方法:

TIEHANDLE CLASS, LIST
READ SELF, SCALAR, LENGTH, OFFSET
READLINE SELF
GETC SELF
WRITE SELF, SCALAR, LENGTH, OFFSET
PRINT SELF, LIST
PRINTF SELF, FORMAT, LIST
CLOSE SELF
DESTROY SELF

并不是上面提到的所有方法都需要实现:Tie::Hash,Tie::Array,Tie::Scalar,和 Tie::Handle 模块提供了有着合理的缺省的基类。参阅第十四章,捆绑变量,获取所有这些方法的 详细描述。和 dbmopen 不同,tie 函数将不会为你 use 或者 require 一个模块——你必须自己 明确地做这件事情。参阅 DB_File 和 Config 方法获取有趣的 tie 实现。

29.2.179 tied

这个函数返回一个引用,该引用指向包含在 VARIABLE 里的标量,数组,散列或者类型团的的下层 对象。(VARIABLE 是最初用 tie 调用把该变量和一个包绑定在一起的同一个值。)如果 VARIABLE 没有和一个包绑定,它返回未定义的数值。因此,比如,你可以用:

   ref tied %hash

找出你的散列与哪个包捆绑。(假设你忘记了。)

29.2.180. time

这个函数返回自“纪元”以来的没有润秒的秒数,纪元通常是 1970年1月1日 00:00:00 UTC。 (注:不要和创造 Unix 的“历史”相混淆。(其他操作系统可能有不同的纪元,更别说历史了。 ))返回值可以传递给 gmtime 和 localtime,可以用于比较 stat 返回的文件修改以及访问的 时间,还有就是传递给 utime。

  $start = time();
   system("some slow command");
   $end = time();
   if ($end - $start > 1) {
      print "Program started: ", scalar localtime($start), "\n";
      print "Program ended:  ", scalar localtime($end), "\n";
   }

29.2.181 times

在这个环境里,这个函数返回一个四元素的列表,该列表给出这个进程和它已结束的子进程以秒计 (可能是分数)的用户和系统 CPU 时间。

   ($user, $system, $cuser, $csystem) = times();
   printf "This pid and its kids have consumed %.3f seconds\n",
      $user + $system + $cuser + $csystem;

在标量环境里,只返回用户时间。比如,要计算一段 Perl 代码的执行速度:

   $stat = times();
   ...
   $end = times();
   printf "that took %.2f CPU seconds of user time\n",
      $end - $start;

29.2.182 tr///

这是转换(也称之为翻译)操作符,它和 Unix sed 程序里的 y/// 操作符类似,但不论从任何人 的角度来看都更好些。参阅第五章。

29.2.182 truncate

这个函数截断在 FILEHANDLE 上打开的或者 EXPR 命名的文件到指定的长度。如果在你的系统上, ftruncate(2) 或者等效的东西没有实现,那么该函数抛出一个例外。(如果你有磁盘空间的话, 你总是可以通过拷贝文件的开头来截断它。)该函数在成功的时候返回真,否则返回 undef。

29.2.183 uc

这个函数返回 EXPR 的大写的版本。它是实现双引号字串里的 \U 逃逸的内部函数。Perl 将试图 在考虑你的区域设置的前提下做正确的事情,不过我们仍在努力让这个功能也能用于 Unicode。 参阅 perllocalle 手册页获取最新的进展。在任何情况下,如果 Perl 使用 Unicode 表,uc 都会转换成大写字符而不是标题字符。参阅 ucfirst 获取转换成标题字符的信息。

29.2.184 ucfirst

这个函数返回将 EXPR 第一个字符标题化(“Unicode”里的标题字符)的版本。而其他字符则不 加触动。它是实现双引号字串里的 \u 逃逸的内部函数。如果你 use locale 并且你的数据看上去 不象 Unicode,那么 Perl 会考虑你当前的 LC_CTYPE 区域设置,但是我们现在不能做任何保证。

要强制字串里第一个字符是标题字符而其他的都是小写字符,你可以用:

   ucfirst lc $word

它等效于"\u\L$word"。

29.2.186 umask

这个函数用 umask(2) 系统调用为该进程设置 umask 并返回原来的那个。你的 umask 告诉操作 系统在创建新文件的时候,哪个权限位是不允许的,包括那些正好是目录的文件。如果省略了 EXPR,那么该函数只是返回当前 umask。比如,为了确保“user”位是允许,而“other”位是 不允许的,你可以用下面的代码:

   umask((umask() & 077) | 7);      # 不改变组的权限位

请记住 umask 是一个数字,通常是以八进制形式给出的;它不是八进制位的字串。如果你拿到的 是一个字串,又见 oct,还要记住这个 umask 位是普通权限位的补。

Unix 权限位 rwxr-x--- 是用三个三位集,或者三个八进制位来表示的:0750(前面的 0 表示它 是八进制而不是其中一位)。因为 umask 的位是翻转的,所以它代表关闭了的权限位。你提供给 你的 mkdir 或者 sysopen 的权限值(或者“模式”)都会被你的 umask 修改,所以就算你告诉 sysopen 创建一个权限为 0777 的文件,如果你的 umask 是 0022,那么创建出来的文件的权限 也是 0755。如果你的 umask 是 0027(组不能写,其他不能读,写,和执行),那么给 sysopen 传递一个 MASK 为 0666 的值将创建一个模式为 0640 的文件(因为 0666 & ~0027 是 0640)。

这里是一些建议:使用模式 0666 创建普通文件(在 sysopen 里)以及 0777 给目录(用 mkdir)和可执行文件。这样就给予用户自由的选择:如果它们想保护文件,那么它们选择进程 umask 022,027,或者甚至特别反社会的掩码 077。程序最好让用户自己做策略决策。这条规则的 例外是那些需要写私人文件的程序:邮件文件,网络浏览器的 cookie,.rhost 文件,等等。

如果你的系统里没有实现 umask(2) 并且你试图限制你自己的权限(也就是说,如果 (EXPR & 0700) > 0),那么你就会触发一个运行时例外。如果你的平台没有实现 umask(2) 并且 你不准备限制自己的权限,那么这个函数简单地返回 undef。

29.2.186 undef

undef 是我们所谓的“未定义值”的缩写。同时,它还是一个永远返回未定义值的函数的名字。 我们很高兴能混淆它们俩。

相同的是,如果你给 undef 函数提供一条记录作为它的参数,那么它还可以明确的解除该记录的 定义。如果声明了 EXPR 参数,那么它必须是一个左值。因此你可能只能在一个标量数值,整个 散列或者数组,一个子过程名字(用 & 前缀),或者一个类型团上这么用,任何和该对象关联的 存储空间都将被恢复用于重复使用(不过在大多数操作系统上都不是返回给系统)。undef 函数对 大多数特殊变量可能都不会做想你想象的处理。在象 $1 这样的只读变量上使用将抛出一个例外。

undef 函数是一个单目操作符,不是列表操作符,因此你只能每次解除一个东西的定义。下面是 一些 undef 作为单目操作符的用法:

   undef $foo;
   undef $bar{'blurfl'};      # 和 delete $bar{'blurfl'} 不同
   undef @ary;
   undef %hash;
   undef &mysub;
   undef *xyz;            # 删除 $xyz, @xyz, %xyz, &xyz 等等。

如果没有参数,undef 只是用做数值:

   select(undef, undef, undef, $naptime);

   return (wantarray ? () : undef) if $they_blew_it;
   return if $they_blew_it;   # 一样的东西

你可以把 undef 用做一个列表赋值中左边的一个占位符,这个时候右边的对应的数值只是简单地 抛弃。除此之外,你不能在其他地方拿 undef 做左值:

   ($a, $b, undef, $c) = &foo;   # 忽略返回的第三个数值

同样,不要拿任何东西和 undef 做比较——那样不会按照你想象的方式处理的。它所作的事情只是 与 0 或者空字串比较。使用 defined 函数判断一个数值是否定义。

29.2.188. unlink

这个函数删除一列文件。(注:实际上,在一个 POSIX 文件系统里,它删除指向真实文件目录记录 (文件名)。因为一个文件可以从一个或多个目录里引用(链接),该文件不会被删除,直到指向 它的最后一个引用被删除。)此函数返回被成功删除的文件名的个数。一些简单的例子:

   $count = unlink 'a', 'b', 'c';
   unlink @goners;
   unlink glob("*.orig");

除非你是超级用户或者给 Perl 使用了 -U 命令行选项,否则 unlink 函数不会删除目录。即使 符合这些条件,你也要注意删除一个目录可能回造成对你的文件系统的彻底损坏,应该用 rmdir 代替。

下面是一个带有非常简单的错误检查的 rm 命令:

   #!/usr/bin/perl
   @cannot = grep {no unlink} @ARGV;
   die "$0: could not unlink @cannot\n" if @cannot;

29.2.189. unpack

这个函数是 pack 的逆操作:它根据 TEMPLATE 把一个表示一个数据结构的字串(EXPR)扩展成 一列数值并返回那些数值。在标量环境里,它可以用于解包一个数值。这里的 TEMPLATE 有着和 pack 函数里的大多数格式——它声明要解包的数值的顺序和类型。参阅 pack 函数获取有关 TEMPLATE 的详细描述。如果 TEMPLATE 里有非法元素,或者试图跨过 x,X,或者 @ 格式字串的 外面,都会抛出例外。

该字串会分解成 TEMPLATE 里描述的片段。每个片段都独立地转化成一个数值。通常,该字串的 字节要么是 pack 的结果,要么代表某种类型的 C 结构。

如果一个域的重复计数比输入字串剩余部分允许的尺寸大,那么重复计数就会被不声不响地缩小。 (不过,你通常会在这个地方放一个 * 做重复计数。)如果输入字串比 TEMPLATE 描述的长, 那么字串剩余的部分被忽略。

unpack 函数对纯文本数据也很有用,而不仅仅是对二进制数据管用。设想你有一个数据文件,它的 内容看起来象下面这样:

1986 Ender's Game           Orson Scott Card
1985 Neuromancer            William Gibson
1984 Startide Rising        David Brin
1983 Foundation's Edge      Isaac Asimov
1982 Downbelow Station      C. J. Cherryh
1981 The Snow Queen         Joan D. Vinge

你不能用 split 来分析出各个域,因为这里没有明显的分隔符。这里的域是由它们的字节偏移量来 决定的。因此就算这是一个普通的文本记录,但因为它是固定格式的,所以你就可以用 unpack 把 它们分解成数据域:

   while (<>) {
      ($year, $title, $author) = unpack("A4 x A23 A*", $_);
      print "$author won ${year}'s Hugo for $title.\n";
   }

(我们在这里写成 ${year}'s 的原因是 Perl 会把 $year's 当作 $year::s 看待。)

下面是一个完整的 undecode 程序:

   #! /usr/bin/perl
   $_ = <> until ($mode, $file) = /^begin\s*(\d*)\s*(\S*)/;
   open(OUT, "> $file") if $file ne "";
   while (<>) {
      last if /^end/;
      next if /[a-z]/;
      next unless int((((ord() - 32) & 077) + 2) / 3) ==
            int (length() / 4);
      print OUT unpack "u", $_;
   }
   chmod oct($mode), $file;

除了那些 pack 里允许的数据域以外,除了各个项自身以外,你还可能在一个数据域前面前缀一个 %number 作成一个所有项的简单的 number 位附加校验和。该校验和是通过类加扩展数值的数字 值来计算的(对于字串域,求 ord($char) 的和,而对于位域,计算零和一的和)。比如,下面的 代码计算和 SysV? sum(1) 相同的数字:

   undef $/;
   $checksum = unpack ("%32C*", <>) % 65535;

下面的代码高效的计算一个位流里的设置上的位的个数:

   $setbits = unpack "%32b*", $selectmask;

下面是一个简单的 BASE64 解码器:

   while (<>) {
      tr#A-Za-z0-9+/##cd;            # 删除非 base64 字符
      tr#A-Za-z0-9+/# -_#;            # 转换成 uuencode 格式
      $len = pack("c", 32 + 0.75*length);   # 计算长度字节
      print unpack("u", $len . $_);      # uudecode 并打印
   }

29.2.190. unshift

这个函数做 shift 的逆操作。(或者是 push 的逆操作,取决于你怎么看它。)它在数组前面 增加 LIST,并返回在数组里的新的元素个数:

   unshift @ARGV, '-e', $cmd unless ARGV[0] =~ /^-/;

请注意 LIST 是整个放到前面,而不是每次一个元素,因此放到前面的元素保持相同顺序。用 reverse 实现这些元素的翻转。

29.2.190. untie

打破 VARIABLE 里包含的变量或者或者类型团和与它捆绑的包之间的绑定。参阅 tie,以及第十四 章的全部,尤其是“一个精细的松绑陷阱”节。

29.2.190. use

use 声明装载一个模块(如果它还没有被装载),并且把子过程和变量从这个命名模块输入到当前 包。(从技术上来讲,它从那个命名模块向当前包输入一些语意,通常是通过把一些子过程或者 变量名作成你的包里的别名的方法。)大多数 use 的声明看起来象:

   use MODULE LIST;

这样和下面是完全一样的:

   BEGIN { require MODULE; import MODULE LIST; }

BEGIN 迫使 require 和 import 在编译时发生。require 确保该模块在还没有装载的时候装入 内存。import 不是内建的函数——它只是一个普通的类方法,调用名字叫 MODULE 的包,告诉该 模块把列表里的特性拖到当前包里来。模块可以用自己喜欢的任何方法实现它的输入方法,尽管 大多数只是通过从 Exporter 类中继承 import 方法。Exporter 类在 Exporter 模块中定义。 参阅第十一章,模块,以及 Exporter 模块获取更多信息。如果找不到 import 方法,那么调用将 不声不响地忽略。

如果你不希望你的名字空间被修改,那么明确地提供一个空列表:

   use MODULE ();

它和下面的代码完全一样:

   BEGIN { require MODULE; }

如果给 use 的第一个参数是象 5.6.2 这样的版本号,那么当前执行着的 Perl 版本必须至少和 声明的版本一样新。如果当前的版本比 VERSION 小,那么就会打印出一条错误信息然后 Perl 马上退出。这样就可以有效地在装载需要更新的版本的库模块之前检查当前 Perl 的版本,因为 有时候我们必须“破坏”老版本的错误特性。(我们总是试图尽可能不破坏任何东西。而实际上 我们总是试图少破坏东西。)

谈到不破坏其他东西,Perl 仍然接受下面形式的老版本号:

   use 5.005_03;

不过,为了和工业标准更好的看齐,Perl 5.6 现在接受(并且也更愿意使用)下面的三段式:

   use 5.6.0;      # 它是版本 5,子版本 6,补丁级 0。

如果 VERSION 参数在 MODULE 后面出现,那么 use 将在类 MODULE 里调用 VERSION 方法, 同时把给出的 VERSION 当作参数给他。请注意在 VERSION 后面没有逗号!缺省的 VERSION 方法(通常是从 UNIVERSAL 类里继承过来的。)会在给出的版本大于变量 $Module::VERSION 的值的情况下发表意见。

参阅第三十二章获取一个标准模块的列表。

因为 use 提供了一个非常开放的接口,所以用法(编译器指示器)也是通过模块来实现的。当前 实现了的用法包括:

   use autouse 'Carp' => qw(carp croak);
   use bytes;
   use constant PI => 4 * atan2(1,1);
   use diagnostics;
   use integer;
   use lib '/opt/projects/spectre/lib';
   use locale;
   use sigtrap qw(die INT QUIT);
   use strict qw(subs vars refs);
   use warnings "deprecated";

许多这些用法模块向当前词法范围输入语意。(它和普通模块不同,普通模块只是向当前包里输入 符号,而除了该词法范围是在带有该包的情况下编译的以外,那些符号和当前词法范围没有什么 关系。也就是说,哦,看看第十一章吧。)

还有一个对应的声明,no,它“戒除”任何原来用 use 输入的东西,让它们变得不再重要:

   no integer;
   no strice 'refs';
   no utf8;
   no warnings "unsafe";

参阅第三十一章获取一个标准用法的列表。

29.2.193 utime

该函数改变一列文件里的每一个文件的访问和修改时间。列表的头两个元素必须是数字化的访问和 修改时间,顺序是访问在前修改在后。该函数返回成功改变的文件的数目。每个文件的 inode 修改 时间设置成当前时间。下面是一个 touch 命令的例子,它设置该文件的修改日期(假设你是 所有者)为近一个月后:

   #! /usr/bin/perl
   # montouch - post-date files now + 1 month
   $day = 24 * 60 * 60;         # 24 小时的秒数
   $later = time() + 30 * $day;   # 30 天接近一个月
   utime $later, $later, @ARGV;

29.2.194. values

这个函数返回一个包含指定散列 HASH 里的所有值的列表。这些值是以看似随机的顺序返回的, 但是这个顺序和 keys 或 each 函数在同一个散列上生成的顺序相同。怪异的是,如果要通过一个 散列的数值对它进行排序,那么你通常需要使用 keys 函数,所以看看 keys 函数里的例子找找 灵感。

你可以用这个函数修改一个散列的数值,因为它返回的列表包含数值的别名,而不是拷贝。(在 早期的版本里,你需要用散列的片段来实现这个功能。)

   for (@hash{keys %hash}) { s/foo/bar/g }    # 老办法
   for (values %hash)      { s/foo/bar/g }   # 新手段

在一个和某个巨大的 DBM 文件捆绑的散列上使用 values 也肯定会生成一个巨大的列表,导致你 拥有一个巨大的进程。你可能应该使用 each 函数,它会一个一个地遍历散列记录,而不会把它们 的所有东西都吞进一个庞大的,哦,应该是巨大的列表里。

29.2.195 vec

vec 函数制造一个存储紧凑的无符号整数的列表。这些整数在一个普通的 Perl 字串里尽可能紧密 的绑在一起。EXPR 里的字串被当作一个位串对待,该未串是用若干元素组成的,而元素的数目取决 于字串的长度。

OFFSET 声明你关心的特定元素的索引。读和写元素的语法是一样的,因为 vec 根据你是在左值 环境还是右值环境里来存储和恢复元素值。

BITS 声明每个元素以位计算的时候宽度是多少,它必须是二的幂:1,2,4,8,16,或 32(有些 平台上还有 64)。(如果声明了任何其他数值,那么就会抛出一个例外。)因此每个元素都可以 包含一个范围在 0 .. (2**BITS)-1 的整数。对于小一些的尺寸,那么每个字节里都会尽可能多的 打包进去元素。如果 BITS 是 4,那么每个字节里有两个元素(通常它们被称为半字节(nybble ))。等等。大于一个字节的整数是以大头在前的字节序存储的。

一个无符号整数列表可以存储在一个标量变量里,方法是把它们分别赋予 vec 函数。(如果 EXPR 不是一个合法的左值,那么抛出一个错误。)在随后的例子里,那些元素是 4 位宽:

   $bitstring = "";
   $offset = 0;
   
   foreach $num (0, 5, 5, 6, 2, 7, 12, 6) {
      vec($bitstring, $offset++, 4) = $num;
   }

如果一个元素超出了它要写的字串的结尾,那么 Perl 先用足够的零内容字节扩展该字串。

存储在标量变量里的向量然后就可以通过声明正确的 OFFSET 来检索:

   $num_elements = length($bitstring)*2;   # 每个字节 2 元素

   foreach $offset (0 .. $num_elements-1) {
      print vec($bitstring, $offset, 4), "\n";
   }

如果选择的元素超出了字串的结尾,那么返回 0。

用 vec 创建的字串还可以用逻辑操作符 |,&,^,和 ~ 操作。如果两个操作数都是字串,那么 这些操作符将假定需要进行的是位串操作。参阅第三章,单目和双目操作符,“位操作符”一节里 的例子。

如果 BITS == 1,那么就可以创建一个所有位都存储在一个标量里的位序列。顺序是这样的, vec($bitstring, 0,1) 保证进入字串里的第一个字节的最低位。

   @bits = (0,0,1,0, 1,0,1,0, 1,1,0,0, 0,0,1,0);

   $bitstring = "";
   $offset = 0;

   foreach $bit (@bits) {
      vec($bitstring, $offset++, 1) = $bit;
   }

   print "$bitstring\n";      # "TC", 也就是 '0x54', '0x43'

一个位串可以通过声明一个“b*”模板给 pack 或者 unpack 从一串 1 和 0 转换过来,或者是 转换成这些 1 和 0 的字串。另外,pack 可以和“b*”模板一起使用从一个 1 和 0 的字串创建 一个位串。该顺序和 vec 需要的顺序是兼容的。

   $bitstring = pack "b*", join('', @bits);
   print "$bitstring\n";   # "TC",和前面例子一样

unpack 可以用于从该位串中抽取这个 0 和 1 的列表:

   @bits = split(//, unpack("b*", $bitstring));
   print "@bits\n";      # 0 0 1 0 1 0 1 0 1 1 0 0 0 0 1 0

如果你知道位串的位的确切长度,那么这个长度可以用于“*”的位置。

参阅 select 获取使用 vec 生成的位图的其他的例子。参阅 pack 和 unpack 获取操作二进制 数据的更高级别的方法。

29.2.196. wait

这个函数等待一个子进程退出并返回消失了的进程的 PID,或者如果没有子进程了就返回 -1(或者 在一些系统里,子进程自动被收割也如此。)它在 $? 里返回的状态和在 system 里描述的一样。 如果你有僵死子进程,那么你就应该调用这个函数,或者 waitpid。

如果你在等待一个子进程,但是用 wait 没有找到它,那么你可能就是调用了 system,关闭了 一个管道,或者在 fork 和 wait 之间使用了反勾号。这些构造也做 wait(2) 并且因此可能收割 你的子进程。使用 waitpid 避免这样的情况。

29.2.197. waitpid

这个函数等待特定的子进程结束并在该进程死亡之后返回其 PID,如果没有其他的子进程时返回 -1,或者 FLAGS 里的标志声明的是非阻塞状态而该进程尚未退出,则返回 0。死亡的进程返回的 状态存储在 $?,并且和 system 里描述的一样。要获取有效的标志值,那么你必须输入 “:sys_wait_h”从 POSIX 里输入标签组。下面是一个等待所有挂起的僵死进程的非阻塞的例子:

   use POSIX ":sys_wait_h";
   do {
      $kid = waitpid(-1, &WNOHANG);
   } until $kid == -1;

在那些既没有实现 waitpid(2) 也没有实现 wait4(2) 系统调用的平台上,你可以声明的 FLAGS 只有 0。换句话说,你在那里可以等待一个特定的 PID,但是你不能在非阻塞模式里做这些事情。

在一些系统里,返回值为 -1 意味着该子进程被自动收割了,因为你设置了 $SIG{CHLD} = 'IGNORE'。

29.2.198. wantarray

如果当前正在执行的子过程在寻找列表数值,那么此函数返回真,否则返回假。如果调用环境需要 的是一个标量,那么该函数返回一个定义了的假(""),而如果调用环境并不需要任何东西,(也 就是说,空环境)那么返回一个未定义的假值(undef);

下面是它的典型用法的例子:

   return unless defined wantarray;      # 不需要干什么事情
   my @a = complex_calculation();
   return wantaray ? @a : \@a;

又见 caller。这个函数真是应该命名为“wantlist”,但是我们是在列表环境还叫数组环境的 时候命名它的。(译注:数组英文词是“array”,列表英文词是“list”。)

29.2.199 warn

这个函数生成一条错误信息,象 die 那样把 LIST 打印到 STDERR,但是并不试图退出或者抛出 一个例外。比如:

   warn "Debug enbled" if $debug;

如果 LIST 为空并且 $@ 已经包含一个数值(通常是前面的 eval 留下来的),那么字串 "\t... caught" 在 STDERR 上附加在 $@ 后面。(这样做类似 die 传播错误的方法,只不过 warn 并不传播(抛出)该例外。)如果你提供的字串上是空的,那么使用 "Warning: something's wrong"。

和 die 一样,如果你提供的字串并不是以换行符结尾,那么自动附加文件和行号信息。warn 函数 和 Perl 的 -w 命令行选项没有关系,但是可以和它一起使用,比如在你想模拟内建函数的时候:

   warn "Something wicked\n" if $^W;

如果安装了 $SIG{__WARN__} 句柄,那么不会打印任何信息。这个句柄是负责对它看到的信息进行 适当处理用的。你想这么做的一个原因可能是把简单的警告转化成一个例外:

   
   local $SIG{__WARN__} = sub {
      my $msg = shift;
      die $msg if $msg =~ /isn't numeric/;
   };

因此大多数句柄都必须对那些它们原先没有准备处理的警告安排显示处理的工作,方法是在句柄里 调用 warn。这么做非常安全,它不会产生无限的循环,因为 WARN 挂钩不会在 WARN 里面被调用。这个行为和 $SIG{__DIE__} 的行为(它不会消除错误文本,但是可以再次调用 die 来改变它)略有区别。

使用 WARN 句柄给我们提供了一个强有力的抑制所有警告的方法,甚至连那些强制性的警告也 给抑制住了。有时候你需要把这个东西封装在 BEGINP{} 块里,这样它就可以在编译时起做用:

# 扫荡掉所有编译时间警告

   BEGIN { $SIG{__WARN__} = sub { warn $_[0] if $DOWARN } }
   my $foo = 10;
   my $foo = 20;      # 不要警告我说重复了 my $foo,
            # 不过,这可是你说的!

   # 在这以前没有编译时和运行时的警告
   $DOWARN = 1;      # 不是一个内建的变量

   #  在这以后打开运行时的警告
   warn "\$foo is alive an $foo!";   # 做显示

参阅 use warnings 用法获取警告的词法范围里的控制。参阅 Carp 模块里的 carp 和 cluck 函数获取其他制造警告信息的方法。

29.2.200. write

这个函数写一条格式化了的记录(可能是多行)到声明的文件句柄,使用和该文件句柄相关联的 格式——参阅第七章里的“格式变量”一节。缺省时与文件句柄相关联的格式是和文件句柄同名的 那个。不过,一个文件句柄的格式可以在你 select 了该句柄以后修改 $~ 变量来修改:

   $old_fh = select(HANDLE);
   $~ = "NEWNAME";
   select($old_fh);

或者说:

   use IO::Handle
   HANLDE->format_name("NEWNAME");

因为格式是放到一个包名字空间里的,所以如果该 format 是在另外一个包里声明的,那么你可能 不得不用该格式的全称:

   $~ = "OtherPack::NEWNAEM";

表单顶部(Top-of-form)的处理是自动操作的:如果当前页里面没有足够的空间容纳格式化的 记录,那么通过写一个进纸符来续该页,这时候在新页上使用一个特殊的表单顶部格式,然后写该 记录。在当前页里余下的行数放在变量 $- 里,你可以把它设置为 0 以强迫在下一次 write 的 时候使用新的一页。(你可能先得 select 该文件句柄。)缺省时,页顶格式的名字就是文件句柄 后面加上 "_TOP",不过,一个文件句柄的格式可以在你 select 了该句柄以后修改 $~ 变量来 修改,或者说:

   use IO::Handle;
   HANDLE->format_top_name("NEWNAME_TOP");

如果没有声明 FILEHANDLE,那么输出就会跑到当前缺省的输出文件句柄,这个文件句柄初始时是 STDOUT,但是可以用单参数形式的 select 操作符修改。如果 FILEHANDLE 是一个表达式,那么 在运行时计算该表达式以决定实际的 FILEHANDLE。

如果声明的 format 或者当前的页顶 format 并不存在,那么抛出一个例外。

糟糕的是,write 函数不是 read 的逆操作。用 print 做简单的字串输出。如果你找到这个函数 的原因是因为你想绕开标准 I/O,参阅 syswrite。

29.2.201 y//

转换操作符(因历史原因,也叫做翻译操作符),也叫做 tr///。参阅第五章。

29.2.136 s///

替换操作符。参阅第五章里的“模式匹配操作符”。

29.2.137. scalar

这个伪函数可以用于 LIST 里,当在列表环境中计算会生成一个不同的结果的时候,强迫 EXPR 在 标量环境中计算。比如:

   my ($nextvar) = scalar ;

避免 在做赋值之前从标准输入把所有的行都读了进来,因为给一个列表(甚至是一个 my 列表)赋值都会产生一个列表环境。(在这里例子里如果没有 scalar,那么来自 的 第一行仍然会赋予 $nextvar,但是随后的行将会被读取并抛弃,因为我们赋值的目标列表只能接受 一个标量数值。)

当然,简单些而且没有那么混乱的方法是不使用圆括弧,这样就把标量环境改变成了列表环境:

   my $nextvar = ;

因为 print 函数是一个 LIST 操作符,所以如果你想把 @ARRAY 的长度打印出来,那么你不得不 说:

   print "Length is ", scalar(@ARRAY), "\n";

Perl 里没有与 scalar 对应的 “list”函数,因为实际上我们从来不需要强迫在列表环境里 计算。这是因为任何需要 LIST 的操作已经给他的列表参数免费提供了一个列表环境。

因为 scalar 是单目操作符,如果你不小心给 EXPR 使用了圆括弧的列表,那么这个东西的行为 就象一个标量逗号表达式一样,在空环境中计算除最后一个列表元素之外的所有其他元素,然后 返回在标量环境中计算的最后一个元素。你很少想要这样的东西。下面的一个语句:

   print uc(scalar(&foo, $bar)), $baz;

在道义上是等效于下面两个的:

   &foo;
   print(uc($bar), $baz);

参阅第二章获取关于逗号操作符的更多细节。参阅第六章的“原型”获取关于单目操作符更多的 信息。

29.2.138 seek

这个函数为 FILEHANDLE 定位文件指针,就好象用于标准 I/O 的 fseek(3) 调用一样。文件里的 第一个位置是在偏移量 0 处,而不是 1 处。同样,偏移量指的是字节位置,而不是行数。通常, 因为行的长度是变化的,所以我们不可能不检查到该点之间的所有文件内容就能访问某一行,除非 你的所有行数都已知是特定的长度,或者你已经做了一个把行数转换成字节数的索引。(同样的 限制也适用于有着变长字符编码的字符位置:操作系统不知道什么是字符,它们只知道字节。)

FILEHANDLE 可以是一个表达式,其值给出实际的文件句柄的名字或者是一个指向任何类似文件 句柄对象的引用。该函数成功时返回真,失败时返回假。为了方便,该函数可以从各种文件位置 计算偏移量。WHENCE 的值声明你的 OFFSET 使用文件的哪个偏移量做它的开始位置:0,文件 开头;1 文件的当前位置;2,文件结尾。如果 WHENCE 的值是 1 或 2。那么 OFFSET 可以为 负值。如果你喜欢用 WHENCE 的符号值,你可以用来自 IO::Seekable 或者 POSIX 模块的 SEEK_SET,SEEK_CUR,和 SEEK_END,或者在 Perl 5.6 里的 Fcntl 模块。

如果你想为 sysread 或者 syswrite 定位文件,那么不要使用 seek;标准 I/O 缓冲技术会令 seek 对文件在系统位置上的作用变得不可预料而且也不能移植。应该用 sysseek。

因为 ANSI C 的规则和严格,在一些系统上,如果你在在读取和写出之间做切换,那么你必须做 一次搜寻。这样做的效果就好象调用标准 I/O 库的 clearerr(3) 函数。你可以用 WHENCE 为 1 (SEEK_CUR)和 OFFSET 为 0 实现这个目的而又不会移动文件位置:

   seek(TEST, 0, 1);

这个函数的一个有趣的用途是允许你跟随文件的增长,比如:

   for (;;) {
      while () {
         grok($_);         # 处理当前行
      }
      sleep 15;
      seek LOG, 0, 1;         # 重置 end-of-file 错误。
   }

最后一个 seek 在不移动指针的情况下清理文件结束错误。因你的 C 库的标准 I/O 实现的标准 程度的不同而异,你可能需要一些更象下面这样的东西:

   for (;;) {
      for ($curpos = tell FILE;   ;   $curpos = tell FILE) {
         grok($_);         # 处理当前行
      }
      sleep $for_a_while;
      seek FILE, $curpos, 0;      # 重置 end-of-file 错误。
   }

类似的策略可以用于在一个数组里记住 seek 每一行的地址。

29.2.139 seekdir

这个函数为下一次 readdir 对 DIRHANDLE 的调用设置当前位置。POS 必须是 telldir 返回的 值。这个函数与对应的系统库过程在可能的目录压缩问题上有相同的注意事项。该函数可能不是在 所有实现了 readdir 的系统上都实现了。而且如果 readdir 没有实现,它也肯定没实现。

29.2.140 select (输出文件句柄)

由于历史原因,Perl 里有完全互不相关的两个 select 操作符。参阅下一节获取另外一个的描述。 这个版本的 select 操作符返回当前选定的输出操作符,并且,如果你提供了 FILEHANDLE,那么 把它设置为当前缺省输出操作符。这样做有两个效果:首先,一个没有文件句柄的 write 或者 print 将缺省输出到这个 FILEHANDLE。其次,与输出相关的特殊变量将指向这个文件句柄。 比如,如果你想为多个输出文件句柄设置了相同的页顶格式,那么你可能需要做这些:

   select REPORT1;
   $^ = 'MyTop';
   select REPROT2;
   $^ = 'MyTop';

但请注意这样就把 REPORT2 当作当前选定的文件句柄了。这种做法可以说是反社会的做法,因为 它可能会真的把一些其他过程的 print 或者 write 语句搞坏。写的好的库过程会在退出的时候把 当前选定文件句柄设置为和进入过程时相同的那个。为了支持这个,FILEHANLDE 可以是一个 表达式,该表达式的值给出实际文件句柄的名字。因此,你可以用下面的代码保存并恢复当前 选顶的文件句柄:

   my $oldfh = select STDER; $| = 1; select $oldfh;

或者使用惯用的但有点模糊的方法:

   select((select(STDERR), $| = 1)[0])

这个例子是这样运转的:制作一个由 select(STDERR) (副作用是选定了 STDERR)的返回值和 $|=1 (它总是 1)组成的列表,但同时,又作为副作用设置了现在选定的 STDERR 的自动冲刷。 该列表的第一个元素(前面那个选定的文件句柄)现在用做外层 select 的一个参数。古怪吧? 这些足够让你知道 List 比较危险了。

你还可以使用标准的 SelectSaver? 模块在退出范围的时候自动恢复前面一个 select。

不过,虽然我们给你解释了上面的全部东西,我们还是可以指出在当今的情况下,你很少需要使用 这种形式的 select,因为你象设置的大多数特殊变量都有面向对象的封装方法帮你做这些事情。 所以,你不用直接设置 $|,而是:

   use IO::Handle;      # 糟糕的是,这可不是个*小*模块。
   STDOUT->autoflush(1);

而前面的格式化例子可以这样编码:

   use IO::Handle;
   REPORT1->format_top_name("MyTop");
   REPORT2->format_top_name("MyTop");

29.2.141 select (准备文件描述符)

四个参数的 select 和前面那个 select 完全无关。这个操作符用于发现你的文件描述符中那个 (如果有的话)已经准备好做输入或者输出了,或者报告一个例外条件。(这样就避免你做轮询。 )它用你声明的位掩码调用 select(2) 系统调用,你可以用 fileno 和 vec 构造这个位掩码, 象这样:

   $rin = $win = $ein = "";
   vec($rin, fileno(STDIN), 1) = 1;
   vec($win, fileno(STDIN), 1) = 1;
   $ein = $rin | $win;

如果你想在许多文件句柄上 select,你可能会写这样的子过程:

   sub fhbits {
      my @fhlist = @_;
      my $bits;
      for (@fhlist) {
         vec($bits, fileno($_), 1) = 1;
      }
      return $bits;
   }
   $rin = fhbits(qw(STDIN TTY MYSOCK));

如果你想重复使用同样的位掩码(这样做更高效),常用的惯用法是:

   ($nfound, $timeleft) = 
      select($rout=$rin, $wout=$win, $eout=$ein, $timeout);

或者阻塞住直到任意文件描述符准备好:

   $nfound = select($rout=$rin, $wout=$win, $eout=$ein, undef);

如你所见,在标量环境中调用 select 只返回 $nfound,找到的准备好的文件描述符数量。

可以用 $wout=$win 技巧是因为一个赋值语句的数值是它自身的左边,因此 $wout 先被赋值删除, 然后又被 select 删除,而 $win 不会变化。

这些参数任何一个都可以是 undef,这个时候它们被忽略。如果 TIMEOUT 不是 undef,那么就是 以秒计,而且可以是分数。(超时时间为 0 的效果就是轮询。)不是所有实现都能返回 $timeleft。如果不能返回 $timeleft,那么它总是返回等于你提供的 $timeout 的 $timeleft。

标准的 IO::Select 模块提供了 select 的更加友善的接口,主要是因为它为你做了所有你能用的 位掩码。

select 的一个应用就是实现比 sleep 分辨率更好的睡眠。要实现这个目的,声明所有位映射为 undef。因此,如果要睡眠(至少)4.75 秒钟,用:

   select undef, undef, undef, 4.75;

(在一些非 Unix 系统上,三个 undef 的形式可能不能用,你可能需要为一个有效的描述符至少 伪装一个位掩码,而那个描述符可以是从来准备不好的。)

我们不应该把缓冲的 I/O(比如 read 或 )和 select 混在一起用,除了 POSIX 允许 的以外,而且就算 POSIX 允许,也只能在真正的 POSIX 系统上使用。这时应该用 sysread。

29.2.142 semctl

这个函数调用 System V IPC 函数 semctl(2)。你可能得先说 use IPC::SysV 以获取正确的 常量定义。如果 CMD 是 IPC_STAT 或者 GETALL,那么 ARG 必须是一个它可以保存返回的 semid_ds 结构或信号灯数值数组的变量。和 ioctl 和 fcntl 一样,返回值用 undef 代表 错误,“0 but true”代表零,其他情况则返回实际数值。

又见 IPC::Semaphore 模块。这个函数只有在那些支持 System V IPC 的机器上才能用。

29.2.143 semget

这个函数调用 System V IPC 系统调用 semget(2)。在调用之前,你应该 use IPC::SysV 以 获取正确的常量定义。该函数返回信号灯 ID,或者如果有错误返回 undef。

又见 IPC::Semaphore 模块。这个函数只能在那些支持 System V IPC 的机器上用。

29.2.144 semop

这个函数调用 System V IPC 系统调用 semop(2) 以执行信号灯操作,比如发信号和等待等等。 在调用之前,你应该使用 use IPC::SysV 以获取正确的常量定义。

OPSTRING 必须是一个 semop 结构的打包的数组。你可以通过说 pack("s*", $semnum, $semop, $semflag)做每一个 semop 结构。信号灯操作的数量是由 OPSTRING 的长度隐含的。该函数在 成功的时候返回真,在失败的时候返回假。

下面的代码在等待信号灯 id 为 $semid 的信号灯 $semnum:

   $semop = pack "s*", $semnum, -1, 0;
   semop $semid, $semop or die "Semaphore trouble: $!\n";

要给信号灯发出信号,只需要把 -1 换成 1 就可以了。

参阅第十六章的“System V IPC”一节。又见 IPC::Semaphore 模块。这个函数只有支持 Systerm V IPC 的机器上可以用。

29.2.145 send

这个函数在套接字上发送一条信息。它和同名系统调用接收相同的标志——参阅 send(2)。在 未联接的套接字上,你必须声明一个要发送的目的 TO,这样就会令 Perl 的 send 象 sendto(2) 那样运行。C 的系统调用 sendmsg(2) 目前没有在标准的 Perl 里实现。send 函数 在成功时返回发送的字节数,失败时返回 undef。

(有些非 Unix 系统错误地把套接字当作与普通文件描述符不同的东西对待,结果就是你必须总是 在套接字上 send 和 recv,而不能使用方便的标准 I/O 操作符。)

我们中至少有一个人会常犯的错误就是把 Perl 的 send 和 C 的 send 和写混淆起来:

   send SOCK, $buffer, length $buffer      # 错

这行代码会莫名其妙地失败,具体情况取决于字串长度和系统需要的 FLAG 位之间的关系。参阅 第十六章中的“消息传递”一节。

29.2.146. setpgrp

这个函数为指定的 PID(对当前进程使用 PID 等于 0)设置当前进程组(PGRP)。如果在那些 没有实现 setpgrp(2) 的系统上调用 setpgrp 将会抛出一个例外。注意:有些系统上会总是忽略 你提供的参数并总是做 setpgrp(0, $$)。幸运的是,这些就是我们最常用的参数。如果省略了 参数,它们缺省是 0, 0。BSD 4.2 版本的 setpgrp 不接受任何参数,但在 BSD 4.4 里,它是 setpgid 函数的同义词。如果需要更好的移植性(从某种角度来看),直接使用 POSIX 模块里的 setpgid 函数。如果你实际上想干的事是把你的脚本作成守护进程,那么请考虑使用 POSIX::setsid() 函数。请注意 POSIX 版本的 getpgrp 并不接受参数,所以只有 setpgrp(0, 0) 是真正可以移植的。

29.2.147. setpriority

这个函数为 WHICH 和 WHO 里声明的一个进程,进程组,或者一个用户设置当前 PRIORITY,参阅 setpriority(2)。在那些没有实现 setpriority(2) 的机器上调用 setpriority 将抛出一个 例外。要把你的程序“nice”下四个单位(和用 nice(1) 处理你的程序一样),用:

   setpriority 0, 0, getpriority(0, 0) + 4;

一个给定的优先级的解释可能会因不同的系统而异。有些权限可能是那些非特权用户所不能使用的。

又见 CPAN 的 BSD::Resource 模块。

29.2.148. setsockopt

这个函数设置你需要的套接字选项。出错时该函数返回 undef。LEVEL 表示你的调用瞄准的是 哪一个协议层。或者就是 SOL_SOCKET,指向在所有层之上的套接字本身。如果你不想传递参数, 那么可以把 OPTVAL 声明为 undef。在套接字上一个常用的选项是 SO_REUSEADDR,这样才能绕开 因为前一个在该端口的 TCP 联接仍然认为固执地认为它在关闭的时候,我们不能绑定特定的地址的 问题。它看起来象这样:

   use Socket;
   socket(SOCK, ...) or die "Can't make socket: $!\n";
   setsocket(SOCK, SOL_SOCKET, SO_REUSEADDR, 1)
      or warn "Can't do setdosockotp: $!\n";

参阅 setsockopt(2) 获取其他可能数值。

29.2.149 shift

这个函数把数组的第一个值移出并且返回它,然后把数组长度减一并且把所有的东西都顺移。 如果在数组中不再存在元素,它返回 undef。

如果省略了 ARRAY,那么该函数在子过程和格式的词法范围里移动 @_;它在文件范围(通常是主程 序)或者在由 eval STRING,BEGIN { },CHECK { },INIT { },和 END {} 这样的构造里面的 词法范围里移动 @ARGV。

子过程通常以拷贝它们的参数到词法变量里开始,而 shift 可以用于这个目的:

   sub marine {
      my $fathoms = shift;      # 深度
      my $fishies   = shift;   # 鱼的数量
      my $o2           = shift;   # 氧气问题
      # ...
   }

shift 还可以用于在你的程序前面处理参数:

while (defined($_ = shift)) {
        /^[^-]/     && do { unshift @ARGV, $_; last };
        /^-w/       && do { $WARN = 1;         next };
        /^-r/       && do { $RECURSE = 1;      next };
        die "Unknown argument $_\n";
}

你还可以考虑使用 Getopt::Std 和 Getopt::Long 模块来处理程序参数。

又见 unshift,push,pop,和 splice。shift 和 unshift 函数在数组左边做的事情和 pop 和 push 在数组右边干的事情是一样的。

29.2.150 shmctl

这个函数调用 System V IPC 系统调用 shmctl(2)。在调用之前,你应该 use IPC::SysV 以 获取正确的常量定义。

如果 CMD 是 IPC_STAT,那么 ARG 必须是一个将要保存返回的 shmid_ds 结构的变量。跟 ioctl 和 fcntl 一样,该函数错误时返回 undef,“0 but true”表示零,其他情况下返回 实际返回值。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.151 shmget

这个函数调用 System V IPC 系统调用 shmget(2)。该函数返回共享内存段的 ID,如果有错误 则返回 undef。在调用之前,先 use SysV?::IPC。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.152 shmread

这个函数从共享内存段 ID 的位置 POS 处开始读取 SIZE 大小的数据(方法是附着在该内存段 上,拷贝出数据,然后与该内存段分离。)。VAR 必须是一个将保存读取出的数据的变量。如果 成功,该函数返回真,如果失败返回假。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.153 shmwrite

这个函数向共享内存段 ID 的位置 POS 处开始写入 SIZE 大小的数据(方法是附着在该内存段 上,拷贝入数据,然后与该内存段分离。)。如果 STRING 太长,那么只写入 SIZE 字节;如果 STRING 太短,那么在后面补空直到 SIZE 字节。如果成功,该函数返回真,如果有错误,返回假。

该函数只能在那些支持 System V IPC 的机器上用。(你可能都读烦了——我们已经写烦了。)

29.2.154 shutdown

这个函数以 HOW 声明的方式关闭一个套接字联接。如果 HOW 为 0,那么不再允许进一步的接收。 如果 HOW 为 1,那么不再允许进一步的发送。如果 HOW 为 2,那么任何事情都不允许。

   
   shutdown(SOCK, 0);   # 不许再读
   shutdown(SOCK, 1);   # 不许再写
   shutdown(SOCK, 2);   # 不许再 I/O

如果你想告诉对端你完成数据写出了,但还没有完成数据读取,或者反过来,在这些情况下它都 非常有用。而且它还是一种更执着的关闭方式,因为同时还关闭任何这些文件描述符在派生出的 进程中的的拷贝。

让我们想象有一个服务器想读取它的客户端的请求,直到文件结尾,然后发送一个回答。如果 客户端调用 close,那么该套接字现在将不能用于 I/O,因此不会有回答能送回来。因此,客户端 应该使用 shutdown 以半关闭这次联接:

   print SERVER "my request\n";   # 发送一些数据
   shutdown(SERVER, 1);         # 发送完毕,没有更多要发的东西了
   $answer = ;         # 但你还可以读

(如果你找到这里是为了找到关闭你的系统的办法,那么你就要执行一个外部的程序干这件事。 参阅 system。)

29.2.155. sin

抱歉,这个操作符什么罪都没犯(译注:英文“sin”也有“罪恶”的含义)。它只是返回 EXPR (用弧度表示)的正弦。

如果需要正弦的逆操作,你可以使用 Math::Trig 或者 POSIX 模块的 asin 函数,或者用下面的 关系:

   sub asin { atan2($_[0], sqrt(1 - $_[0] * $_[0])) }

29.2.156 sleep

这个函数令脚本睡眠 EXPR 秒,如果没有 EXPR 则是永久睡眠,并且返回睡眠的秒数。 你可以 通过给该进程发送一个 SIGALRM 的方法来中断睡眠。在一些老式系统里,它可能比你要求的描述 整整少睡一秒,具体情况取决于它是如何计算秒的。大多数现代的系统都是睡足秒数。不过,在 这些系统上它们很有可能睡眠的时间要长一些,因为在一台繁忙的多任务系统上,你的系统可能 无法马上得到调度。如果可能,select (等待文件描述符)调用可以给你更好的分辨率。你还 可以用 syscall 调用一些 Unix 系统支持的 getitimer(2) 和 setitimer(2) 过程。你不应该 混合 alarm 和 sleep 调用,因为 sleep 通常是用 alarm 实现的。

又见 POSIXE 模块的 sigpause 函数。

29.2.157 socket

这个函数打开一个指定类型的套接字,并且把它附着在 SOCKET 文件句柄上。DOMAIN,TYPE,和 PROTOCOL 都是和 socket(2) 一样的声明。如果没有定义 SOCKET,那么 Perl 将自动激活它。 在使用这个函数之前,你的程序应该包含下面这行:

   use Socket;

它给你正确的常量。该函数成功时返回真,参阅在第十六章里的“套接字”节里的例子。

在那些支持对文件的 exec 时关闭(close-on-exec)的系统上,该标记将为新打开的文件描述符 设置,就象 $^F 判定的那样。参阅第二十八章里的 $^F($SYSTEM_FD_MAX)。

29.2.158 socketpair

这个函数在声明的域中创建一个指定类型的匿名套接字对。DOMAIN,TYPE,和 PROTOCOL 都和 socketpair(2) 里声明的一样。如果两个套接字参数都没有声明,那么它们自动激活。该函数成功 时返回真,失败时返回假。在那些没有实现 socketpair(2) 的系统上,调用这个函数会抛出一个 例外。

这个函数的通常用法是在 fork 之前使用。生成的进程中有一个关闭 SOCKET1,而另外一个关闭 SOCKET2。你可以双向使用这些套接字,而不象 pipe 函数创建的文件句柄那样是单向的。有些 系统用 socketpair 的方式定义 pipe,这时候调用 pipe(Rdr, Wtr) 相当于:

   use Socket;
   socketpair(Rdr, Wtr, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
   shutdown(Rdr, 1);      # 不允许读者写
   shutdown(Wtr, 0);      # 不允许写者读

在那些支持对文件的 exec 时关闭(close-on-exec)的系统上,该标记将为新打开的文件描述符 设置,就象 $^F 判定的那样。参阅第二十八章里的 $^F($SYSTEM_FD_MAX)。又见在第十六章里 的“双向通讯”一节尾部的例子。

29.2.159 sort

这个函数对 LIST 进行排序并返回排好序的列表值。缺省时,它以标准字串比较顺序排序(未定义 数值排在已定义空字串前面,而空字串又在其他任何东西前面)。如果 use locale 用法起作用, 那么 sort LIST 根据当前的区域集的数值对 LIST 排序。

如果给出了 USERSUB,那么它就是一个返回小于,等于,或者大于 0 的整数的子过程名字,具体 返回什么取决于列表中的元素应该如何排序。(很便利的 <=> 和 cmp 操作符可以用于执行三向 数字和字串比较。)如果给出了 USERSUB,但该函数未定义,那么 sort 抛出一个例外。

为了提高效率,绕开了通常用于子过程的调用代码,这样就有了下面的结果:这个子过程不能是 递归子过程(你也不能用一个循环控制操作符退出该块或者过程),并且将要接受比较的两个元素 不是通过 @_ 传递进子过程的,而是通过临时设置 sort 编译所在的包的全局变量 $a 和 $b( 参阅后面的例子)。变量 $a 和 $b 是真实值的别名,所以不要在子过程中修改它们。

子过程要求的动作是比较。如果它返回的结果是不一致的(比如,有时候说 $x[1] 小于 $x[2], 而有时候说的正相反),那么结果就不会良好。(这也是你不能修改 $a 和 $b 的另外一个原因。)

USERSUB 可以是标量变量名字(未代换),这时,它的值要么是引用实际子过程的符号引用,要么 是硬引用。(符号名更好些,即使用了 use strict 'refs' 用法也如此。)在 USERSUB 的 位置,你可以提供一个 BLOCK 用做一个匿名内联排序子过程。

要做一次普通的数字排序,你可以说:

   sub numerically { $a <=> $b }
   @sortedbynumber = sort numerically 53, 29,11,32, 7;

要以降序排序,你可以简单地在 sort 后面应用 reverse,或者你可以在排序过程里把 $a 和 $b 反过来:

sub numerically { $a <=> $b }
@sortedbynumber = sort numerically 53,29,11,32,7;

   @descending = reverse sort numerically 53,29,11,32,7;

   sub reverse_numerically { $b <=> $a }
   @descending = sort reverse_numerically 53,29,11,32,7;

要对字串进行大小写不敏感的排序,在比较之前用 lc 处理 $a 和 $b:

    @unsorted = qw/sparrow Ostrich LARK catbird blueJAY/;
    @sorted = sort { lc($a) cmp lc($b) } @unsorted;

(在 Unicode 里,用 lc 做大小写规范化要比用 uc 好,因为有些语言里抬头体和大写是不一样 的。不过它对普通的 ASCII 排序没有什么影响,并且如果你想让 Unicode 能正确排序,那么你的 规范化过程可能要比 lc 更别致一些。)

对散列按照数值排序是 sort 函数的常用法之一。比如,如果 %sales_amount 散列记录部门销售 情况,那么在排序过程里做一次散列查找就可以让我们将散列键字根据它们的数值排序:

# 从销售额最高的部门到最低的部门

    sub bysales { $sales_amount{$b} <=> $sales_amount{$a} }

    for $dept (sort bysales keys %sale_amount) {
        print "$dept => $sales_amount{$dept}\n";
    }

你可以通过使用 || 或者 or 操作符级连多个比较的方法进行额外层次的排序。这种方法相当漂亮, 因为比较操作符通常在相等的时候返回 0,这样就令它们能落到下一个比较。下面,散列键字首先 根据它们相关的销售额排序,然后在根据键字本身进行排序(以处理有两个或多个部门销售额 相同的情况):

   sub by_sales_then_dept {
      $sales_amount{$b} <=> $sales_amount{$a}
         ||
      $a cmp $b
   }

   for $dept (sort by_sales_then_dept keys %sale_amount) {
      print "$dept => $sales_smount{$dept}\n";
   }

假设 @recs 是一个散列引用的数组,而这里每个散列包含象 FIRSTNAME,LASTNAME,AGE, HEIGHT,和 SALARY 这样的域。下面的过程把那些记录中的人们按照下面的顺序排列:先是财富, 然后是身高,然后是年龄(越小越靠前),最后是名字的字母顺序:

sub prospects {
    $b->{SALARY}    <=>  $a->{SALARY}
        ||
    $b->{HEIGHT}    <=>  $a->{HEIGHT}
        ||
    $a->{AGE}       <=>  $b->{AGE}
        ||
    $a->{LASTNAME}  cmp  $b->{LASTNAME}
        ||
    $a->{FIRSTNAME} cmp  $b->{FIRSTNAME}
}

@sorted = sort prospects @recs;

任何可以从 $a 和 $b 中得到的有用信息都可以在一个排序过程中比较的基础来用。比如,如果 多行文本要根据特定域来排序,那么可以在排序过程中使用 split 以获取该域:

   @sorted_lines = sort {
      @a_fields = split /:/, $a;      # 冒号分隔的域
      @b_fields = split /:/, $b;
         
      $a_fields[3] <=> $b_fields[3]   # 对第四个域进行能够数字排序,然后
         ||
      $a_fields[0] cmp $b_fields[0]   # 对第一个域进行能够字串排序,然后
         ||
      $b_fields[2] <=> $a_fields[2]   # 对第而个域进行行能够数字反向排序
      ...            # 等等
   } @lines;

不过,因为 sort 使用给 $a 和 $b 的不同的数值对多次运行排序过程,所以前面的例子将会比对 每一行都做多余的重新分裂。

为了避免发生象为了比较数据域导致的多次的行分裂带来的开销,我们可以在排序之前对每个值进行 一次操作,然后把生成的信息保存起来。下面,我们创建了一个匿名数组以捕获每一行以及该行的 分裂结果:

   @temp = map { [$_, split /:/] } @lines;

然后,我们对数组引用排序:

   @temp = sort {
      @a_fields = @$a[1..$#$a];
      @b_fields = @$b[1..$#$b];

      $a_fields[3] <=> $b_fields[3]   # 对第四个域进行能够数字排序,然后
         ||
      $a_fields[0] cmp $b_fields[0]   # 对第一个域进行能够字串排序,然后
         ||
      $b_fields[2] <=> $a_fields[2]   # 对第而个域进行行能够数字反向排序
      ...               # 等等
   } @temp;

在这个数组引用排完序之后,我们就可以从这个匿名数组里检索原始行了:

   @sorted_lines = map {$_->[0] } @temp;

概而括之,这个 map-sort-map 技巧,就是我们通常称之为 Schwartzian 变换的东西,可以用 一个语句实现:

   @sorted_lines = map { $_->[0] }
         sort {
            @a_fields = @$a[1..$#$a];
            @b_fields = @$b[1..$#$b];

            $a_fields[3] <=> $b_fields[3]
               ||
            $a_fields[0] <=> $b_fields[0]
               ||
            $b_fields[2] <=> $b_fields[2]
            ...
         }
         map { [$_, split /:/]} @lines;

不要把 $a 和 $b 定义成词法变量(用 my)。它们都是包全局变量(如果它们可以免于 use strict 对普通全局变量的限制)。不过你的确需要保证你的排序过程是在同一个包里的, 或者用调用者的包名字修饰 $a 和 $b。

我们已经说过,在 Perl 5.6 里你可以用标准的参数传递方法(以及不一样的是,用 XS 子过程做 排序子过程)写排序子过程,前提是你用一个 ($$) 的原型声明了这个排序子过程。并且如果你是 这么用的,那么实际上你是可以把 $a 和 $b 声明为词法变量的:

   sub numerically ($$) {
      my ($a, $b) = @_;
      $a <=> $b;
   }

将来,当完整的原型都实现了以后,你就可以只用说:

      
   sub numerically ($a, $b) { $a <=> $b}

然后我们或多或少就能回到开始的地方。

29.2.160 splice

这个函数从一个 ARRAY 中删除 OFFSET 和 LENGTH 指明的元素,并且,如果给出了LIST,则用 LIST 的元素替换它。如果 OFFSET 是负数,那么该函数从数组的后面向前数,但如果该值会伸到 数组开头的前面,那么就会抛出一个例外。在列表环境中,splice 返回从该数组中删除的元素。 在标量环境中,它返回最后删除的元素,而如果没有的话返回 undef。如果新元素的数量不等于 旧元素的数量,那么该数组根据需要伸缩,并且元素的位置根据衔接后的情况进行改变。如果省略 了 LENGTH,那么该函数从数组里删除从 OFFSET 开始的所有东西。如果省略了 OFFSET,那么该 数组在读取的时候清空。下面的等式成立(假设 $[ 为 0):

直接方法 splice 等效
push(@a, $x, $y) splice(@a, @a, 0, $x, $y)
pop(@a) splice(@a, -1)
shift(@a) splice(@a, 0, 1)
unshift(@a, $x, $y) splice(@a, 0, 0, $x, $y)
$a[$x] = $y splice(@a, $x, 1, $y)
(@a, @a = ()) splice(@a)

splice 函数还可以方便地用于切开传递给子过程的参数列表。比如,假设列表长度在列表之前传递:

   sub list_eq {            # 比较两个列表值
      my @a = splice(@_, 0, shift);
      my @b = splice(@_, 0, shift);
      return 0 unless @a == @b;   #  长度相同?
      while(@a) {
         return 0 if pop(@a) ne pop(@b);
      }
      return 1;
   }
   if (list_eq($len, @foo[1..$len], scalar(@bar), @bar)) { ... }

不过,拿数组引用来干这事更清晰一些。

29.2.161 spit

这个函数扫描字串中 EXPR 给出的分隔符,并且把该字串劈成一个子字串列表,在列表环境中返回 生成的列表值,或者在标量环境中返回子字串的数量。(注:标量环境同时还令 split 把它的 结果写到 @_,不过这个用法现在废弃了。)分隔符是用重复的模式匹配进行判断的,用的是 PATTERN 里给出的正则表达式,因此分隔符可以是任意大小,并且不一定在每次匹配都是一样的 字串。(分隔符不象平常那样返回;我们在本节稍后讨论例外情况。)如果 PATTERN 完全不能匹配 该字串,那么 split 把原始字串当作子字串返回。如果它匹配了一次,那么你就得到两个子字串, 以此类推。你可以在 PATTERN 里使用正则表达式修饰词,比如 /PATTERN/i,/PATTERN/x,等等。 如果你以模式 /^/ 进行分裂,那么就假设是 //m 修饰词。

如果声明了 LIMIT 并且是正的,该函数分裂成不超过那么多的域(当然如果它用光了分隔符,那么 是可以分裂成比较少的子字串的)。如果 LIMIT 是负数,那就把它当作声明了任意大的 LIMIT。 如果省略了 LIMIT 或者是零,那么将从结果中删除结尾的空域(那些潜在的 pop 用户应该好好 记住)。如果省略了 EXPR,那么该函数就分裂 $_ 字串。如果还省略了 PATTERN 或者它是一个 文本空格,“ ”,那么该函数对空格进行操作,/\s+/,但是忽任何开头的空格。

可以分裂任意长度的字串:

   @chars  = split //,    $word;
   @fields  = split /:/,   $line;
   @words = split " ",   $paragraph;
   @lines = split /^/,   $buffer;

一个可以匹配空串或者其他的一些比空串长的字串的模式(比如,一个由任意一个字符加上 * 或者 ? 修饰的模式)将把 EXPR 的值分裂成独立的字符,只要它匹配字符之间的空串;非空匹配会象 通常的情况那样忽略匹配过的分隔符字符。(换句话来说,一个模式不会在一个点匹配多过一次, 即使它和一个零宽匹配也如此。)比如:

   print join ':', split / */, 'hi there';

生成输出“h:i:t:h:e:r:e”。空白消失了是因为它作为分隔符一部分匹配。举一个小例子,空模式 // 简单 地分裂成独立的字符,而空格并不消失。(对于正常模式匹配而言,// 模式会在上一次成功匹配处重复, 但是 split 的模式免受此过。)

LIMIT 参数只分裂字串的一部分:

   ($login, $passwd, $remainder) = split /:/, $_, 3;

我们鼓励你把你的字串分裂成这样的列表名字,这样你的代码就有了自文档的特性。(可以用于 出错检查,请注意如果字串里比三个域少,那么 $remainder 将会是未定义。)当给一个列表赋值 的时候,如果省略了 LIMIT,那么 Perl 提供一个 LIMIT,其数值比列表中的变量数量大一,以此 避免不必要的工作。对于上面的分裂,LIMIT 缺省时是 4,而 $remainder 将只收到第三个域,而 不是所有剩下的域。在时间要求很严格的应用里,避免分裂成比我们需要的更多的域是一个好习惯。 (强大的语言的问题就是,它给你强大的功能的是以花费在时间上的愚蠢为代价的。)

我们早先说过分隔符不会被返回,但是如果 PATTERN 包含圆括弧,那么每一对圆括弧匹配的子字串 都会包括在结果列表中,分散在那些平常返回的域之中。下面是一个简单的例子:

   split /([-,])/, "1-10,20";

生成列表:

   (1, '-', 10, ',', 20)

如果有更多圆括弧,那么为每个圆括弧对返回一个域,即使有些圆括弧对没有匹配也如此,这种情况 下,为那些位置返回未定义数值。因此,如果你说:

   split /(-)|(,)/, "1-10,20";

那么结果是:

   (1, '-', undef, 10, undef, 20);

/PATTERN 参数的位置可以放这么一个表达式,该声明在运行时生成不同的模式。和普通模式一样, 如果想只做一次运行时编译,那么用 /$varable/o。

有一个特殊的情况,如果该表达式是一个空格(“ ”),那么该函数会象没有参数的 split 那样 在空格上把字串分裂开。因此 split(" ") 可以用于模拟 awk 的缺省行为。相反,split(/ /) 将给你和前导空格一样多的空的初始化域。(除了这个特殊的例子以外,如果你提供的是一个字串 而不是一个正则表达式,那么它还是会被解释成一个正则表达式。)你可以用这个属性把开头和 结尾的空白删除,并且把中间的空白都压缩成一个空白:

   $string = join(' ', split(' ', $string));

下面的例子把一个 RFC 822 消息头分裂成一个包含 $head{Date},$head{Subject},等等的 散列。它使用了给一个散列赋予一个配对列表的技巧,理由是域和分隔符交错。它利用圆括弧把 每个分隔符的一部分当作返回列表值的一部分返回。因为 split 模式保证把返回的东西利用包含 圆括弧的好处按照配对的形式返回,所以散列赋值就可以保证收到一个包含键字/数值对的列表, 这里每个键字就是一个头域的名字。(糟糕的是,这个技巧会丢失有着相同域的多个行的信息, 比如 Received-By 行。啊,哦...)

   $header =~ s/\n\s+/ /g;      # 融合连续行
   %head = ('FRONTSTUFF', split /^(\S*?):\s*/m, $header);

下面的例子处理整个 Unix passwd(5) 文件。你可以忽略 chomp,这个时候 $shell 的结尾将有 换行符。

   open PASSWD, '/etc/passwd';
   while () {
      chomp;      # 删除结尾的换行符
      ($login, $passwd, $uid, $gid, $gcos, $home, $shell) = 
         split /:/;
      ...
   }

下面是一个如何处理每个输入文件里的每一行中的每个词,创建一个单词频率散列的例子:

   while (<>) {
      foreach $word (split) {
         $count{$word}++;
      }
   }

split 的逆操作由 join 执行(只不过 join 只能在所有域之间用同样的分隔符连接)。要用固定 位置的域分解字串,请使用 unpack。

29.2.162. sprintf

这个函数返回一个格式化字串,格式化习惯是 C 的库函数 sprintf 的是 printf 习惯。参阅你的 系统的 sprintf(3) 或 printf (3) 获取一些通用原则的解释。FORMAT 包含一个带有嵌入的域 指示符的文本,LIST 里的元素就是逐一替换到这些域中去的。

Perl 做自己的 sprintf 格式化——它模拟 C 函数 sprintf,但是它没有用 C 的 sprintf。 (注:除了浮点数以外,并且就算是浮点数也只允许标准的修饰词。)结果是,任何你本地的 sprintf(3) 函数的扩展都不能在 Perl 里使用。

Perl 的 sprintf 允许全局使用的已知转化在 表29-4 中列出。

表29-4。sprintf 的格式

含义
%% 一个百分号
%c 一个带有给定数字的字符
%s 一个字串
%d 一个有符号整数,十进制
%u 一个无符号整数,十进制
%o 一个无符号整数,八进制
%x 一个无符号整数,十六进制
%e 一个浮点数,科学记数法表示
%f 一个浮点数,用固定的小数点表示
%g 一个浮点数,以 %e 或 %f 表示

另外,Perl 允许下列广泛支持的转换:

含义
%x 类似 %x,但使用大写字符
%E 类似 %e,但使用大写的“E”
%G 类似 %g,但是带一个大写的“E”(如果正确的话)
%b 一个无符号整数,二进制
%p 一个指针(输出十六进制的 Perl 值的地址)
%n 特殊:把到目前为止输出的字符数放到参数列表中的下一个变量里

最后,为了向下兼容(我们的意思就是“向下”),Perl 允许下列不必要的但广泛支持的转换:

含义
%i %d 的同义词
%D %ld 的同义词
%U %lu 的同义词
%O %lo 的同义词
%F %f 的同义词

Perl 允许下列众所周知的标志出现在 % 和转换字符之间:

域 | 含义|

space 用空格前缀正数
+ 用加号前缀正数
- 在域内左对齐
- 用零而不是空格进行右对齐
# 给非零八进制前缀“0”,给非零十六进制前缀“0x”
number 最小域宽度
.number “精度”:浮点数的小数点后面的位数字串最大长度。整数最小长度
l | | 把整数解释成 C 类型的 long 或者 unsigned long|
h 把整数解释成 C 类型的 short 或者 unsigned short(如果没有提供标志,那么把整数解释成 C 类型 int 或者 unsigned)

还有两个 Perl 相关的标志

含义
V 把整数解释成 Perl 标准的整数类型
v 把字串解释成一个整数向量,输出成用点分隔的数字,或者是用任意参数列表里前面带 * 的字串分隔

如果你的 Perl 理解“四倍数”(64位整数),不管是该平台本机支持还是因为你指明 Perl 带着 该功能编译,那么字符 d u o x X b i D U O 打印64位整数,并且它们前面可以选择前缀 ll, L,或则 q。比如,%lld %16LX %qo。

如果 Perl 理解“long double”(要求该平台支持 long double),那么你可以在 e f g E F G 标志前面增加可选的 ll 或者 L。比如,%llf %Lg。

在标志里可以出现数字的位置,都可以用一个星号(“*”)代替,这时候 Perl 使用参数列表里的 下一个项作为给出的数字(也就是说,当作域宽度或者精度)。如果通过“*”获取的域宽度是负数, 那么它和“-”标志有一样的效果:左对齐。

v 标志可以用于显示任意字串里的序数值:

   sprintf "version is v%vd\n", $^V;      # Perl 的版本
   sprintf "address is %vd\n", %addr;   # IPv4 地址
   sprintf "address is %*vX\n", ":", $addr;   # IPv6 地址
   sprintf "bits are %*vb\n", " ", $bits;   # 随机的位串

29.2.163 sqrt

这个函数返回 EXPR 的平方根。如果需要其他的根,比如立方根,你可以使用 ** 操作符求那个 数字的分数幂。不要试图在着两种方法里使用负数,因为它有一些稍微有些复杂的问题(并且抛出 一个例外)。但是有一个模块可以处理这些事情:

   use Main::Complex;
   print sqrt(-2);      # 打印出 1.4142135623731i

29.2.164. srand

这个函数为 rand 操作符设置随机数种子。如果省略了 EXPR,那么它使用一个内核提供的半随机的 数值(如果内核支持 /dev/urandom 设备)或者是一个基于当前时间和进程号以及一些其他东西的 数值。通常我们完全没有必要调用 srand,因为如果你没有明确调用它,那么它也会在第一次调用 rand 操作符时隐含调用。不过,在早于 Perl 5.004 的版本里不是这样的,所以如果你的脚本 需要在老 Perl 版本上运行,那么你就应该调用 srand。

那些经常被调用的程序(比如 CGI 脚本),如果只是简单地用 time ^ $$ 做种子的话,那么很 容易惨遭下面的数学性质的攻击,那就是:有三分之一的机会 a^b == (a+1)^(b+1)。所以不要 这么干。应该用下面的代码:

   srand( time() ^ ($$ + ($$ << 15)) );

如果用于加密目的,那么你需要用比缺省的种子生成更随机的算法。有些系统上有 /dev/random 设备就比较合适,否则,拿一个或多个会迅速改变操作系统状态的程序的输出,压缩以后进行 校验和计算是常用的方法。比如:

   srand (time ^ $$ ^ unpack "%32L*", `ps wwaxl | gzip`);

如果你特别关心这些问题,那么请参阅 CPAN 上的 Math::TrulyRandom 模块。

不要在你的程序里多次调用 srand,除非你知道你在干什么并且知道为什么这么做。这个函数的 目的是给 rand 函数种子,这样 rand 就可以在你每次运行你的程序的时候生成不同的序列。只 需要在你的程序开始做一次,否则你就不能从 rand 中获得随机的数值!

29.2.165. stat

在标量环境里,这个函数返回一个布尔值,该值表示调用是否成功。在列表环境里,它返回一个 13 个元素的列表,给出一个文件的统计信息,该文件要么是通过 FILEHANDLE 打开,要么是用 EXPR 命名。它的典型应用如下:

   ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
      $atime, $mtime, $ctime, $blksize, $blocks)
         = stat $filename;

不是在所有文件系统类型上都支持这些域;不支持的域返回 0。表 29-5 列出了各个域的含义。

表 29-5。stat 返回的域


索引 含义
0 $dev 文件系统的设备号
1 $ino i 节点号码
2 $mode 文件模式(类型和权限)
3 $nlink 指向该文件的(硬)链接数量
4 $uid 文件所有者的数字用户 ID
5 $gid 文件所属组的数字组 ID
6 $rdev 设备标识符(只用于特殊文件)
7 $size 文件的总尺寸,以字节计
8 $atime 自纪元以来以秒计的上一次访问时间
9 $mtime 自纪元以来以秒计的上一次修改时间
10 $ctime 自纪元以来以秒计的上一次i节点改变的时间(不是创建时间!)
11 $blksize 选定的用于文件系统 I/O 的块尺寸
12 $blocks 实际分配的块数量

$dev 和 $ino 放在一起,在同一个系统里唯一地标识一个文件。$blksize 和 $blocks 很可能 只在 BSD 衍生出的文件系统里有。如果有 $block 域,那么它是以 512 字节的块汇报的。 $blocks*512 的值可能和 $size 差距相当大,因为有些文件包含未分配的块,或者说“洞”, 它们没有在 $blocks 中计算。

如果传递给 stat 一个特殊的文件句柄,该句柄里包含下划线,那么就不会做实际的 stat(2) 调用,而是返回上一次 stat,lstat,或者基于 stat 的文件测试操作符(比如 -r,-w,和 -x) 的 stat 结构。

因为模式包含文件类型及其权限,所以如果你想看真正的权限,那么你应该屏蔽掉文件类型部分, 并且在 printf 或者 sprintf 里用“%o”:

   $mode = (stat($filehane))[2];
   printf "Permissions are %04o\n", $mode &07777;

File::stat 模块提供一个方便的通过名字访问的机制:

   use File:;stat;
   $sb = stat($filename);
   printf "File is %s, size is %s, perm %04o, mtime %s\n",
      $filename, $sb->size, $sb->mode & 07777,
      scalar localtime $sb->mtime;

你还可以从 Fcntl 模块里输入各种不同模式位的符号定义。参阅联机文档获取更多细节。

提示:如果你只需要文件尺寸,那么可以用 -s 文件测试操作符,它直接返回以字节计的文件大小。 另外还有返回以天计的文件年龄的文件测试。

29.2.166. study

这个函数花了一些额外的时间用在研究 SCALAR 上,预测在进行下一次修改之前要做的匹配次数。 这么做也许能但也许不能节约时间,具体情况取决于你正在搜索的模式的天性和数量,以及待搜索 字串中字符出现频率的分布——你可能需要比较有它和没它的运行时间来看看哪个运行得快一些。 那些扫描许多常量字串(包括在更复杂的模式里的常量部分)的循环会从 study 中得到最大好处。 如果你的所有模式匹配都是前面有锚符号的常量字串,那么 study 一点忙都帮不上,因为那里没有 扫描。你每次只能拥有一个 study——如果你研究另外一个标量的话,那么前面那个就“没有研究” 了。

study 运转的方法是:做一个包含待搜索字串中的每个字符的链表,因此,打个比方,我们就知道了 所有的“k”字符的位置。从每个搜索字串里选出最少见的字符,这是基于从一些 C 程序和英文文本 构造出来的频率统计表的。只对那些包含这个最少见字符的位置进行检查。

比如,下面是一个循环,它在每个包含特定模式的行前面插入一个生成索引的记录:

   while(<>) {
      study;
      print ".IX foo\n"      if /\bfoo\b/;
      print ".IX bar\n"      if /\bbar\b/;
      print ".IX blurfl\n"      if /\bblurfl\b/;
      ...
      print;
   }

为了搜索 /\bfoo\b/,只查看 $_ 里的那些包含“f”的位置,因为“f”比“o”少见。除了在 病态的情况下,这样做是很有优势的。唯一的问题是它是否能节约比先制作链表花的时间更多的 时间。

如果你必须搜索那些你直到运行时才知道的字串,那么你可以把整个循环作成一个字串然后 eval 它以避免每次都要重新编译你的模式。再通过设置 $/ 把整个文件输入成一条记录,把这些组合 起来可以非常快,通常比那些专业程序,象 fgrep(1) 什么的都快。下面的程序扫描一个文件列表 (@files),搜索一个单词列表(@words),并且把那些包含大小写无关匹配的文件名字打印出来:

   $search = 'while (<>) { study;';
   foreach $word (@words) {
      $search .= "++\$seen{\$ARGV} if /\\b$word\\b/i;\n";
   }
   $search .= "}";
   @ARGV = @files;
   undef $/;      # 吃进整个文件
   eval $search;      # 这里运行程序
   die $@ if $@;      # 处理 eval 失败
   $/ = "\n";      # 恢复正常的输入终止符
   foreach $file (sort keys(%seen)) {
      print "$file\n";
   }

既然我们有 qr// 操作符,那么上面的编译完的运行时 eval 看上去没有必要。下面的做相同的 事情:

   @pats = ();
   foreach $word (@words) {
      push @pats, qr/\b${word}\b/i;
   }
   @ARGV = @files;
   undef $/;      # 吃进每个完整的文件
   while (<>) {
      for $pat (@pats) {
         $seen{$ARGV}++ if /$pat/;
      }
   }
   $/ = "\n";      # 恢复正常的输入终止符
   foreach $file (sort keys(%seen)) {
      print "$file\n";
   }

29.2.167 sub

子过程声明和定义的语法看起来挺复杂的,但是在实践上实际相当简单。所有东西都是基于下面 语法的:

   sub NAME PROTO ATTRS BLOCK

所有的四个域都是可选的;唯一的限制就是如果这些域的确存在的话那么它们必须以这些顺序出现, 并且你必须至少使用 NAME 或者 BLOCK 之一。目前,我们会忽略 PROTO 和 ATTRS;它们只是 基本语法的修饰词。NAME 和 BLOCK 都是保证正确重要部分:

在上面三种情况中的任何一种里,PROTO 和 ATTRS 之一或者全部都可以在 NAME 之后和/或 BLOCK 之前出现。原型是一个放在圆括弧里的字符列表,它告诉分析器如何对待该函数的参数。 属性是用一个冒号引入的,它告诉分析器有关这个函数的额外的信息。下面是一个包含四个域的 典型的定义:

   sub numstrcmp ($$) : locked {
      my ($a, %b) = @_;
      return $a <=> $b || $a cmp %b;
   }

有关属性列表和它们的操作的细节,请参阅第三十一章里的 attributes 用法。又见第六章和第八章的“匿名子过程”。

29.2.168. substr

这个函数从 EXPR 给出的字串中抽取一个子字串然后返回它。这个子字串是从字串前面 OFFSET 个 字符的位置开始抽取的。(注意:如果你曾经修改了 $[,那么字串的开头就不再是 0 了,不过 因为你没有修改过 $[,所以它的开头还是 0。)如果 OFFSET 是负数,那么子字串是从字串后面 数这么多偏移量位置开始的。如果省略了 LENGTH,那么把从该位置到字串结尾的东西都抽取出来。 如果 LENGTH 是负数,那么该长度是当作在字串尾部剩余那么多字符来理解的。否则,LENGTH 表示要抽取的子字串的长度,通常就是你想要的东西。

你可以把 substr 当作一个左值(可以给之赋值的东西)来用,这个时候 EXPR 也必须是一个合法 的左值。如果你给你的子字串赋予比它短的东西,那么该字串将收缩,而如果你给它赋予比它长的 东西,那么它会变长。要想保持该字串长度一致,你可能需要用 sprintf 或者 x 操作符填充或者 截断你的数值。如果你试图给一个跨过该字串尾部的未分配区域赋值,那么 substr 就会抛出一个 例外。

在 $_ 的当前值前面增加字串“Larry”,用:

   substr($var, 0, 0) = "Larry";

替换 $_ 的第一个字符为“Moe”,用:

   substr($var, 0, 1) = "Moe";

最后,把 $var 的最后一个字符换成“Curly”,用:

   substr($var, -1) = "Curly";

把 substr 当作左值使用的另外一个方面就是声明 REPLACEMENT 字串作为其第四个参数。这样就 允许你替换 EXPR 的某部分并且返回在一次操作之前的东西,就好象你用 splice 实现的功能那样。 下面一个例子也是把 $var 的最后一个字符替换成“Curly”,并且把那个被替换的字符放到 $oldstr 里:

   $oldstr = substr($var, -1, 1, "Curly");

你不一定只是在赋值语句中使用 substr 作为左值。下面的代码把任何空格替换成句点,但是只 替换字串中的最后十个字符:

   substr($var, -10) =~ s/ /./g;

29.2.169. symlink

这个函数创建一个新的文件,该文件是是指向一个旧文件的符号链接。此函数成功时返回真,否则 返回假。在那些不支持符号链接的系统上,它在运行时抛出一个例外。要想检查这个例外,你可以 用 eval 捕获所有可能的错误:

   $can_symlink = eval  { symlink("", ""); 1 };

或者使用 Config 模块。要注意的是如果你提供了一个相对符号链接,那么它会被解释成相对于 该符号链接本身的路径,而不是相对于你的当前工作目录。

又见本章早些时候的 link 和 readlink。

29.2.170 syscall

这个函数调用列表的第一个元素声明的系统调用(意思就是一次系统调用,而不是一个 shell 命令 ),同时把列表中其他元素作为参数传递给系统调用。(现在,许多这些调用可以通过 POSIX 模块 更容易地使用。)如果 syscall(2) 未实现,那么该函数抛出一个例外。

这些参数按照下面的方式解释:如果一个给定的参数是数字,那么该参数就作为一个 C 整数传递。 如果不是,那么就传递一个指向该字串值的指针。你有责任确保这个字串足够长,以便能够接收 任何可能写到它里面去的结果;否则,你就等着核心倾倒吧。你不能拿一个字串文本(或者其他 只读的字串)当作给 syscall 的参数,因为 Perl 已经假定任何字串指针都是可写的。如果你的 整数参数不是文本并且在数字环境里从不会被解释,那么你可能需要给它们加 0 以强迫它们看起来 象数字。

syscall 返回被调用的系统调用返回的任何数值。在 C 传统里,如果你那个系统调用失败,那么 syscall 返回 -1 并且设置 $!(errno)。有些系统调用在成功的时候合理地返回 -1。操作这样 的的调用的正确方法是在调用之前赋值给 $!=0;然后如果 syscall 返回 -1 的话检查 $! 的值。

不是所有的系统调用可以用这个方法访问。比如,Perl 支持最多给你的系统调用传递 14 个参数。 通常这么多已经足够了,但是,对于那些返回多个数值的系统调用就有问题了。比如 syscall($SYS_pipe):它返回创建的管道的读端文件号码。我们没有办法检查另外一端的文件 号码。你可以用 pipe 避免这个例子里的问题。要解决一般性的问题,写直接访问系统调用的 XSUB(外部过程模块,一种 C 写的程序)。然后把你的新模块放到 CPAN,使之流行开来。

下面的子过程以浮点数返回当前时间,而不是象 time 那样返回整数秒。(它只能在那些支持 gettimeofday(2) 系统调用的机器上用。)

   sub finetime() {
      package main;      # 用于下一个 require
      require 'syscall.ph';
      # 预先把缓冲区设置为 32 位长...
      my $tv = pack("LL", ());
      syscall(&SYS)gettimeofday, $tv, undef) >= 0
         or die "gettimeofday: $!";
      my($seconds, $microseconds) = unpack("LL", $tv);
      return $seconds + ($microseconde/ 1_000_000);
   }

假设 Perl 不支持 setgroups(2) 系统调用,(注:尽管它可以通过 $( 支持),但是你的核心 支持。那么你就可以用下面的方法获得它的支持:

   require 'syscall.ph';
   syscall(&SYS_setgroups, scalar @newgids, pack("i*", @newgids))
      or die "setgroups: $!";

你可能需要按照 Perl 安装指导里描述的那样运行 h2ph,检查 syscall.ph 是否存在。有些系统 可能要求使用 "II" 模板。更头疼的是,syscall 假设 C 类型 int,long,和 char* 的尺寸是 相等的。希望你不要把 syscall 当作移植性的体现。

参阅 CPAN 里的 Time::HiRes 模块获取一个有着更好的分辨率的时间装置的更严格的方法。

29.2.171 sysopen

sysopen 函数打开 FILENAME 给出文件名的文件,并且把它和 FILEHANDLE 关联起来。如果 FILEHANDLE 是一个表达式,那么它的值用做该文件句柄的名字或者引用。如果 FILEHANDLE 是一个未定义值的变量,那么 Perl 将会为你创建一个值。如果调用成功,那么返回值是真,否则 是假。

这个函数是你的系统 open(2) 系统调用后面跟着一个 fdopen(2) 库调用的接口。因此,在这儿你 需要略微把自己想象成一个 C 程序员。MODE 参数的可能数值和标志位可以通过 Fcntl 模块获得。 因为不同的系统支持不同的标志位,所以不要指望你的系统里能够用上所有这些标志位。参阅你的 open(2) 手册页或者它本地的等价物获取细节。当然,下面的标志在那些带有合理 C 库的系统里 是存在的:

标志 含义
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读和写
O_CREAT 如果文件不存在则创建之
O_EXCL 如果文件已经存在则失败
O_APPEND 附加到文件上
O_TRUNC 截断该文件
O_NONBLOCK 非阻塞访问

不过,还有许多其他选项。下面是一些不那么常见的标志:

标志 含义
O_NDELAY O_NONBLOCK 的老式同义词
O_SYNC 写块时直到数据已经从物理上写入下层的硬件以后(才返回)可能还会看到 O_ASYNC,O_DSYNC 和 O_RSYNC。
O_EXLOCK 带着 LOCK_EX 的 flock(只是劝告性的)
O_SHLOCK 带着 LOCK_SH 的 flock(只是劝告性的)
O_DIRECTORY 如果该文件不是目录则失败
O_NOFOLLOW 如果路径中最后一个部件是符号链接则失败
O_BINARY 为 Microsoft 系统 binmode 该句柄。有时候还会有一个 O_TEXT存在以获取相反的行为
O_LARGEFILE 有些系统需要这个标志来标识超过 2 GB 的文件
O_NOCTTY 如果你还没有控制终端,那么打开一个终端文件不会令该终端成为这个进程的控制终端,通常不再需要了。

O_EXCL 标志不是用于锁定的:在这里,排它意味着如果该文件已经存在,那么 sysopen 失败。

如果 FILENAME 命名的文件还不存在,并且 MODE 包括 O_CREAT 标志,那么 sysopen 将 在被你的当前 umask 修改后的参数 MASK 决定的权限范围内(或者如果省略这个参数时缺省 是 0666)创建该文件。这样的缺省是有道理的,参阅 unmask 里的记录获得一个解释。

用 open 和 sysopen 打开的文件句柄可以交互地使用。你不必因为碰巧用 sysopen 打开了文件 而使用 sysread 和它的朋友们来操作文件,而如果你用 open 打开它也不意味着你就不能用 sysread 等函数。open 和 sysopen 都可以做一些对方做不了的事情。普通的 open 可以打开 管道,派生进程,设置纪律,复制文件句柄,以及把一个文件描述符号码转换成一个文件句柄。 它还忽略文件名开头和结尾的空白,并且把“-”当作一个特殊的文件名。但是如果你要打开的是 一个真正的文件,那么 sysopen 就可以做 open 能做的任何事情。

下面的例子显示了对两个函数的等效调用。我们为了清晰起见省略了 or die $! 检查,不过你自己 的程序里可是一定要检查这些值的呦。我们将把我们限制于只检查那些实际上在所有操作系统里都 可以用的标志。这个活只是用位操作符 | 把传递给 MODE 参数的数值 OR (或)在一起而已。

而下面的事情是你可以用 sysopen 干的,但是却不能用普通的 open 干:

在第三十二章描述的 FileHandle? 模块提供了一套打开文件的面向对象的同义词(以及一点点新的 功能)。我们很欢迎你在任何用 open,sysopen,pipe,socket,或者 accept 创建的句柄上 调用 FileHandle? 方法(注:实际上是 IO::File 或者 IO::Handle 方法),就算你不用该模块 初始化这些句柄也可以。

29.2.172 sysread

这个函数试图使用低层系统调用 read(2) 从你声明的 FILEHANDLE 里读取 LENGTH 字节到变量 SCALAR 中。该函数返回读取的字节数量,或者在 EOF 时返回 0。(注:在 Perl 里没有 syseof 函数,但这样是对的,因为 eof 在设备文件(比如说终端)上运转的并不怎么正确。用 sysread 并且检查返回值是否为 0 来判断你是否读完了。)出错时,sysread 函数返回 undef。 SCALAR 将会根据实际读取的长度伸缩。如果声明了 OFFSET,那么它指明应该从字串里的哪个位置 开始读取字节,这样你就可以在一个用做缓冲区的字串中间读取。要获取使用 OFFSET 的例子, 请参阅 syswrite。如果 LENGTH 为负数或者 OFFSET 指向了该字串的外边,那么就会抛出一个 例外。

你应该准备处理那些标准 I/O 通常会为你处理的问题(比如中断了的系统调用)。因为它绕开了 标准的 I/O,所以不要把 sysread 和其他类型的读取,print ,printf,write,seek,tell, 或者 eof 在同一个文件句柄上混合使用,除非你准备承受极其希奇古怪(和/或痛苦)的东西。 同样,请注意,如果你从一个包含 Unicode 或者任何其他多字节编码的文件里读取数据,那么 缓冲区的边界有可能落在一个字符的中间。

29.2.173 sysseek

这个函数使用系统调用 lseek(2) 设置 FILEHANDLE 的系统位置。它绕开了标准 I/O,因此把它 和读(除了 sysread 以外),print,printf,write,seek,tell,或者 eof 混合起来使用将 会导致混乱。FILEHANDLE 可以是一个表达式,该表达式的值给出文件句柄的名字。WHENCE 的值为 0 时设置句柄新位置为 POSITION ,1 时设置为当前位置加 POSITION,2 时设置为 EOF 加 POSITION(通常为负数)。你可以用来自标准 IO::Seekable 和 POSIX 模块或者——Perl 5.6 里的 Fcntl 模块里面的 SEEK_SET,SEEK_CUR 和 SEEK_END 作为 WHENCE 的值,而 Fcntl 模块可能更容易移植和更方便一些。

成功时返回新位置,失败时返回 undef。位置零是以特殊字串“0 but true”返回的,该字串可以 直接当数字使用而不会导致警告。

29.2.174 system

这个函数为你执行任何系统里的程序并返回该程序的退出状态——而不是它的输出。要捕获命令行 上的输出,你应该用反勾号或者 qx//。system 函数的运转非常类似 exec,只不过 system 先做 一个 fork,然后在 exec 之后等待执行的程序的结束。也就是说它为你运行这个程序并且在它 完成之后返回,而 exec 用新的程序代替你运行的程序,所以如果替换成功的话它从不返回。

参数的处理因参数的数目的不同而不同,就象在 exec 里描述的那样,包括判断是否调用 shell 以及你是否用声明另外一个 PATHNAME 的方法使用了该函数其他的名称。

因为 system 和反勾号阻塞 SIGINT 和 SIGQUIT,所以向那些正在这样运行的程序发送这些信号 之一(比如通过一个 Control-C)时并不会中断你的主程序。但是你运行的另外一个程序的确收到 这个信号。请检查 system 的返回值,判断你运行的程序是否正常退出。

   @args = ("command", "arg1", "arg2");
   system(@args) == 0
      or die "system @args failed: $?"

返回值是和该函数通过 wait(2) 系统调用返回的一样的退出状态。在传统的语意里,要获取实际的 退出值,要除以 256 或者右移 8 位。这是因为低 8 位里有一些其他的东西。(实际上是其他的 两些东西。)最低七位标识杀死该进程的信号号码(如果有的话),而第八位标识该进程是否倾倒 了核心。你可以通过 $?($CHILD_ERROR)来检查所有失效可能性,包括信号和核心倾倒:

   $exit_value = $? >> 8;
   $exit_value = $? & 127;   # 或者 0x7f, 0177, 0b0111_1111
   $dumped_core = $? & 128;   #  或者 0x80, 0200, 0b1000_0000

如果该程序是通过系统 shell (注:定义为 /bin/sh 或者任何在你的平台上有意义的东西,但 不是那些用户碰巧在某个时候用到的 shell。)运行的,这可能是因为你只有一个参数而且该参数 里面有 shell 元字符,那么通常返回码受那个 shell 的怪癖和功能的影响。换句话说,在这种 情况下,你可能无法获取我们前面描述了详细信息。

29.2.175 syswrite

这个函数试图用 write(2) 系统调用向你声明的 FILEHANDLE 里写入从变量 SCALAR 里获取的 LENGTH 字节的数据。该函数返回实际写入的字节数,或者是出错时返回 undef。如果声明了 OFFSET,那么 它指明从字串里的哪个位置开始写。(比如,你可能用一个字串做一个缓冲区,这时你就需要这个 功能了,或者你需要从一个部分写中恢复过来。)负数 OFFSET 表示写应该从该字串的后面向前数 这么多个字节。如果 SCALAR 是空的,那么唯一允许的 OFFSET 是 0。如果 LENGTH 为负数或者 OFFSET 指向了字串的外面,那么就会抛出一个例外。

要从文件句柄 FROM 中拷贝数据到文件句柄 TO,你可以用下面这样的东西:

   use Errno qw/EINTR/;
   $blksize = (stat FROM)[11] || 16384;      # 选定的块大小?
   while ($len = sysread FROM, $buf, $blksize) {
      if (!defined $len) {
         next if $! == EINTR;
         die "System read error: $!\n"
      }
      $offset = 0;
      while ($len) {            # 处理部分写问题
         $written = syswrite TO, $buf, $len, $offset;
         die "System write error: $!\n" unless defined $written;
         $offset   += $written;
         $len   -= $written;
      }
   }

你必须准备处理标准 I/O 通常会为你处理的问题,比如部分写。因为 syswrite 绕开了 C 标准 I/O 库,所以不要把它的调用和读(除了 sysread 以外),写(象 print,printf,或者 write),或者其他 stdio 函数,比如 seek,tell,或者 eof 混合在一起用,除非你想自找 麻烦。

29.2.176 tell

这个函数返回 FILEHANDLE 的当前文件位置(以零为基的字节数)。该值通常可以在程序中稍后的 时候传递给 seek 函数以找回当前位置。FILEHANDLE 可以是一个给出实际文件句柄的表达式, 或者一个指向文件对象的引用。如果省略 FILEHANDLE,那么该函数返回最后一个读取的文件的 位置。只有普通文件的文件位置才有意义。设备,管道,和套接字都没有文件位置。

没有 systell 函数,你可以用 sysseek(FH, 0, 1) 来实现同样的功能。参阅 seek 获取一个 如何使用 tell 的例子。

29.2.177. telldir

这个函数返回在 DIRHANDLE 上的 readdir 的当前位置。而这个返回值可以给 seekdir 用于访问 一个目录里的某个特定的位置。该函数和对应的系统库过程在关于可能的目录压缩问题上有这一样的 注意事项。该函数可能不是在所有实现了 readdir 的地方有实现了,即使该平台实现了它,你也 不能计算它的返回值。因为该返回值只是一个晦涩的数值,只对 seekdir 有意义。

29.2.177. tie

此函数把一个变量和一个类绑定在一起,而该类提供了该变量的实现。VARIABLE 是要绑定的变量 (标量,数组,或者散列)或者类型团(代表一个文件句柄)。CLASSNAME 是实现一个正确类型的 类名字。

任何额外的参数都传递给该类的合适的构造方法,可能是 TIESCALAR,TIEARRAY,TIEHASH 或者 TIEHANDLE 之一。(如果没有找到合适的方法,则抛出一个例外。)通常,那些都是可能被传递给 象 dbm_open(2) 这样的 C 函数的参数,但是它们的含义是和包相关的。构造器返回的对象随后被 tie 函数返回,而如果你想在 CLASSNAME 里访问其他方法,那么这个对象就很有用了。(该对象 也可以通过 tied 函数访问。)因此,一个把散列与一个 ISAM 绑定的实现可以提供一些额外的 方法用于顺序地跨过一个键字的集合(ISAM 里的“S”就是 sequentially,顺序的意思),因为 你典型的 DBM 实现是不能做这些事情的。

象 keys 和 values 这样的函数在和 DBM 这样的大对象一起使用的时候可能返回非常巨大的数值 列表。你可能会更愿意使用 each 函数来遍历这样的列表。比如:

   use NDBM_File;
   tie %ALIASES, "NDBM_File", "/etc/aliases", 1, 0
      or die "Can't open aliases: $!\n";
   while (($key, $val) = each %ALIASES) {
      print $key, ' = ', $val, "\n";
   }
   untie %ALIASES;

一个实现散列的类应该提供下列方法:

TIEHASH CLASS, LIST
FETCH SELF, KEY
STORE SELF, KEY, VALUE
DELETE SELF, KEY
CLEAR SELF
EXISTS SELF, KEY
FIRSTKEY SELF
NEXTKEY SELF, LASTKEY
DESTROY SELF

一个实现普通数组的类应该提供下列方法:

    TIEARRAY CLASS, LIST
    FETCH SELF, SUBSCRIPT
    STORE SELF, SUBSCRIPT, VALUE
    FETCHSIZE SELF
    STORESIZE SELF, COUNT
    CLEAR SELF
    PUSH SELF, LIST
    POP SELF
    SHIFT SELF
    UNSHIFT SELF, LIST
    SPLICE SELF, OFFSET, LENGTH, LIST
    EXTEND SELF, COUNT
    DESTROY SELF

一个实现标量的类应该提供下列方法:

    TIESCALAR CLASS, LIST
    FETCH SELF,
    STORE SELF, VALUE
    DESTROY SELF

一个实现文件句柄的类应该提供下列方法:

TIEHANDLE CLASS, LIST
READ SELF, SCALAR, LENGTH, OFFSET
READLINE SELF
GETC SELF
WRITE SELF, SCALAR, LENGTH, OFFSET
PRINT SELF, LIST
PRINTF SELF, FORMAT, LIST
CLOSE SELF
DESTROY SELF

并不是上面提到的所有方法都需要实现:Tie::Hash,Tie::Array,Tie::Scalar,和 Tie::Handle 模块提供了有着合理的缺省的基类。参阅第十四章,捆绑变量,获取所有这些方法的 详细描述。和 dbmopen 不同,tie 函数将不会为你 use 或者 require 一个模块——你必须自己 明确地做这件事情。参阅 DB_File 和 Config 方法获取有趣的 tie 实现。

29.2.179 tied

这个函数返回一个引用,该引用指向包含在 VARIABLE 里的标量,数组,散列或者类型团的的下层 对象。(VARIABLE 是最初用 tie 调用把该变量和一个包绑定在一起的同一个值。)如果 VARIABLE 没有和一个包绑定,它返回未定义的数值。因此,比如,你可以用:

   ref tied %hash

找出你的散列与哪个包捆绑。(假设你忘记了。)

29.2.180. time

这个函数返回自“纪元”以来的没有润秒的秒数,纪元通常是 1970年1月1日 00:00:00 UTC。 (注:不要和创造 Unix 的“历史”相混淆。(其他操作系统可能有不同的纪元,更别说历史了。 ))返回值可以传递给 gmtime 和 localtime,可以用于比较 stat 返回的文件修改以及访问的 时间,还有就是传递给 utime。

  $start = time();
   system("some slow command");
   $end = time();
   if ($end - $start > 1) {
      print "Program started: ", scalar localtime($start), "\n";
      print "Program ended:  ", scalar localtime($end), "\n";
   }

29.2.181 times

在这个环境里,这个函数返回一个四元素的列表,该列表给出这个进程和它已结束的子进程以秒计 (可能是分数)的用户和系统 CPU 时间。

   ($user, $system, $cuser, $csystem) = times();
   printf "This pid and its kids have consumed %.3f seconds\n",
      $user + $system + $cuser + $csystem;

在标量环境里,只返回用户时间。比如,要计算一段 Perl 代码的执行速度:

   $stat = times();
   ...
   $end = times();
   printf "that took %.2f CPU seconds of user time\n",
      $end - $start;

29.2.182 tr///

这是转换(也称之为翻译)操作符,它和 Unix sed 程序里的 y/// 操作符类似,但不论从任何人 的角度来看都更好些。参阅第五章。

29.2.182 truncate

这个函数截断在 FILEHANDLE 上打开的或者 EXPR 命名的文件到指定的长度。如果在你的系统上, ftruncate(2) 或者等效的东西没有实现,那么该函数抛出一个例外。(如果你有磁盘空间的话, 你总是可以通过拷贝文件的开头来截断它。)该函数在成功的时候返回真,否则返回 undef。

29.2.183 uc

这个函数返回 EXPR 的大写的版本。它是实现双引号字串里的 \U 逃逸的内部函数。Perl 将试图 在考虑你的区域设置的前提下做正确的事情,不过我们仍在努力让这个功能也能用于 Unicode。 参阅 perllocalle 手册页获取最新的进展。在任何情况下,如果 Perl 使用 Unicode 表,uc 都会转换成大写字符而不是标题字符。参阅 ucfirst 获取转换成标题字符的信息。

29.2.184 ucfirst

这个函数返回将 EXPR 第一个字符标题化(“Unicode”里的标题字符)的版本。而其他字符则不 加触动。它是实现双引号字串里的 \u 逃逸的内部函数。如果你 use locale 并且你的数据看上去 不象 Unicode,那么 Perl 会考虑你当前的 LC_CTYPE 区域设置,但是我们现在不能做任何保证。

要强制字串里第一个字符是标题字符而其他的都是小写字符,你可以用:

   ucfirst lc $word

它等效于"\u\L$word"。

29.2.186 umask

这个函数用 umask(2) 系统调用为该进程设置 umask 并返回原来的那个。你的 umask 告诉操作 系统在创建新文件的时候,哪个权限位是不允许的,包括那些正好是目录的文件。如果省略了 EXPR,那么该函数只是返回当前 umask。比如,为了确保“user”位是允许,而“other”位是 不允许的,你可以用下面的代码:

   umask((umask() & 077) | 7);      # 不改变组的权限位

请记住 umask 是一个数字,通常是以八进制形式给出的;它不是八进制位的字串。如果你拿到的 是一个字串,又见 oct,还要记住这个 umask 位是普通权限位的补。

Unix 权限位 rwxr-x--- 是用三个三位集,或者三个八进制位来表示的:0750(前面的 0 表示它 是八进制而不是其中一位)。因为 umask 的位是翻转的,所以它代表关闭了的权限位。你提供给 你的 mkdir 或者 sysopen 的权限值(或者“模式”)都会被你的 umask 修改,所以就算你告诉 sysopen 创建一个权限为 0777 的文件,如果你的 umask 是 0022,那么创建出来的文件的权限 也是 0755。如果你的 umask 是 0027(组不能写,其他不能读,写,和执行),那么给 sysopen 传递一个 MASK 为 0666 的值将创建一个模式为 0640 的文件(因为 0666 & ~0027 是 0640)。

这里是一些建议:使用模式 0666 创建普通文件(在 sysopen 里)以及 0777 给目录(用 mkdir)和可执行文件。这样就给予用户自由的选择:如果它们想保护文件,那么它们选择进程 umask 022,027,或者甚至特别反社会的掩码 077。程序最好让用户自己做策略决策。这条规则的 例外是那些需要写私人文件的程序:邮件文件,网络浏览器的 cookie,.rhost 文件,等等。

如果你的系统里没有实现 umask(2) 并且你试图限制你自己的权限(也就是说,如果 (EXPR & 0700) > 0),那么你就会触发一个运行时例外。如果你的平台没有实现 umask(2) 并且 你不准备限制自己的权限,那么这个函数简单地返回 undef。

29.2.186 undef

undef 是我们所谓的“未定义值”的缩写。同时,它还是一个永远返回未定义值的函数的名字。 我们很高兴能混淆它们俩。

相同的是,如果你给 undef 函数提供一条记录作为它的参数,那么它还可以明确的解除该记录的 定义。如果声明了 EXPR 参数,那么它必须是一个左值。因此你可能只能在一个标量数值,整个 散列或者数组,一个子过程名字(用 & 前缀),或者一个类型团上这么用,任何和该对象关联的 存储空间都将被恢复用于重复使用(不过在大多数操作系统上都不是返回给系统)。undef 函数对 大多数特殊变量可能都不会做想你想象的处理。在象 $1 这样的只读变量上使用将抛出一个例外。

undef 函数是一个单目操作符,不是列表操作符,因此你只能每次解除一个东西的定义。下面是 一些 undef 作为单目操作符的用法:

   undef $foo;
   undef $bar{'blurfl'};      # 和 delete $bar{'blurfl'} 不同
   undef @ary;
   undef %hash;
   undef &mysub;
   undef *xyz;            # 删除 $xyz, @xyz, %xyz, &xyz 等等。

如果没有参数,undef 只是用做数值:

   select(undef, undef, undef, $naptime);

   return (wantarray ? () : undef) if $they_blew_it;
   return if $they_blew_it;   # 一样的东西

你可以把 undef 用做一个列表赋值中左边的一个占位符,这个时候右边的对应的数值只是简单地 抛弃。除此之外,你不能在其他地方拿 undef 做左值:

   ($a, $b, undef, $c) = &foo;   # 忽略返回的第三个数值

同样,不要拿任何东西和 undef 做比较——那样不会按照你想象的方式处理的。它所作的事情只是 与 0 或者空字串比较。使用 defined 函数判断一个数值是否定义。

29.2.188. unlink

这个函数删除一列文件。(注:实际上,在一个 POSIX 文件系统里,它删除指向真实文件目录记录 (文件名)。因为一个文件可以从一个或多个目录里引用(链接),该文件不会被删除,直到指向 它的最后一个引用被删除。)此函数返回被成功删除的文件名的个数。一些简单的例子:

   $count = unlink 'a', 'b', 'c';
   unlink @goners;
   unlink glob("*.orig");

除非你是超级用户或者给 Perl 使用了 -U 命令行选项,否则 unlink 函数不会删除目录。即使 符合这些条件,你也要注意删除一个目录可能回造成对你的文件系统的彻底损坏,应该用 rmdir 代替。

下面是一个带有非常简单的错误检查的 rm 命令:

   #!/usr/bin/perl
   @cannot = grep {no unlink} @ARGV;
   die "$0: could not unlink @cannot\n" if @cannot;

29.2.189. unpack

这个函数是 pack 的逆操作:它根据 TEMPLATE 把一个表示一个数据结构的字串(EXPR)扩展成 一列数值并返回那些数值。在标量环境里,它可以用于解包一个数值。这里的 TEMPLATE 有着和 pack 函数里的大多数格式——它声明要解包的数值的顺序和类型。参阅 pack 函数获取有关 TEMPLATE 的详细描述。如果 TEMPLATE 里有非法元素,或者试图跨过 x,X,或者 @ 格式字串的 外面,都会抛出例外。

该字串会分解成 TEMPLATE 里描述的片段。每个片段都独立地转化成一个数值。通常,该字串的 字节要么是 pack 的结果,要么代表某种类型的 C 结构。

如果一个域的重复计数比输入字串剩余部分允许的尺寸大,那么重复计数就会被不声不响地缩小。 (不过,你通常会在这个地方放一个 * 做重复计数。)如果输入字串比 TEMPLATE 描述的长, 那么字串剩余的部分被忽略。

unpack 函数对纯文本数据也很有用,而不仅仅是对二进制数据管用。设想你有一个数据文件,它的 内容看起来象下面这样:

1986 Ender's Game           Orson Scott Card
1985 Neuromancer            William Gibson
1984 Startide Rising        David Brin
1983 Foundation's Edge      Isaac Asimov
1982 Downbelow Station      C. J. Cherryh
1981 The Snow Queen         Joan D. Vinge

你不能用 split 来分析出各个域,因为这里没有明显的分隔符。这里的域是由它们的字节偏移量来 决定的。因此就算这是一个普通的文本记录,但因为它是固定格式的,所以你就可以用 unpack 把 它们分解成数据域:

   while (<>) {
      ($year, $title, $author) = unpack("A4 x A23 A*", $_);
      print "$author won ${year}'s Hugo for $title.\n";
   }

(我们在这里写成 ${year}'s 的原因是 Perl 会把 $year's 当作 $year::s 看待。)

下面是一个完整的 undecode 程序:

   #! /usr/bin/perl
   $_ = <> until ($mode, $file) = /^begin\s*(\d*)\s*(\S*)/;
   open(OUT, "> $file") if $file ne "";
   while (<>) {
      last if /^end/;
      next if /[a-z]/;
      next unless int((((ord() - 32) & 077) + 2) / 3) ==
            int (length() / 4);
      print OUT unpack "u", $_;
   }
   chmod oct($mode), $file;

除了那些 pack 里允许的数据域以外,除了各个项自身以外,你还可能在一个数据域前面前缀一个 %number 作成一个所有项的简单的 number 位附加校验和。该校验和是通过类加扩展数值的数字 值来计算的(对于字串域,求 ord($char) 的和,而对于位域,计算零和一的和)。比如,下面的 代码计算和 SysV? sum(1) 相同的数字:

   undef $/;
   $checksum = unpack ("%32C*", <>) % 65535;

下面的代码高效的计算一个位流里的设置上的位的个数:

   $setbits = unpack "%32b*", $selectmask;

下面是一个简单的 BASE64 解码器:

   while (<>) {
      tr#A-Za-z0-9+/##cd;            # 删除非 base64 字符
      tr#A-Za-z0-9+/# -_#;            # 转换成 uuencode 格式
      $len = pack("c", 32 + 0.75*length);   # 计算长度字节
      print unpack("u", $len . $_);      # uudecode 并打印
   }

29.2.190. unshift

这个函数做 shift 的逆操作。(或者是 push 的逆操作,取决于你怎么看它。)它在数组前面 增加 LIST,并返回在数组里的新的元素个数:

   unshift @ARGV, '-e', $cmd unless ARGV[0] =~ /^-/;

请注意 LIST 是整个放到前面,而不是每次一个元素,因此放到前面的元素保持相同顺序。用 reverse 实现这些元素的翻转。

29.2.190. untie

打破 VARIABLE 里包含的变量或者或者类型团和与它捆绑的包之间的绑定。参阅 tie,以及第十四 章的全部,尤其是“一个精细的松绑陷阱”节。

29.2.190. use

use 声明装载一个模块(如果它还没有被装载),并且把子过程和变量从这个命名模块输入到当前 包。(从技术上来讲,它从那个命名模块向当前包输入一些语意,通常是通过把一些子过程或者 变量名作成你的包里的别名的方法。)大多数 use 的声明看起来象:

   use MODULE LIST;

这样和下面是完全一样的:

   BEGIN { require MODULE; import MODULE LIST; }

BEGIN 迫使 require 和 import 在编译时发生。require 确保该模块在还没有装载的时候装入 内存。import 不是内建的函数——它只是一个普通的类方法,调用名字叫 MODULE 的包,告诉该 模块把列表里的特性拖到当前包里来。模块可以用自己喜欢的任何方法实现它的输入方法,尽管 大多数只是通过从 Exporter 类中继承 import 方法。Exporter 类在 Exporter 模块中定义。 参阅第十一章,模块,以及 Exporter 模块获取更多信息。如果找不到 import 方法,那么调用将 不声不响地忽略。

如果你不希望你的名字空间被修改,那么明确地提供一个空列表:

   use MODULE ();

它和下面的代码完全一样:

   BEGIN { require MODULE; }

如果给 use 的第一个参数是象 5.6.2 这样的版本号,那么当前执行着的 Perl 版本必须至少和 声明的版本一样新。如果当前的版本比 VERSION 小,那么就会打印出一条错误信息然后 Perl 马上退出。这样就可以有效地在装载需要更新的版本的库模块之前检查当前 Perl 的版本,因为 有时候我们必须“破坏”老版本的错误特性。(我们总是试图尽可能不破坏任何东西。而实际上 我们总是试图少破坏东西。)

谈到不破坏其他东西,Perl 仍然接受下面形式的老版本号:

   use 5.005_03;

不过,为了和工业标准更好的看齐,Perl 5.6 现在接受(并且也更愿意使用)下面的三段式:

   use 5.6.0;      # 它是版本 5,子版本 6,补丁级 0。

如果 VERSION 参数在 MODULE 后面出现,那么 use 将在类 MODULE 里调用 VERSION 方法, 同时把给出的 VERSION 当作参数给他。请注意在 VERSION 后面没有逗号!缺省的 VERSION 方法(通常是从 UNIVERSAL 类里继承过来的。)会在给出的版本大于变量 $Module::VERSION 的值的情况下发表意见。

参阅第三十二章获取一个标准模块的列表。

因为 use 提供了一个非常开放的接口,所以用法(编译器指示器)也是通过模块来实现的。当前 实现了的用法包括:

   use autouse 'Carp' => qw(carp croak);
   use bytes;
   use constant PI => 4 * atan2(1,1);
   use diagnostics;
   use integer;
   use lib '/opt/projects/spectre/lib';
   use locale;
   use sigtrap qw(die INT QUIT);
   use strict qw(subs vars refs);
   use warnings "deprecated";

许多这些用法模块向当前词法范围输入语意。(它和普通模块不同,普通模块只是向当前包里输入 符号,而除了该词法范围是在带有该包的情况下编译的以外,那些符号和当前词法范围没有什么 关系。也就是说,哦,看看第十一章吧。)

还有一个对应的声明,no,它“戒除”任何原来用 use 输入的东西,让它们变得不再重要:

   no integer;
   no strice 'refs';
   no utf8;
   no warnings "unsafe";

参阅第三十一章获取一个标准用法的列表。

29.2.193 utime

该函数改变一列文件里的每一个文件的访问和修改时间。列表的头两个元素必须是数字化的访问和 修改时间,顺序是访问在前修改在后。该函数返回成功改变的文件的数目。每个文件的 inode 修改 时间设置成当前时间。下面是一个 touch 命令的例子,它设置该文件的修改日期(假设你是 所有者)为近一个月后:

   #! /usr/bin/perl
   # montouch - post-date files now + 1 month
   $day = 24 * 60 * 60;         # 24 小时的秒数
   $later = time() + 30 * $day;   # 30 天接近一个月
   utime $later, $later, @ARGV;

29.2.194. values

这个函数返回一个包含指定散列 HASH 里的所有值的列表。这些值是以看似随机的顺序返回的, 但是这个顺序和 keys 或 each 函数在同一个散列上生成的顺序相同。怪异的是,如果要通过一个 散列的数值对它进行排序,那么你通常需要使用 keys 函数,所以看看 keys 函数里的例子找找 灵感。

你可以用这个函数修改一个散列的数值,因为它返回的列表包含数值的别名,而不是拷贝。(在 早期的版本里,你需要用散列的片段来实现这个功能。)

   for (@hash{keys %hash}) { s/foo/bar/g }    # 老办法
   for (values %hash)      { s/foo/bar/g }   # 新手段

在一个和某个巨大的 DBM 文件捆绑的散列上使用 values 也肯定会生成一个巨大的列表,导致你 拥有一个巨大的进程。你可能应该使用 each 函数,它会一个一个地遍历散列记录,而不会把它们 的所有东西都吞进一个庞大的,哦,应该是巨大的列表里。

29.2.195 vec

vec 函数制造一个存储紧凑的无符号整数的列表。这些整数在一个普通的 Perl 字串里尽可能紧密 的绑在一起。EXPR 里的字串被当作一个位串对待,该未串是用若干元素组成的,而元素的数目取决 于字串的长度。

OFFSET 声明你关心的特定元素的索引。读和写元素的语法是一样的,因为 vec 根据你是在左值 环境还是右值环境里来存储和恢复元素值。

BITS 声明每个元素以位计算的时候宽度是多少,它必须是二的幂:1,2,4,8,16,或 32(有些 平台上还有 64)。(如果声明了任何其他数值,那么就会抛出一个例外。)因此每个元素都可以 包含一个范围在 0 .. (2**BITS)-1 的整数。对于小一些的尺寸,那么每个字节里都会尽可能多的 打包进去元素。如果 BITS 是 4,那么每个字节里有两个元素(通常它们被称为半字节(nybble ))。等等。大于一个字节的整数是以大头在前的字节序存储的。

一个无符号整数列表可以存储在一个标量变量里,方法是把它们分别赋予 vec 函数。(如果 EXPR 不是一个合法的左值,那么抛出一个错误。)在随后的例子里,那些元素是 4 位宽:

   $bitstring = "";
   $offset = 0;
   
   foreach $num (0, 5, 5, 6, 2, 7, 12, 6) {
      vec($bitstring, $offset++, 4) = $num;
   }

如果一个元素超出了它要写的字串的结尾,那么 Perl 先用足够的零内容字节扩展该字串。

存储在标量变量里的向量然后就可以通过声明正确的 OFFSET 来检索:

   $num_elements = length($bitstring)*2;   # 每个字节 2 元素

   foreach $offset (0 .. $num_elements-1) {
      print vec($bitstring, $offset, 4), "\n";
   }

如果选择的元素超出了字串的结尾,那么返回 0。

用 vec 创建的字串还可以用逻辑操作符 |,&,^,和 ~ 操作。如果两个操作数都是字串,那么 这些操作符将假定需要进行的是位串操作。参阅第三章,单目和双目操作符,“位操作符”一节里 的例子。

如果 BITS == 1,那么就可以创建一个所有位都存储在一个标量里的位序列。顺序是这样的, vec($bitstring, 0,1) 保证进入字串里的第一个字节的最低位。

   @bits = (0,0,1,0, 1,0,1,0, 1,1,0,0, 0,0,1,0);

   $bitstring = "";
   $offset = 0;

   foreach $bit (@bits) {
      vec($bitstring, $offset++, 1) = $bit;
   }

   print "$bitstring\n";      # "TC", 也就是 '0x54', '0x43'

一个位串可以通过声明一个“b*”模板给 pack 或者 unpack 从一串 1 和 0 转换过来,或者是 转换成这些 1 和 0 的字串。另外,pack 可以和“b*”模板一起使用从一个 1 和 0 的字串创建 一个位串。该顺序和 vec 需要的顺序是兼容的。

   $bitstring = pack "b*", join('', @bits);
   print "$bitstring\n";   # "TC",和前面例子一样

unpack 可以用于从该位串中抽取这个 0 和 1 的列表:

   @bits = split(//, unpack("b*", $bitstring));
   print "@bits\n";      # 0 0 1 0 1 0 1 0 1 1 0 0 0 0 1 0

如果你知道位串的位的确切长度,那么这个长度可以用于“*”的位置。

参阅 select 获取使用 vec 生成的位图的其他的例子。参阅 pack 和 unpack 获取操作二进制 数据的更高级别的方法。

29.2.196. wait

这个函数等待一个子进程退出并返回消失了的进程的 PID,或者如果没有子进程了就返回 -1(或者 在一些系统里,子进程自动被收割也如此。)它在 $? 里返回的状态和在 system 里描述的一样。 如果你有僵死子进程,那么你就应该调用这个函数,或者 waitpid。

如果你在等待一个子进程,但是用 wait 没有找到它,那么你可能就是调用了 system,关闭了 一个管道,或者在 fork 和 wait 之间使用了反勾号。这些构造也做 wait(2) 并且因此可能收割 你的子进程。使用 waitpid 避免这样的情况。

29.2.197. waitpid

这个函数等待特定的子进程结束并在该进程死亡之后返回其 PID,如果没有其他的子进程时返回 -1,或者 FLAGS 里的标志声明的是非阻塞状态而该进程尚未退出,则返回 0。死亡的进程返回的 状态存储在 $?,并且和 system 里描述的一样。要获取有效的标志值,那么你必须输入 “:sys_wait_h”从 POSIX 里输入标签组。下面是一个等待所有挂起的僵死进程的非阻塞的例子:

   use POSIX ":sys_wait_h";
   do {
      $kid = waitpid(-1, &WNOHANG);
   } until $kid == -1;

在那些既没有实现 waitpid(2) 也没有实现 wait4(2) 系统调用的平台上,你可以声明的 FLAGS 只有 0。换句话说,你在那里可以等待一个特定的 PID,但是你不能在非阻塞模式里做这些事情。

在一些系统里,返回值为 -1 意味着该子进程被自动收割了,因为你设置了 $SIG{CHLD} = 'IGNORE'。

29.2.198. wantarray

如果当前正在执行的子过程在寻找列表数值,那么此函数返回真,否则返回假。如果调用环境需要 的是一个标量,那么该函数返回一个定义了的假(""),而如果调用环境并不需要任何东西,(也 就是说,空环境)那么返回一个未定义的假值(undef);

下面是它的典型用法的例子:

   return unless defined wantarray;      # 不需要干什么事情
   my @a = complex_calculation();
   return wantaray ? @a : \@a;

又见 caller。这个函数真是应该命名为“wantlist”,但是我们是在列表环境还叫数组环境的 时候命名它的。(译注:数组英文词是“array”,列表英文词是“list”。)

29.2.199 warn

这个函数生成一条错误信息,象 die 那样把 LIST 打印到 STDERR,但是并不试图退出或者抛出 一个例外。比如:

   warn "Debug enbled" if $debug;

如果 LIST 为空并且 $@ 已经包含一个数值(通常是前面的 eval 留下来的),那么字串 "\t... caught" 在 STDERR 上附加在 $@ 后面。(这样做类似 die 传播错误的方法,只不过 warn 并不传播(抛出)该例外。)如果你提供的字串上是空的,那么使用 "Warning: something's wrong"。

和 die 一样,如果你提供的字串并不是以换行符结尾,那么自动附加文件和行号信息。warn 函数 和 Perl 的 -w 命令行选项没有关系,但是可以和它一起使用,比如在你想模拟内建函数的时候:

   warn "Something wicked\n" if $^W;

如果安装了 $SIG{__WARN__} 句柄,那么不会打印任何信息。这个句柄是负责对它看到的信息进行 适当处理用的。你想这么做的一个原因可能是把简单的警告转化成一个例外:

   
   local $SIG{__WARN__} = sub {
      my $msg = shift;
      die $msg if $msg =~ /isn't numeric/;
   };

因此大多数句柄都必须对那些它们原先没有准备处理的警告安排显示处理的工作,方法是在句柄里 调用 warn。这么做非常安全,它不会产生无限的循环,因为 WARN 挂钩不会在 WARN 里面被调用。这个行为和 $SIG{__DIE__} 的行为(它不会消除错误文本,但是可以再次调用 die 来改变它)略有区别。

使用 WARN 句柄给我们提供了一个强有力的抑制所有警告的方法,甚至连那些强制性的警告也 给抑制住了。有时候你需要把这个东西封装在 BEGINP{} 块里,这样它就可以在编译时起做用:

# 扫荡掉所有编译时间警告

   BEGIN { $SIG{__WARN__} = sub { warn $_[0] if $DOWARN } }
   my $foo = 10;
   my $foo = 20;      # 不要警告我说重复了 my $foo,
            # 不过,这可是你说的!

   # 在这以前没有编译时和运行时的警告
   $DOWARN = 1;      # 不是一个内建的变量

   #  在这以后打开运行时的警告
   warn "\$foo is alive an $foo!";   # 做显示

参阅 use warnings 用法获取警告的词法范围里的控制。参阅 Carp 模块里的 carp 和 cluck 函数获取其他制造警告信息的方法。

29.2.200. write

这个函数写一条格式化了的记录(可能是多行)到声明的文件句柄,使用和该文件句柄相关联的 格式——参阅第七章里的“格式变量”一节。缺省时与文件句柄相关联的格式是和文件句柄同名的 那个。不过,一个文件句柄的格式可以在你 select 了该句柄以后修改 $~ 变量来修改:

   $old_fh = select(HANDLE);
   $~ = "NEWNAME";
   select($old_fh);

或者说:

   use IO::Handle
   HANLDE->format_name("NEWNAME");

因为格式是放到一个包名字空间里的,所以如果该 format 是在另外一个包里声明的,那么你可能 不得不用该格式的全称:

   $~ = "OtherPack::NEWNAEM";

表单顶部(Top-of-form)的处理是自动操作的:如果当前页里面没有足够的空间容纳格式化的 记录,那么通过写一个进纸符来续该页,这时候在新页上使用一个特殊的表单顶部格式,然后写该 记录。在当前页里余下的行数放在变量 $- 里,你可以把它设置为 0 以强迫在下一次 write 的 时候使用新的一页。(你可能先得 select 该文件句柄。)缺省时,页顶格式的名字就是文件句柄 后面加上 "_TOP",不过,一个文件句柄的格式可以在你 select 了该句柄以后修改 $~ 变量来 修改,或者说:

   use IO::Handle;
   HANDLE->format_top_name("NEWNAME_TOP");

如果没有声明 FILEHANDLE,那么输出就会跑到当前缺省的输出文件句柄,这个文件句柄初始时是 STDOUT,但是可以用单参数形式的 select 操作符修改。如果 FILEHANDLE 是一个表达式,那么 在运行时计算该表达式以决定实际的 FILEHANDLE。

如果声明的 format 或者当前的页顶 format 并不存在,那么抛出一个例外。

糟糕的是,write 函数不是 read 的逆操作。用 print 做简单的字串输出。如果你找到这个函数 的原因是因为你想绕开标准 I/O,参阅 syswrite。

29.2.201 y//

转换操作符(因历史原因,也叫做翻译操作符),也叫做 tr///。参阅第五章。

-- TingYu - 31 Jan 2006
to top