您正在查看: Per 分类下的文章

第二十九章 函数(O-R)


第二十九章,函数 (O-Y)

  • 第二十九章,函数 (O-Y)
    • 29.2 按照字母顺序排列的 perl 函数
      • 29.2.103. oct
      • 29.2.103 open
      • 29.2.105 opendir
      • 29.2.106 ord
      • 29.2.107 our
      • 29.2.108. pack
      • 29.2.109. package
      • 29.2.110 pipe
      • 29.2.111. pop
      • 29.2.112 pos
      • 29.2.113 print
      • 29.2.114 printf
      • 29.2.115. prototype
      • 29.2.116 push
      • 29.2.117. q/STRING/
      • 29.2.118. quotemeta
      • 29.2.119 rand
      • 29.2.120 read
      • 29.2.121. readdir
      • 29.2.122 readline
      • 29.2.123 readlink
      • 29.2.124. readpipe
      • 29.2.125 recv
      • 29.2.126 redo
      • 29.2.127 ref
      • 29.2.128 rename
      • 29.2.129 require
      • 29.2.130 reset
      • 29.2.131. return
      • 29.2.132 reverse
      • 29.2.133. rewinddir
      • 29.2.134. rindex
      • 29.2.135. rmdir

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

29.2.103. oct

  • oct EXPR
  • 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, MODE, LIST
  • open FILEHANDLE, EXPR
  • open FILEHANDLE

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(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, "&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, "

第二十九章 函数(S-Z)


第二十九章,函数 (S-Y)

  • 第二十九章,函数 (S-Y)
    • 29.2 按照字母顺序排列的 perl 函数.
      • 29.2.136 s///
      • 29.2.137. scalar
      • 29.2.138 seek
      • 29.2.139 seekdir
      • 29.2.140 select (输出文件句柄)
      • 29.2.141 select (准备文件描述符)
      • 29.2.142 semctl
      • 29.2.143 semget
      • 29.2.144 semop
      • 29.2.145 send
      • 29.2.146. setpgrp
      • 29.2.147. setpriority
      • 29.2.148. setsockopt
      • 29.2.149 shift
      • 29.2.150 shmctl
      • 29.2.151 shmget
      • 29.2.152 shmread
      • 29.2.153 shmwrite
      • 29.2.154 shutdown
      • 29.2.155. sin
      • 29.2.156 sleep
      • 29.2.157 socket
      • 29.2.158 socketpair
      • 29.2.159 sort
      • 29.2.160 splice
      • 29.2.161 spit
      • 29.2.162. sprintf
      • 29.2.163 sqrt
      • 29.2.164. srand
      • 29.2.165. stat
      • 29.2.166. study
      • 29.2.167 sub
      • 29.2.168. substr
      • 29.2.169. symlink
      • 29.2.170 syscall
      • 29.2.171 sysopen
      • 29.2.172 sysread
      • 29.2.173 sysseek
      • 29.2.174 system
      • 29.2.175 syswrite
      • 29.2.176 tell
      • 29.2.177. telldir
      • 29.2.177. tie
      • 29.2.179 tied
      • 29.2.180. time
      • 29.2.181 times
      • 29.2.182 tr///
      • 29.2.182 truncate
      • 29.2.183 uc
      • 29.2.184 ucfirst
      • 29.2.186 umask
      • 29.2.186 undef
      • 29.2.188. unlink
      • 29.2.189. unpack
      • 29.2.190. unshift
      • 29.2.190. untie
      • 29.2.190. use
      • 29.2.193 utime
      • 29.2.194. values
      • 29.2.195 vec
      • 29.2.196. wait
      • 29.2.197. waitpid
      • 29.2.198. wantarray
      • 29.2.199 warn
      • 29.2.200. write
      • 29.2.201 y//

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

29.2.136 s///

  • s///

替换操作符。参阅第五章里的“模式匹配操作符”。

29.2.137. scalar

  • scalar EXPR

这个伪函数可以用于 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

  • seek FILEHANDLE, OFFSET, WHENCE

这个函数为 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

  • seekdir DIRHANDLE, POS

这个函数为下一次 readdir 对 DIRHANDLE 的调用设置当前位置。POS 必须是 telldir 返回的 值。这个函数与对应的系统库过程在可能的目录压缩问题上有相同的注意事项。该函数可能不是在 所有实现了 readdir 的系统上都实现了。而且如果 readdir 没有实现,它也肯定没实现。

29.2.140 select (输出文件句柄)

  • select FILEHANDLE
  • 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 RBITS, WBITS, EBITS, TIMEOUT

四个参数的 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

  • semctl ID, SEMNUM, CMD, ARG

这个函数调用 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

  • semget KEY, NSEMS, SIZE, FLAGS

这个函数调用 System V IPC 系统调用 semget(2)。在调用之前,你应该 use IPC::SysV 以 获取正确的常量定义。该函数返回信号灯 ID,或者如果有错误返回 undef。

又见 IPC::Semaphore 模块。这个函数只能在那些支持 System V IPC 的机器上用。

29.2.144 semop

  • semop KEY, OPSTRING

这个函数调用 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 SOCKET, MSG, FLAGS, TO
  • send SOCKET, MSG, FLAGS

这个函数在套接字上发送一条信息。它和同名系统调用接收相同的标志——参阅 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

  • setpgrp PID, PGRP

这个函数为指定的 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

  • setpriority WHICH, WHO, PRIORITY

这个函数为 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

  • setsockopt SOCKET, LEVEL, OPTNAME, OPTVAL

这个函数设置你需要的套接字选项。出错时该函数返回 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

  • shift ARRAY
  • 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

  • shmctl ID, CMD, ARG

这个函数调用 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

  • shmget KEY, SIZE, FLAGS

这个函数调用 System V IPC 系统调用 shmget(2)。该函数返回共享内存段的 ID,如果有错误 则返回 undef。在调用之前,先 use SysV?::IPC。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.152 shmread

  • shmread ID, VAR, POS, SIZE

这个函数从共享内存段 ID 的位置 POS 处开始读取 SIZE 大小的数据(方法是附着在该内存段 上,拷贝出数据,然后与该内存段分离。)。VAR 必须是一个将保存读取出的数据的变量。如果 成功,该函数返回真,如果失败返回假。

该函数只能在那些支持 System V IPC 的机器上用。

29.2.153 shmwrite

  • shmwrite ID, STRING, POS, SIZE

这个函数向共享内存段 ID 的位置 POS 处开始写入 SIZE 大小的数据(方法是附着在该内存段 上,拷贝入数据,然后与该内存段分离。)。如果 STRING 太长,那么只写入 SIZE 字节;如果 STRING 太短,那么在后面补空直到 SIZE 字节。如果成功,该函数返回真,如果有错误,返回假。

该函数只能在那些支持 System V IPC 的机器上用。(你可能都读烦了——我们已经写烦了。)

29.2.154 shutdown

  • shutdown SOCKET, HOW

这个函数以 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 = < SERVER >;    # 但你还可以读

(如果你找到这里是为了找到关闭你的系统的办法,那么你就要执行一个外部的程序干这件事。 参阅 system。)

29.2.155. sin

  • sin EXPR
  • sin

抱歉,这个操作符什么罪都没犯(译注:英文“sin”也有“罪恶”的含义)。它只是返回 EXPR (用弧度表示)的正弦。

如果需要正弦的逆操作,你可以使用 Math::Trig 或者 POSIX 模块的 asin 函数,或者用下面的 关系:

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

29.2.156 sleep

  • sleep EXPR
  • sleep

这个函数令脚本睡眠 EXPR 秒,如果没有 EXPR 则是永久睡眠,并且返回睡眠的秒数。 你可以 通过给该进程发送一个 SIGALRM 的方法来中断睡眠。在一些老式系统里,它可能比你要求的描述 整整少睡一秒,具体情况取决于它是如何计算秒的。大多数现代的系统都是睡足秒数。不过,在 这些系统上它们很有可能睡眠的时间要长一些,因为在一台繁忙的多任务系统上,你的系统可能 无法马上得到调度。如果可能,select (等待文件描述符)调用可以给你更好的分辨率。你还 可以用 syscall 调用一些 Unix 系统支持的 getitimer(2) 和 setitimer(2) 过程。你不应该 混合 alarm 和 sleep 调用,因为 sleep 通常是用 alarm 实现的。

又见 POSIXE 模块的 sigpause 函数。

29.2.157 socket

  • socket SOCKET, DOMAIN, TYPE, PROTOCOL

这个函数打开一个指定类型的套接字,并且把它附着在 SOCKET 文件句柄上。DOMAIN,TYPE,和 PROTOCOL 都是和 socket(2) 一样的声明。如果没有定义 SOCKET,那么 Perl 将自动激活它。 在使用这个函数之前,你的程序应该包含下面这行:

   use Socket;

它给你正确的常量。该函数成功时返回真,参阅在第十六章里的“套接字”节里的例子。

在那些支持对文件的 exec 时关闭(close-on-exec)的系统上,该标记将为新打开的文件描述符 设置,就象 $^F 判定的那样。参阅第二十八章里的 $^F($SYSTEM_FD_MAX)。

29.2.158 socketpair

  • socketpair SOCKET1, SOCKET2, DOMAIN, TYPE, PROTOCOL

这个函数在声明的域中创建一个指定类型的匿名套接字对。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

  • sort USERSUB LIST
  • sort BLOCK LIST
  • sort LIST

这个函数对 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

  • splice ARRAY, OFFSET, LENGTH, LIST
  • splice ARRAY, OFFSET, LENGTH
  • splice ARRAY, OFFSET
  • splice ARRAY

这个函数从一个 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

  • split /PATTERN/, EXPR, LIMIT
  • split /PATTERN/, EXPR
  • split /PATTERN/
  • split

这个函数扫描字串中 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

  • sprintf FORMAT, LIST

这个函数返回一个格式化字串,格式化习惯是 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

  • sqrt EXPR
  • sqrt

这个函数返回 EXPR 的平方根。如果需要其他的根,比如立方根,你可以使用 ** 操作符求那个 数字的分数幂。不要试图在着两种方法里使用负数,因为它有一些稍微有些复杂的问题(并且抛出 一个例外)。但是有一个模块可以处理这些事情:

   use Main::Complex;
   print sqrt(-2); # 打印出 1.4142135623731i

29.2.164. srand

  • srand EXPR
  • 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

  • stat FILEHANDLE
  • stat EXPR
  • 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

  • study SCALAR
  • 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
    • sub NAME ATTRS
    • sub NAME PROTO
    • sub NAME

  • 命名定义 *sub NAME PROTO ATTRS BLOCK
    • sub NAME ATTRS BLOCK
    • sub NAME PROTO BLOCK
    • sub NAME BLOCK

  • 未命名定义
    • sub PROTO ATTRS BLOCK
    • sub ATTRS BLOCK
    • sub PROTO BLOCK
    • sub BLOCK

子过程声明和定义的语法看起来挺复杂的,但是在实践上实际相当简单。所有东西都是基于下面 语法的:

   sub NAME PROTO ATTRS BLOCK

所有的四个域都是可选的;唯一的限制就是如果这些域的确存在的话那么它们必须以这些顺序出现, 并且你必须至少使用 NAME 或者 BLOCK 之一。目前,我们会忽略 PROTO 和 ATTRS;它们只是 基本语法的修饰词。NAME 和 BLOCK 都是保证正确重要部分:

  • 如果你只有 NAME 而没有 BLOCK,它就是一个该名字的声明(并且如果你想调用该子过程,那么你就必须稍后用 NAME 和 BLOCK 提供一个定义。)命名的声明是非常有用的,因为如果编译器知道它是一个用户定义子过程,那么它会对该名字另眼相看。你可以把这样的子过程当作一个函数或者当作一个操作符来调用,就象内建函数一样。有时候我们把这样的东西叫做提前声明。

  • 如果你同时提供了 NAME 和 BLOCK,那么它就是一个标准的命名子过程定义(如果你在前面没有声明,那么它还是声明)。命名定义也很重要,因为 BLOCK 把一个实际的含义(子过程体)和声明关联起来。这就是我们所谓的定义和声明的区别。不过,定义和声明也有类似的地方,那就是子过程代码看不到它,并且它不返回你可以用之来引用子过程的内联的值。

  • 如果你只有 BLOCK 而没有 NAME,那么它就是一个匿名的定义,也就是一个匿名子过程。因为它没有名字,所以它根本就不是声明,而是一个真正的操作符,在运行时返回一个指向匿名子过程体的引用。这个东西在把代码当作数据对待的时候极为有用。它允许你传递一段奇怪的代码用做回调函数,并且甚至还可以当作闭合块用——如果该 sub 定义操作符提到了任何在其自身以外的词法变量的话。这就意味着对同样 sub 操作符的不同调用都将进行记录工作,以保证在闭合块的生命期里,每个这样的词法变量的正确的“版本”都是该闭合块可见的,甚至于该词法变量所属的最初的范围被摧毁了也如此。

在上面三种情况中的任何一种里,PROTO 和 ATTRS 之一或者全部都可以在 NAME 之后和/或 BLOCK 之前出现。原型是一个放在圆括弧里的字符列表,它告诉分析器如何对待该函数的参数。 属性是用一个冒号引入的,它告诉分析器有关这个函数的额外的信息。下面是一个包含四个域的 典型的定义:

   sub numstrcmp ($$) : locked {  my ($a, %b) = @_;  return $a  $b || $a cmp %b;
   }

有关属性列表和它们的操作的细节,请参阅第三十一章里的 attributes 用法。又见第六章和第八章的“匿名子过程”。

29.2.168. substr

  • substr EXPR, OFFSET, LENGTH, REPLACEMENT
  • substr EXPR, OFFSET, LENGTH
  • substr EXPR, OFFSET

这个函数从 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

  • symlink OLDNAME, NEWNAME

这个函数创建一个新的文件,该文件是是指向一个旧文件的符号链接。此函数成功时返回真,否则 返回假。在那些不支持符号链接的系统上,它在运行时抛出一个例外。要想检查这个例外,你可以 用 eval 捕获所有可能的错误:

   $can_symlink = eval  { symlink("", ""); 1 };

或者使用 Config 模块。要注意的是如果你提供了一个相对符号链接,那么它会被解释成相对于 该符号链接本身的路径,而不是相对于你的当前工作目录。

又见本章早些时候的 link 和 readlink。

29.2.170 syscall

  • syscall LIST

这个函数调用列表的第一个元素声明的系统调用(意思就是一次系统调用,而不是一个 shell 命令 ),同时把列表中其他元素作为参数传递给系统调用。(现在,许多这些调用可以通过 POSIX 模块 更容易地使用。)如果 syscall(2) 未实现,那么该函数抛出一个例外。

这些参数按照下面的方式解释:如果一个给定的参数是数字,那么该参数就作为一个 C 整数传递。 如果不是,那么就传递一个指向该字串值的指针。你有责任确保这个字串足够长,以便能够接收 任何可能写到它里面去的结果;否则,你就等着核心倾倒吧。你不能拿一个字串文本(或者其他 只读的字串)当作给 syscall 的参数,因为 Perl 已经假定任何字串指针都是可写的。如果你的 整数参数不是文本并且在数字环境里从不会被解释,那么你可能需要给它们加 0 以强迫它们看起来 象数字。

syscall 返回被调用的系统调用返回的任何数值。在

第二十八章 特殊名字


第二十八章,特殊名字

本章是关于那些对 perl 有特殊含义的变量的。大多数这些变量都有合理的可记忆性,或者模拟 某一种(或两种) shell 的变量。但是如果你想把长变量名当作同义词来用,只需要在你的程序 顶部说:

use English;

这样就在当前包里把所有短名字作成长名字的别名。这些变量中有一些甚至还有中间名字,通常是 从 awk 借来的。大多数人最终都使用短名字,至少那些常用的变量是这样的。在本书全部内容中, 我们统一使用短名字,不过也经常提到长名字(在圆括弧里)这样你就可以很容易地在本章中找到 它们。

这些变量的语意可能相当神奇。(要创造自己的神奇,参阅第十四章,捆绑变量。)有少数几个 变量是只读的。如果你试图给它们赋值,那么会抛出一个例外。

在随后的内容里,我们将先提供一个 perl 赋予了特殊含义的变量和函数的简明列表,按照类型 分组,这样你就可以查找那些你不太能确定正确名字的变量。然后我们将按照它们正确名字(或者 它们的不那么正确的名字)的字母顺序解释所有这些变量。

*按照类型分组的特殊名字

我们这里用的“类型”这个词的含义比较宽松——本节在这里实际上更多地是按照范围来对变量 分组,也就是说,它们在哪里是可见的。

**正则表达式特殊变量

下面的与模式匹配相关的特殊变量在整个发生模式匹配的动态范围内都可见(除了 $* 之外,它现 在是废弃了的)。换句话说,它们的行为就好象它们是用 local 声明的一样,这样你就不用自己 那样定义它们了。参阅第五章,模式匹配。

$* $digits @+ (@LAST_MATCH_END) @- (@LAST_MATCH_START) $+ ($LAST_PAREN_MATCH) $^R ($LAST_REGEXP_CODE_RESULT) $& ($MATCH) $'($POSTMATCH) $`($PREMATCH)

**每个文件句柄的相关变量

这些特殊变量从不需要在 local 里提到,因为它们总是指向一些属于当前选出的输出文件句柄的 数值 —— 每个文件句柄保存它自己的数值集。但你 select 另外一个文件句柄时,老文件句柄 保存这些变量原来的数值,而这些变量现在反映的是这个新文件句柄的数值。又见第三十二章, 标准模块,里的 FileHandle? 模块。

$| ($AUTOFLUSH, $OUTPUT_AUOTFLUSH) $- ($FORMAT_LINES_LEFT) $= ($FORMAT_LINES_PER_PAGE) $~ ($FORMAT_NAME) $% ($FORMAT_PAGE_NUMBER) $^ ($FORMAT_TOP_NAME)

**每个包里的特殊变量

这些特殊变量在每个包里分开存在。你没必要把它们局部化,因为 sort 在 $a 和 $b 上自动做 这些事情,并且其他的变量最好是放在那里别动(尽管如果你 use strict 的话,你可能需要用 out 声明它们)。

$a $b @EXPORT @EXPORT_OK %EXPORT_TAGS %fields @ISA %OVERLOAD $VERSION

**程序范围的特殊变量

这些变量在所有意义上都完全是全局的——也就是它们在每个包里的含义都是一样的,因为如果你 不给他们加任何修饰的话,那么它们全部被强制进入到 main 包中(除了 @F 以外,它在 main 里是特殊的,但并不强制)。如果你想用这些变量之一的临时拷贝,那么你必须在当前动态范围里 把它局部化。

%ENV $< ($UID, $REAL_USER_ID) %INC $> ($EUID, $EFFECTIVE_USER_ID) ...(略)

**每个包一个的文件句柄

除了 DATA 之外(它总是每个包一个),如果下面的文件句柄没有用其他包名字修饰的话,那就 总是假设在 main 里:

_(下划线) ...(略)

**每个包一个的特殊函数

下面的子过程名字对 Perl 有特殊含义。它们总会因为某些事件而隐含地调用,这些事件可以是 访问一个捆绑的变量或者试图调用一个未定义的函数等等。我们不会在本章描述它们,因为它们在 本书的其它部分已经多次提到了。

未定义函数调用拦截器(参阅第十章,包)

AUTOLOAD

垂死的对象的终结(参阅第十二章,对象):

DESTROY

例外对象(参阅下一章的 die):

PROPAGATE

自动初始化和自动清理函数(参阅第十八章,编译):

BEGIN,CHECK,INIT,END

方法(参阅第十四章):

BINMODE,CLEAR, ...(略)

**按照字母顺序排列的特殊变量

我们根据长变量名的字母顺序排列了这些变量。如你不知道这些变量的长名字,你可以在前一节里 找到它们。(没有字母名字的变量在前面排列。)

所以这里我们就不用再次重复了,每个变量的描述以下面的注解中的一个或多个开头:

注解 | 含义

XXX | 废弃了,不要在任何新程序里再用
NOT | 并不正式存在(只用于内部)
ALL | 真正的全局量,由所有包共享
PKG | 包全局变量;每个包都有自己的一套
FHA | 文件句柄属性;每个 I/O 对象一个
DYN | 自动处于动态范围内(隐含 ALL)
LEX | 编译时的词法范围
RO | 只读,如果你修改它会抛出一个例外

如果列出了多于一个变量名或者符号,那么缺省时只有短名字可以使用。使用 English 模块可以 让当前包里可以用长的同义词,但只有当前包可以用,即使该变量标记着 [ALL] 也如此。

形如 method HANDLE EXPR 这样的记录显示了那些每个文件句柄变量一个的面向对象的接口, 这些变量是 FileHandle? 和各种 IO:: 模块提供的。(如果你愿意,你还可以使用 HANDLE->method(EXPR) 表示法。)这些变量让你在检查或者改变该变量之前可以避免调用 select 来改变缺省输出句柄。每个这样的句柄都返回 FileHandle? 属性的旧数值;如果提供了 EXPR,那么就会设置一个新值。如果没有提供 EXPR,大多数这些方法对当前变量不做任何事情 ——除 autoflush 外,它会假设一个参数 1,以示区别。

_(下划线) [ALL] 这是一个特殊的文件句柄,用于缓存上一次成功的 stat,lstat,或者文件测试 操作符(比如 -w $file 或 -d $file)的信息。

$digits [DYN, RO] 编号变量 $1,$2,等等(数字可以大到你想要的任何数目)(注:尽管许多 正则表达式引擎只能支持最多九个后引用,Perl 可没有这样的限制,所以如果你用到$768,Perl 也不会在意,只不过是如果你在正则表达式里用了这么多圆括弧的话, 你的代码的维护人可能会介意。),它们包含在当前活跃动态范围里上一次模式匹配中 对应的圆括弧集的匹配文本。(记忆方法:象 \digits。)

$[ [XXX,LEX] 数组中的第一个元素的索引和子字串中第一个字符。缺省是 0,不过我们通常 设置为 1,这样 Perl 在处理脚标和计算 index 和 substr 函数时的行为就更象 awk (或者 FORTRAN)。因为后来我们发现这么干很危险,所以现在给 $[ 赋值被当作是词法 范围里编译器的指示器,并且不能影响任何其他文件的行为。(记忆方法:[ 开始一个 下标。)

$# [XXX,ALL] 不要用这个变量了,用 printf 代替。$# 包含打印出去的数字的输出格式, 它是我们想模拟 awk 的 OFMT 变量的有因无果的一个东西。(记忆方法:# 是数字符号, 不过如果你很聪明,那么就忘掉它好了,这样你就不会因把你的程序搞得乱七八糟而苦恼。 )

$* [XXX,ALL] 哇,三个废弃了的变量在一行里!这个东西可以(但是不应该)设置为真, 好让 Perl 对每个没有明确 /s 的模式匹配都假设一个 /m。(记忆方法:* 匹配多个 东西。)

$a [PKG] 这个变量被 sort 函数用于保存需要比较的每一对数值的第一个($b 是每一对的 第二个)。$a 所在的包就是 sort 操作符编译进去的那个包,这个包不一定和它的比较 函数编译进去的那个包一样。这个变量在 osrt 比较块里会被隐含的局部化。因为它是 一个全局量,所以它不会让 use strict 发出警告。因为它是实际数组数值的一个别名, 所以你可能会认为你可以改变它,但是你不能,参阅 sort 函数。

$ACCUMULATOR $^A [ALL] write 累积器写 format 行的当前值。一个格式包含把它们的结果放到 $^A 中去 的 formline 命令。在调用完它的格式之后,write 把 $^A 的内容打印出来然后清空 它。所以你从来看不到 $^A 的内容,除非你自己调用 formline 并且查看它。参阅formline 函数。

$ARG $_ [ALL] 缺省的输入和模式搜索空间。下面各对是相等的:

while(<>) {...} # 只是在没有修饰的 while 测试中相等 while (defined($_ = <>)) { ... }

...(略)

下面是一些在你没有声明需要操作的东西的时候,Perl 会假设是 $_ 的地方:

* print 和 unlink 这样的列表函数,以及象 ord,pos,和 int 这样的单目函数, 还有所有文件测试(除了 -t 以外,它的缺省是 STDIN)。所有缺省是 $_ 的函数也在 第二十九章,函数,里做了标记。

* 模式匹配操作符 m// 和 s//,以及没有使用 =~ 操作符时的转换操作符 y/// 和tr///。

* 在 foreach 循环里,如果没有提供其他变量,那么遍历变量缺省也是 $_(甚至在用for 或者用做一个语句修饰词的时候也是它)。

* 在 grep 和 map 函数里的隐含的遍历变量。(在那里没有办法用其他变量干这件事。)

* 如果一个 ,readline,或者 glob 操作符的结果本身作为 while 测试的唯一 标准的时候,缺省的放输入记录的地方也是 $_。这个赋值不是在 while 测试外部 发生的,如果在 while 表达式里还有任何其他的元素,也不是在 while 测试外面赋值。

(记忆方法:下划线(underline)是某种操作下(underlying)的操作数。 译注:英文 underlying 的意思是“当前...的”,和英文的下划线"underline"同音。)

@ARG @_ [ALL] 在一个子过程里,这个变量保存传递给子过程的参数列表。参阅第六章,子过程。 在标量环境里的分割可以把这个数组分割开,但是这个用法已经废弃了。

ARGV [ALL] 特殊的文件句柄,它遍历在 @ARGV 里的所有命令行文件名。通常写成尖角操作符 里的空文件句柄:<>。

$ARGV [ALL] 当使用 <> 或者 readline 操作符从 ARGV 句柄里读取数据的时候,包含当前 文件名。

@ARGV [ALL] 这个数组包含传递给脚本的命令行参数。请注意 $#ARGV 通常是参数的数目减一, 因此 $ARGV[0] 是第一个参数,而不是命令名;用 scalar @ARGV 获取程序参数的数目。 从 $0 中获得文件名。

ARGVOUT [ALL] 当你在 -i 开关或者 $^I 变量下处理 ARGV 句柄时使用这个特殊的文件句柄。 参阅第十九章,命令行接口。

$b [PKG] 这个变量和 $a 一起用于在 sort 里做比较。参阅 $a 和 sort 函数获取细节。

$BASETIME $^T [ALL] 该脚本开始运行的时间,是自纪元以来的秒数(对于 Unix 系统而言是 1970 年 开始)。-M,-A 和 -C 文件测试返回的数值是与这个时间的相对值。

$CHILD_ERROR $? [ALL] 上一次管道关闭,反勾号(``)命令或者 wait,waitpid,或者 system 函数 返回的状态。请注意它不仅仅是简单的退出代码,而是由下层的 wait(2) 或者waitpid(2) 系统调用返回的完整的 16 位状态。因此,子过程的退出值在高位,也就是 $? >> 8;在低位,$? & 127 告诉你该进程是因为哪个信号(如果有)退出的,而$? & 128 汇报该进程的死亡是否产生一个内核的倾倒。(记忆方法:类似 sh 和其 变体的 $? 。)

在 END 块里,$? 包含将要赋值给 exit 的数值。你可以在 END 里修改 $? 的值来改变 脚本的退出状态。

在 VMS 里,这个用法使用 use vmsish 'status' 令 $? 反映真正的 VMS 的退出状态, 而不是缺省的 POSIX 状态的仿真。

$COMPILING $^C [ALL] 与 -c 开关相关联的内部标记的当前数值,主要是和 -MO 和 perlcc(1) 工具 一起使用,好让代码在为代码生成编译的时候修改自身的行为。比如,你可能想在编译 时就 AUTOLOAD,而不是使用普通的延迟装载,这样代码就可以马上生成。参阅第十八章。

DATA [PKG] 这个特殊的文件句柄指向当前文件中任何跟在 END 或者 DATA 记号后面 的东西。__END__ 记号总是打开 main::DATA 文件句柄,因此它是在主程序中使用。 DATA 记号在任何当前起作用的包中打开 DATA 句柄,因此不同的模块可以有自己的 DATA 文件句柄,因为(可以假定)它们有不同的包名字。

$DEBUGGING $^D [ALL] 内部调试标志的当前值,是从命令行上的 -D 开关设置的;参阅第十九章获取 它的位数值。(记忆方法:-D 开关的数值。)

$EFFECTIVE_GROUP_ID $EGID $) [ALL] 该进程有效的 GID(组 ID)。如果你用的机器同时支持多个组的成员关系,那么 $) 给出了你所处的一个用空格分隔的组的列表。第一个数字是 getegid(2) 的返回,而随后 的是 getgroups(2) 的返回,其中之一可能与第一个数字相同。

类似地,给 $) 的赋值也必须是一个空格分隔的数字列表。第一个数字用于设置有效的GID,其他的传递给 setgroups(2) 系统调用。要获得给 setgroups 一个空列表的 效果,你只需要重复新的有效 GID;比如,强制一个有效 GID 为 5 和一个空的有效setgroups 列表,说:

$) = "5 5";

(记忆方法:圆括弧是用于给东西分组的。如果你运行在 setgid 下,有效的 GID 是你 正(right:英文中与“右”同形)处在的组)注意:$,$(,和 $) 只能在那些 支持对应的系统 set-id 过程的机器上使用。$( 和 $) 只能在那些支持 setregid(2)的机器上对换使用。

$EFFECTIVE_USER_ID $EUID $> [ALL] 这个进程的有效的 UID,和 geteuid(2) 系统调用返回的一样。例子:

$< = $>; # 把真实 uid 设置为有效 uid ($) = ($>,$<); # 交换真实和有效 uid

(记忆方法:如果你在 setid 下运行,它是你前往的 UID。)注意:$< 和 $> 只能在那些 支持 setreuid(2) 的机器上交换使用。而且有时候甚至那些支持的机器也不能交换。

%ENV [ALL] 包含你的当前环境变量的散列。在 %ENV 里设置一个数值同时改变了你的进程和 在这个赋值之后派生的子进程的环境。(在任何类似 Unix 的系统里,它都不能改变 父进程的环境。)

$ENV{PATH} = "/bin:/usr/bin"; $ENV{PAGER} = "less"; $ENV{LESS} = "MQeicsnf"; # 我们最喜欢的给 less(1) 的开关 system "man perl"; # 选择新的设置

要从你的环境中删除内容,请记住在散列数值上使用 delete 函数而不是 undef 。

请注意作为 crontab(5) 记录运行的进程继承了一个非常贫瘠的环境变量集。(如果你的 程序在命令行上运行得很好但在 cron 下运行的很糟,那么原因可能就是这个。)还要 注意,如果你在作为一个 setuid 脚本运行,你还应该设置 $ENV{PATH},$ENV{shell}, $ENV{BASH_ENV},和 $ENV{IFS}。参阅第二十三章,安全。

$EVAL_ERROR $@ [ALL] 上一次 eval 操作抛出的当前例外或者 Perl 语法错误信息。(记忆方法:语法 错误“at”(在)哪里?(译注:@ 是“at”的意思。))和 $! ($OS_ERROR)不同, 它是在失败的时候设置的,但是成功的时候并不清除,而如果上一次 eval 有编译错误 或者运行时例外的时候,$@ 是保证要设置的(为一个真值),并且如果没有这些问题 发生,也是保证被清除的(成一个假值)。这个变量里不会收集警告信息。不过,你可以 通过设置一个路径处理警告,方法是按照我们本节 稍后描述的方法设置$SIG{__WARN__}。

请注意 $@ 的数值可能是一个例外对象而不是一个字串。如果是这样,而且该例外对象 有为其对象类定义的字串化重载的话,你可能仍然可以把它当作字串对待。如果你通过 说:

die if $@;

传播一个例外,那么一个例外对象就会调用 $@->PROPAGATE 以查找要做的动作。(一个 字串例外只是向该字串增加一个“propagated at”行。)

$EXCEPTIONS_BEING_CAUGHT $^S [ALL] 这个变量反映解释器的当前状态,如果是在一个 eval 里面,则返回真,否则返回 假。如果当前的编译单元尚未完成,那么它就是未定义,这种情况可能是在$SIG{__DIE__} 和 $SIG{__WARN__} 文件句柄里的情况。(记忆方法:eval 的状态。)

$EXECUTABLE_NAME $^X [ALL] perl 二进制文件本身执行的名字,来自 C 的 argv[0]。

@EXPORT[PKG] Exporter 模块的 import 方法会查看这个数组变量,寻找当这个模块被 use 的 时候,或者使用了 :DEFAULT 输入标记的时候,缺省时需要输出的其他包变量和子过程。 它无法免除 use strict 的检查,所以,如果你打开了 use strict 用法,那么你必须 用 our 声明它或者使用带包名字的全称。不过,那些以字串“EXPORT”开头的所有变量 如果只使用一次的话,都免于警告的检查。参阅第十一章,模块。

@EXPORT_OK [PKG] Export 模块的 import 方法会查看这个变量以判断一个请求的输入是否合法。 它不能免于 use strict 的检查。参阅第十一章。

%EXPORT_TAS [PKG] 当用户请求一个前导冒号的输入符号的时候,比如 use POSIX ":sys_wait_h", Exporter 的 import 方法会检查这个散列变量。这时候键字是这个冒号标签,但是没有 前面的冒号。当请求这个冒号标签的时候,其值应该是一个包含将要输入的符号的数组 引用,所有这些变量也必须出现在 @EXPORT 或者 @EXPORT_OK 里。它不能免于use strict 的检查。参阅第十一章。

$EXTENDED_OS_ERROR $^E [ALL] 与当前操作系统相关的错误信息。在 Unix 里,$^E 等效于 $!($OS_ERROR), 但它在 OS/2,VMS,和 Microsfot 系统,以及 MacPerl? 里是不一样的。参考你的 Perl 版本获取详细描述。在 $! 里提到的注意事项通常也适用于 $^E。(记忆方法: 额外的错误解释。)

@F [PKG] 如果给出了 -a 命令行开关,它就是存放分裂后的输入行数据域的数组。如果没有 使用 -a 选项,这个数组就没有特殊含义。(这个数组实际上只是 @main::F,并且马上 就会在所有其他包中消失。)

%FIELDS [NOT,PKG] 这个散列是被 use fields 用法在内部使用的,用于判断目标散列的当前 合法的字段。参阅第十二章里的, use fields,use base,和“用 use fields定义的域”。

format_formfeed HANDLE EXPR $FORMAT_FORMFEED $^L [ALL] 在 write 函数写一个表格的头部之前它隐含输出的用于换页的东西。缺省是 “\f”。

format_lines_left HANDLE EXPR $FORMAT_LINES_LEFT $- [FHA] 当前选定的输出句柄在页面上留下的行数,与 format 声明和 write 函数一起 使用。(记忆方法:lines_on_page - lines_printed。)

format_lines_per_page HANDLE EXPR $FORMAT_LINES_PER_PAGE $= [FHA] 当前选定的输出句柄的当前页长(可打印行数),和 format 和 write 一起 使用。缺省是 60。(记忆方法:= 是水平行数。)

format_line_break_characters HANDLE EXPR $FORMAT_LINE_BREAK_CHARACTERS $: [ALL] 在一个格式里,如果一个字串需要断开以保持连续域(以 ^ 开头),这时候用的 断开字符集。缺省是“ \n-”,用来在空白或者连字符上断开。(记忆方法:冒号在诗歌 里是一个技术词汇,表示一行的一部分。现在你只需要记住这个方法就可以了...)

format_name HANDLE EXPR $FORMAT_NAME $~ [FHA] 当前选定的输出句柄的当前报表格式的名字。缺省是该文件句柄的名字(记忆方法: 在 $^ 后面拐个弯。)

format_page_number HANDLE EXPR $FORMAT_PAGE_NUMBER $% [FHA] 当前选定的输出句柄的饿当前页码,与 format 和 write 一起使用。(记忆 方法:% 是 troff(1) 里的页码寄存器。什么!你不知道 troff 是什么?)

format_top_name HANDLE EXPR $FORMAT_TOP_NAME $^ [FHA] 当前选定输出句柄的页顶格式的名字。缺省是后缀 _TOP 的文件句柄的名字。 (记忆方法:指向页面顶部。)

$^H [NOT,LEX] 这个变量为 Perl 分析器保存词法范围里的状态位(也被叫做“暗示”)。 这个变量是严格地只在内部使用的。它的可用性,行为,和内容都是可能在不加任何通知 的情况下修改的。如果你动了他,那么你就会患上科学家未知的某种可怕的热带疾病而 完蛋。(记忆方法:我们 不想给你提示。)

%^H [NOT,LEX] %^H 散列提供和 $^H 一样的词法范围的语意,这样它就可以用于实现词法 范围的程序。请阅读在 $^H 里列出的可怕的警告,并且我们还要加上一条,就是这个 变量仍然是实验性的。

%INC [ALL] 这个散列包含每个 Perl 文件通过 do FILE,require,或 use 装载的文件名。 键字是你声明的文件名,而数值是实际找到该文件的位置。require 操作符使用这个数组 判断某个文件是否已经被 装载了。比如:

%perl -MLWP::Simple -le 'print $INC{"LWP/Simple.pm"}' /opt/perl/5.6.0/lib/site_perl/LWP/Simple.pm

@INC [ALL] 这个数组包含一个目录的列表,Perl 模块可能通过 do FILE,require,或 use 寻找这些目录。它初始的时候包含任何 -I 命令行开关和在 PERL5LIB 环境变量里的 目录,后面跟着缺省的 Perl 库,比如:

...(略)

最后跟着“.”,代表单前目录。如果你需要从你的程序里面修改这个列表,试着使用use lib 用法,它不仅在编译时修改该变量,而且还追加任何相关的体系相关的目录 (比如那些包含 XS 模块使用的共享库的目录):

use lib "/mypath/libdir/"; use SomeMod?;

$INPLACE_EDIT $^I [ALL] 现场编辑扩展的当前值。使用 undef 关闭现场编辑。你可以在你的程序内部使用 这个变量以获取和 -i 开关提供的相同的行为。比如,要实现和下面命令相同的功能:

%perl -i.orig -pe 's/foo/bar/g' *.c

你可以在你的程序里使用等效的代码:

local $^ = '.orig'; local @ARGV = glob("*.c"); while(<>) {s/foo/bar/g;print; }

(记忆方法:-i 开关的值。)

$INPUT_LINE_NUMBER $NR $. [ALL] 你最后读取(或者在其上调用了 seek 或 tell )的文件句柄的当前记录数目 (通常是行数)。该值可能与文件中实际的物理行数不同——取决于实际有效的“行”的 概念是什么——参阅 $/ ($INPUT_RECORE_SEPARATIO)获取如何影响该行为的方法。 在文件句柄上的明确的关闭动作将重置这个行数。因为 <> 从来不会做明确关闭动作, 所以行数在 ARGV 文件间递增(但是请参阅 eof 里的例子)。局部化 $. 同时也局部化 了 Perl 的“最后阅读的文件句柄”的概念。(记忆方法:许多程序使用“.”表示当前 行数。)

$INPUT_RECORD_SEPARATOR $RS $/ [ALL] 输入记录分隔符,缺省是换行符,readline 函数, 操作符,和 chomp 函数 会使用它。它的作用类似 awk 的 RS 变量,并且如果你把它设置为空字串,它就把一个 或者多个空白行当作一个记录终止符。(不过空白行必须没有任何隐藏的空格或水平 制表符。)你可以把它设置为一个多字符字串以匹配一个多字符终止符,但是你不能为 一个模式设置它—— awk 怎么着也会在某些事情上有些优点。

请注意,如果一个文件包含连续的空白行,那么把 $/ 设置为“\n\n”的含义和把它设置 为“”略有区别。把它设置为“”将把两个或者更多的连续空白行当作一个空白行。而 设置为“\n\n”意味着 Perl 将盲目地把第三个换行当作下一段的内容。

完全取消 $/ 的定义将令下一行的输入操作把剩余的文件内容当作一个标量数值一次吃进:

undef $/; # 打开全文件模式 $_ = ; # 现在整个文件在此 s/\n[ \t]+/ /g; # 删除缩进的行

如果你在 $/ 是未定义的时候使用 while(<>) 构造来访问 ARGV 句柄,那么每次读取都 会读取下一个文件的内容:

undef $/; while(<>) { # $_ 里有下一个文件的所有内容... }

尽管我们在上面使用了 undef,但是更安全的做法是用 local 解除一个 $/ 的定义:

{ local $/;$_ = ; }

如果把 $/ 设置为一个引用,该引用指向一个整数,或者一个保存着整数数值的标量, 或者一个可以转换成一个整数的标量,那么都会令 readline 和 操作符读取固定 长度的记录(其最大记录长度是引用的整数数值),而不是读取由特定字串终止的变长 记录。所以:

$/ = \32768; # 或者 \"32768" 或者 \$scalar_var_containing_32768 opoen(FILE, $myfile); $record = ;

将从 FILE 句柄里读进不超过 32,768 字节的一条记录。如果你不是从一个面向记录的 文件里读取(或者你的操作系统里没有面向记录的文件),那么你就很有可能每次读取 动作都获取一整段数据。如果一条记录比你设置的记录尺寸还要大,那么你就会分段地 获得该记录。只有那些标准的 I/O 支持 read(3) 函数的系统上,记录模式才能和行 模式混合良好;VMS 就是一个显著的例外。

当 $/ 设置成打开记录模式——或者它被解除定义的时候,调用 chomp 没有任何作用。 又见第十九章里的命令行开关 -0(数字零)和 -l(字母 L 的小写)。(记忆方法: 当你引用诗歌的时候,/ 用于分隔行。)

@ISA [PKG] 如果在当前包中不能找到一个方法调用的时候,这个数组包含需要查找的其他包的 名字。也就是说,它包含该包的基础类。use base 用法隐含地这些这个变量。它不能 免除 use strict 的检查。参阅第十二章。

@LAST_MATCH_END @+ 这个数组保存在当前活跃的动态范围里,与上一次的成功子匹配的结尾之间的偏移量。 $+[0] 是与整个匹配结尾之间的偏移量。这个值与你对正在匹配的变量调用 pod 函数 返回的数值是一样的。(在我们说“与结尾之间的偏移量”的时候,我们实际的意思是与 跟在刚匹配完的东西后面第一个字符之间的偏移,这样我们就可以从开头偏移中减去结尾 偏移,并得到长度。)这个数组的第 n 个元素保存第 n 个子匹配的偏移量,所以 $+[1] 就是 $1 结束的偏移量,$+[2] 就是 $2 结束的偏移量,等等。你可以使用 $#+ 判断上 一次成功的匹配里有多少个子组。参阅 @-(@LAST_MATCH_START)。

在对某变量 $var 的一次成功的匹配之后:

* $` 与 substr($var, 0, $-[0]) 相同 * $& 与 substr($var, $-[0], $+[0] - $-[0]) 相同 * $' 与 substr($var, $+[0] ) 相同 * $1 与 substr($var, $-[1], $+[1] - $-[1]) 相同 * $2 与 substr($var, $-[2], $+[2] - $-[2]) 相同 * $3 与 substr($var, $-[3], $+[3] - $-[3]) 相同,以此类推。

@LAST_MATCH_START @- [DYN,RO] 这个数组保存在当前活跃的动态范围里,与上一次的成功子匹配的开头之间的 偏移量。$-[0] 是与整个匹配开头之间的偏移量。这个数组的第 n 个元素保存第 n 个 子匹配的偏移量,所以 $-[1] 就是 $1 开头的偏移量,$-[2] 就是 $2 开头的偏移量, 等等。你可以使用 $#- 判断上一次成功的匹配里有多少个子组。参阅@+(@LAST_MATCH_END)。

$LAST_PAREN_MATCH $+ [DYN,RO] 这个变量返回在当前活跃动态范围里,上一次成功模式的最后一个圆括弧 子匹配。如果你不知道(或者不关心)一套候选模式中的哪个匹配上了,那么它就很有用。 (记忆方法:要积极(译注:positive,英文“+”号的单词)并且向前看。)例子:

$rev = $+ if /Version: (.*)|Revision: (.*)/;

$LAST_REGEXP_CODE_RESULT $^R [DYN] 这个变量包含在一个有 (?{ CODE }) 构造的成功匹配的模式里面,最后一段 代码的执行结果。$^R 给予你一个执行代码并且把执行结果记住为稍后的模式使用的 方法,或者甚至是模式之后使用。

当 Perl 的正则表达式引擎在模式中移动的时候,它可能多次碰到 (?{CODE}) 表达式。 在引擎的处理过程中,它记住每个 $^R 的数值,因此,如果它在后面必须回朔一个 表达式,它就可以恢复前面一个 $^R 的数值。换句话说,$^R 在模式里有一个动态 范围,很象 $1 及其同党。

因此 $^R 并不只是在一个模式里执行的最后一段代码的结果。它是导致成功匹配的最后 一段代码的结果。一个必然的推论就是如果该匹配没有成功,那么 $^R 就会恢复成在 模式匹配发生之前它的数值。

如果 (?{ CODE }) 模式直接作为一个 (?(COND) IFTRUE|IFFALSE) 的条件作用,那么 不会设置 $^R。

$LIST_SEPARATOR $" [ALL] 如果一个数组或者数组片段被代换到一个双引号字串里(或者类似的东西),这个 变量就是在独立的元素之间要放的字串。缺省是一个空格。(记忆方法:显然,我们希望 是这样。)

$^M [ALL] 缺省时,用光内存是不可捕获的。不过,如果你的 perl 编译成利用 $^M 的 优点,那么你可以把它用做一个应急存储器池。如果你的 Perl 是带着-DPERL_EMERGENCY_SBRK 并且使用 Perl 的 malloc,那么:

$^M = 'a' x (1<<16);

将分配一个 64K 的缓冲区用于紧急状态。参阅 Perl 源程序目录里的 INSTALL 文件 获取如何打开这个选项的信息。

为了让你小心地使用这个高级特性,我们给你设置了一些障碍:这个变量没有use English 长名字(并且我们也不会告诉你怎么记忆。)

$MATCH $& [DYN,RO] 在当前活跃的动态范围里,这个变量里是上一次成功的模式匹配匹配的字串。 (记忆方法:就象在一些编辑器里的 & 一样。)

$OLD_PERL_VERSION $] [ALL] 返回版本号+补丁级别/1000。你可以用它在脚本的开始处判断执行该脚本的Perl 解释器的版本是否落在正确的范围里。(记忆方法:这个版本的 Perl 在右方括弧 里吗?)例子:

warn "no checksumming!\n" if $] < 3.019; die "Must have prototyping available\n" if $] < 5.003;

又见 use VERSION 和 require VERSION 的文档,寻找一种当 Perl 解释器版本太老的 时候的便利的失效方法。参阅 $^V 获取更灵活的 Perl 版本的 UTF-8 的表现形式。

$OSNAME $^O [ALL] 这个变量包含当前 perl 二进制文件为之编译的平台的名字(通常是操作系统名字 )。它是从 Config 模块中抽取同样信息的低成本替代品。

$OS_ERROR $ERRNO $! [ALL] 如果在一个数字环境中使用,产生上一次系统调用错误的当前值,适用所有常见 注意事项。(这就意味着除非你拿到的某个返回值就表明是一次系统错误,否则你不能 倚赖 $! 的值做任何事情。)如果你在一个字串环境里使用它,$! 就生成对应的系统 错误字串。比如,如果你希望 $! 返回某特定错误的字串,或者你想为 die 设置退出 值,那么你可以给 $! 赋予一个错误数字。又见第三十二章的 Errno 模块。(记忆方法: 刚才错误是什么?)

%OS_ERROR %ERRNO %! [ALL] 这个散列只有在你装载了第三十二章里描述的 Errno 模块之后才定义。一旦你 做了装载工作,那么你就可以给 %! 一个特定的错误字串做脚标,并且只有它是当前 错误的时候,其值才为真。比如,只有 C 的 errno 变量当前设置为 C #define 值, ENOENT 的时候,$!{ENOENT} 才为真。这样就可以很方便地访问与供应商相关的符号。

autoflush HANDLE EXPR $OUTPUT_AUTOFLUSH $AUTOFLUSH $| [FHA] 如果设置为真,则强制当前选定的输出句柄在每次 print,printf,和 write之后都进行缓冲区冲刷。(我们把这个动作称做命令缓冲。与流行的看法相反,设置这个 变量并不关闭缓冲。)缺省是假,在许多系统上这就意味着如果输出是到终端的,那么STDOUT 就是行缓冲,否则就是块缓冲,甚至在管道和套接字上也是如此。如果你在向 一个管道输出的时候,(比如你通过 rsh(1) 运行一个 Perl 脚本并且想在脚本运行的 时候就可以看到其输出。)那么设置这个变量就非常有用。当你设置这个变量为真的 时候,如果你的当前选定文件句柄的输出缓冲区里有挂起的为冲刷的数据,那么该缓冲区 就会被立即冲刷,这是赋值动作的副作用。参阅单参数形式的 select ,获取一个控制STDOUT 以外的文件句柄的缓冲的例子。(记忆方法:当你想让你的管道不停泵取数据的 时候。)

这个变量对输入缓冲没有任何影响;为了影响输入缓冲,参阅第二十九章的 getc 或者在 第三十二章 POSIX 模块里的例子。

$OUTPUT_FIELD_SEPARATOR $OFS $, [ALL] 用于 print 的输出字段分隔符(实际上是结束符)。通常,print 只是简单地 打印出你声明的列表元素,在它们中间不加任何东西。按照设置 awk 的 OFS 变量的 方法设置这个变量,声明你想在字段之间打印什么东西。(记忆方法:当你的 print语句里有一个“,”的时候打印的东西。)

$OUTPUT_RECORD_SEPARATOR $ORS $\ [ALL] 用于 print 的输出记录分隔符(实际上是结束符)。通常,print 只是简单地 打印出你声明的逗号分隔的字段,并不假设任何结尾的新行或者记录分隔符。按照设置awk 的 ORS 变量的方法设置这个变量,声明在 print 结束的时候打印什么东西。 (记忆方法:你设置 $\ 代替在 print 结尾追加 “\n”。同样,它也很象 /,但它是 你从 Perl 里“拿回来”的东西。)又见第十九章里的 -l(是“line”的意思)命令行 开关。

%OVERLOAD [NOT,PKG] 这个散列的记录是 use overload 用法在内部设置的,用于为当前包的类 对象实现操作符重载。参阅第十三章,重载。

$PERLDB $^P [NOT,ALL] 打开 Perl 调试器的内部变量(perl -d)。

$PERL_VERSION $^V [ALL] Perl 解释器的修订版,版本,和子版本,是用二进制的“版本字串”表示的。 V-字串通常没有数字值,但这个变量有双值,有一个数字值等于老的 $] 变量;也就 是说,一个浮点数,数值是 修订版 + 版本/1000 + 子版本/1,000,000。其字串值是从 UTF-8 字符来的:chr($revision) . chr($version) . chr($subversion)。这就 意味着 $^V 是不可打印的。要打印它,你必须说:

printf "%vd", $^V;

从正面来说,它还意味着你可以使用普通的字串比较来判断执行你的脚本的 Perl 解释器 是否在正确的版本范围里。(这个功能适用于任何代表 v-字串的版本数字,而不仅仅是Perl 的。)例子:

warn "No 'our' declarations!\n" if $^V lt v5.6;

参阅 use VERSION 和 require VERSION 的文档,寻找一种当运行的 Perl 解释器比你 希望的版本老的时候失效的一种便利方法。又见 $],看看 Perl 版本的最初的表现形式。

$POSTMATCH $' [DYN,RO] 这个变量里保存在当前活跃的动态范围里,上一次成功匹配里跟在匹配字串 后面的字串。(记忆方法:' 常跟在一个引起的字串后面。)例子:

$_ = 'abcdefghi'; /def/; print "$`:$&:$'"; # 打印 abc:def:ghi

因为在动态范围里,Perl 不知道哪个模式需要将它们的结果保存到这里变量里,所以在 程序里的任何位置提及 $` 或 $' 都将导致在程序里的所有模式匹配的性能损失。对于 小程序而言,这些算不上 什么,但是如果你是在写可以复用的模块代码,那么你可能就 应该避免使用这一对儿。上面的例子可以用下面的代码代替,但是不会遭受全局的性能 损失:

$_ = 'abcdefghi'; /(.*?)(def)(.*)/s; # /s 以防 $1 包含换行符 print "$1:$2:$3\n"; # 打印 abc:def:ghi

$PREMATCH $` [DYN,RO] 这个变量里保存在当前活跃的动态范围里,上一次成功匹配里在匹配字串前面 的字串。(记忆方法:` 通常放在一个引起字串的前面。)参阅前面的 $' 里性能损失的 描述。

$PROCES_ID $PID $$ [ALL] 运行这个脚本的 Perl 的进程号(PID)。在 fork 的时候,这个变量会自动 更新。实际上,你甚至可以自己设置 $$;不过,这么干不会改变你的 PID。要改变 PID 将是一个漂亮的窍门。(记忆方法:和各种 shell 里一样。)

你需要注意的是不要在任何可能错误地解释成一个析引用的地方使用 $$:$$alphanum。 在这个情况下,要写${$}alphanum 以便与 ${$alphanum} 相区别。

$PROGRAM_NAME $0 [ALL] 包含存放正被执行的 Perl 脚本的文件的文件名。给 $0 赋值是一个神奇的操作: 它试图修改通常 ps(1) 程序报告的参数区域。这种赋值作为一种标识当前程序状态的 方法可能要比隐藏你运行的程序更有用些。不过这个赋值不能在所有系统上运行。(记忆 方法:和 sh,ksh,bash,等等里一样。)

$REAL_GROUP_ID $GID $( [ALL] 这个进程的真实组 ID(GID)。如果你运行的平台支持同时在多个组里的成员 关系,$( 给出一个相互分隔的,你所处的组列表。第一个数字是 getgid(2) 返回的 数值,随后的就是 getgroups(2) 返回的,其中之一可能和第一个数字相同。

不过,给 $( 赋值的时候,这个值必须是用于设置真实 GID 的单个数字。所以 $( 给出 的数值不能在没有强制成数字(比如加个零)之前给回 $(,这是因为你只能有一个真实 组。参阅 $) ($EFFECTIVE_GROUP_ID)替换品,它总是允许你设置多个有效的组。

(记忆方法:圆括弧是用于分组的。如果你运行在 setgid 下,真实 GID 是你离开的组 (译注:英文“left”有“离开”之意,同时又是“左边”的同型词。))

$REAL_USER_ID $UID $< [ALL] 这个进程的真实用户 ID(UID),和 getuid(2) 系统调用返回的一样。你是否 能修改这个数值以及你怎样修改这个数值随着你的系统实现的变化而变化——参阅 $>( $EFFECTIVE_USER_ID)里的例子。(记忆方法:如果你运行在 setuid 下,这是你来自 的地方。)

%SIG [ALL] 这个散列用于给各种信号设置信号句柄。(参阅第十六章,进程间通讯,里的 “信号”一节)比如:

sub handler {my $sig = shift; # 第一个参数是信号名字syswrite STDERR, "Caught a SIG$sig--shutting down\n"; # 避免在异步句柄里的标准 I/O 以避免内核倾倒。 # (甚至字串连接都是危险的。)close LOG; # 这里调用标准 I/O,所以可能内核倾倒!exit 1; # 不过既然我们正在退出,所以也没什么损失了。 }

$SIG{INF} = \&handler; $SIG{QUIT} = \&handler; ... $SIG{INT} = 'DEFAULT'; # 恢复缺省动作 $SIG{QUIT} = 'IGNORE'; # 忽略 SIGQUIT

%SIG 散列包含与那些还没有设置句柄的信号的未定义的数值。一个句柄可以作为一个 子过程引用或者一个字串声明。如果一个字串的值不是两个特殊动作“DEFAULT”或 “IGNORE”之一的时候,它就是一个函数的名字,如果这个名字不是带包名的全称,那么 就会解释为在 main 包里。下面是其他一些例子:

$SIG{PIPE} = "Plumber"; # 正确,假设为 main::Plumbr $SIG{PIPE} = \&Plumber; # 很好,使用来自当前包的 Plumber

还可以用 %SIG 散列设置一些内部挂钩。如果要打印一条警告信息的话,那么就会调用$SIG{__WARN__} 标识的过程。这条警告信息被当作第一个参数传递。一个 WARN挂钩的存在导致给 STDERR 的通常的打印动作被消除。你可以用他把警告保存在一个 变量里,或者把警告转化成一个致命错误,象这样:

local $SIG{__WARN__} = sub { die $_[0] }; eval $proggie;

这样做类似于说:

uas warnings qw/FATAL all/; eval $proggie;

只不过前一个有动态范围,而第二个有词法范围。

$SIG{__DIE__} 标识的过程提供了一个用魔术般的一吻把青蛙例外转变成王子例外的 方法,不过,经常不能运行。

它的最好的用途就是在一个准备死在一个未捕获的例外的的程序上,在它临终的时候做 一些临终处理。用这个方法也救不了你,但是你可以最后再喊一句口号。

例外信息被当作第一个参数传递进来。当一个 DIE 挂钩过程返回的时候,例外处理 会象没有挂钩一样继续处理,除非挂钩过程本身通过一个 goto,一个循环退出或者一个die 退出。在调用的过程中 DIE 句柄被明确地关闭,所以然后你自己就可以从一个 DIE 句柄里调用真正的 die。(如果它没有关闭,那么句柄会永远递归地调用 自己。)$SIG{__WARN__} 句柄的运转状况类似。

只有主程序可以设置 $SIG{__DIE__},模块不能。这是因为在目前,甚至那些被捕捉的 例外也仍然触发一个 $SIG{__DIE__} 句柄。我们强烈反对设置这个句柄,因为这样做 可能破坏那些无辜的模块:这些模块正在等待它们预期的例外的时候,那些例外却被莫名 其妙地被修改了。你应该只是把这个特性当作最后的手段,而且如果你必须这么做,应该 总在前面放一个 local 以限制危险的时期。

不要试图在这个特性上制作例外处理机制。你应该用 eval {} 捕获那些例外。

STDERR [ALL] 用于在任何包里做标准错误的特殊文件句柄。

STDIN [ALL] 用于在任何包里做标准输入的特殊文件句柄。

STDOUT [ALL] 用于在任何包里做标准输出的特殊文件句柄。

$SUBSCRIPT_SEPARATOR $SUBSEP $; [ALL] 用于多维散列仿真的下标分隔符。如果你用下面这样的变量引用散列:

$foo{$a, $b, $c}

那么它实际上的意思是:

$foo{join($;, $a, $b, $c)}

但是不要这么写:

@foo{$a, $b, $c} # 一个片段--注意 @

它的意思是:

($foo{$a}, $foo{$b}, $foo{$c})

缺省时是“\034”,和 awk 里的 SUBSEP 一样。请注意如果你的键字包含二进制数据, 那么可能就没有安全的 $;。(记忆方法:逗号——语法上的脚标分隔符——是半个分号。 没错,我们知道这么说相当牵强,不过 $, 已经被更重要的事情占据着了。)

尽管我们还没有废弃这个特性,你现在也应该开始考虑使用“真正”的多维散列了,比如 用 $foo{$a}{$b}{$c} 代替 $foo{$a, $b, $c}。不过,伪(多维)散列可能更容易 排序,并且用做 DBM 文件更容易检查。

$SYSTEM_FD_MAX $^F [ALL] 最大“系统”文件描述符,通常是 2。系统文件描述符在一个 exec 过程中传递 给新程序,而更高的文件描述符则不能传递过去。同样,在一个 open 过程中,即使open 失败也会保留系统文件描述符。(通常文件描述符会在 open 尝试之前关闭,并且 如果 open 失败仍然保持关闭状态。)请注意一个文件描述符的 exec-时关闭的状态将会 在 open 的时候由 $^F 的数值决定,而不是 exec 的时候。我们可以通过临时抬高$^F 的数值来避免这个现象:

{local $^F = 10_000;pipe(HITHER, THITHER) or die "can't pipe:$!"; }

$VERSION [PKG] 当你声明了一个模块的最小可接受版本的时候,Perl 会访问这个变量,就象 use SomeMod? 2.5 里一样。如果 $SomeMod::VERSION 小于 2.5,那么抛出一个例外。 从技术上来讲,它是查看这个变量的 UNIVERSAL->VERSION 方法,所以,如果你希望非 缺省行为的时候,你可以在当前包里定义你自己的 VERSION 函数。参阅第十二章。

$WARNING $^W [ALL] 全局警告开关的当前布尔数值。又见第三十一章,实用模块,里的 use warnings用法和用于词法范围警告的 -W 和 -X 命令行开关,它们不受这个变量的影响。(记忆 方法:这个变量与 -w 开关有关。)

${^WARNING_BITS} [NOT,ALL] use warnings 用法打开的当前警告检查设置。参阅第三十一章的use warnings 获取更多细节。

${^WIDE_SYSTEM_CALLS} [ALL] 全局标志,在可能的情况下,允许 Perl 做的所有系统调用都使用系统本身的 宽字符 API (wide-character)。你还可以从命令行上用 -C 命令行开关把它打开。 它的初始值通常是 0,主要是为了与早于 5.6 的 Perl 的兼容,但是如果系统提供用户 可设置的缺省(比如通过,$ENV{LC_CTYPE})的时候,可能会自动被 Perl 设置为 1。 use bytes 用法总是在当前词法范围里覆盖这个标记的影响。

现在,让我们拥抱非常大的一章...


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


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

  • 第二十九章,函数 (A-D)
    • 29.1 按类分的 perl 函数
    • 29.2 按照字母顺序排列的 perl 函数

为了便于参考,本章以字母顺序(注:有时候,紧密联系的函数在系统手册页里组合在一起, 因此我们在这里也将尊重那些分组。比如,要寻找 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 的函数和类函数关键字。有些函数在多个标题下出现。

  • 标量操作
    chomp, chop, chr, crypt, hex, index, lc, lcfirst, length, oct, ord, pack, q//, qq//, reverse, rindex, sprintf, substr, tr///, uc, ucfirst, y///

  • 正则表达式和模式匹配
    m//, pos, qr//, quotemeta, s///, split, study

  • 数学函数
    m//, pos, qr//, quotemeta, s///, split, study

  • 数珠处理
    abs, atan2, cos, exp, hex, int, log, oct, rand, sin, sqrt, srand

  • 列表处理
    pop, push, shift, splice, unshift

  • 散列处理
    delete,each,exists,keys,values

  • 输入和输出
    binmode, close, closedir, dbmclose, dbmopen, die, eof, fileno, flock, format, getc, print, printf, read, readdir, readpipe, rewinddir, seek, seekdir, select (ready file descriptors), syscall, sysread, sysseek, syswrite, tell, telldir, truncate, warn, write

  • 定长数据和记录
    pack, read, syscall, sysread, sysseek, syswrite, unpack, vec

  • 文件句柄,文件,和目录
    chdir, chmod, chown, chroot, fcntl, glob, ioctl, link, lstat, mkdir, open, opendir, readlink, rename, rmdir, select (ready file descriptors), select (output filehandle), stat, symlink, sysopen, umask, unlink, utime

  • 程序流控制
    caller, continue, die, do, dump, eval, exit, goto, last, next, redo, return, sub, wantarray

  • 范围(搜寻)
    caller, import, local, my, no, our, package, use

  • 杂项
    defined, dump, eval, formline, lock, prototype, reset, scalar, undef, wantarray

  • 进程和进程组
    alarm, exec, fork, getpgrp, getppid, getpriority, kill, pipe, qx//, setpgrp, setpriority, sleep, system, times, wait, waitpid

  • 库模块
    do, import, no, package, require, use

  • 类和对象
    bless,dbmclose,dbmopen,package,ref,tie,tied,untie,use

  • 低层套接字访问
    accept, bind, connect, getpeername, getsockname, getsockopt, listen, recv, send, setsockopt, shutdown, socket, socketpair

  • System V (系统五)进程间通讯
    msgctl, msgget, msgrcv, msgsnd, semctl, semget, semop, shmctl, shmget, shmread, shmwrite

  • 抓取用户和组信息
    endgrent, endhostent, endnetent, endpwent, getgrent, getgrgid, getgrnam, getlogin, getpwent, getpwnam, getpwuid, setgrent, setpwent

  • 抓取网络信息
    endprotoent, endservent, gethostbyaddr, ethostbyname, gethostent, getnetbyaddr, getnetbyname, getnetent, getprotobyname, getprotobynumber, getprotoent, getservbyname, getservbyport, getservent, sethostent, setnetent, setprotoent, setservent

  • 时间
    gmtime,localtime,time,times

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

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

  • $_ 使用 $_ ($ARG)作为缺省变量
  • $! 在系统错误时设置 $! ($OS_ERROR)。
  • $@ 抛出例外,用 eval 捕获 $@($EVAL_ERROR)。
  • $? 当子进程退出时设置 $? ($CHILD_ERROR)。
  • T(实)感染返回的数据
  • T(虚)在一些系统,区域,或者句柄设置下感染返回数据
  • XARG 如果给出一个参数是不合适的类型,抛出一个错误
  • XRO 如果修改一个只读对象,则抛出一个例外。
  • XT 如果填充感染了的数据,则抛出一个例外。
  • XU 如果在当前的平台上没有实现则抛出一个例外。

这里没有标记那些给它们填充了感染了的数据则返回感染数据的函数,因为大多数函数都是这样。 尤其是如果你在 %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) == @filesor 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, "

第二十九章 函数(E-N)


第二十九章,函数 (E-N)

  • 第二十九章,函数 (E-N)
    • 29.2 按照字母顺序排列的 perl 函数
      • 29.2.30 each
      • 29.2.31 eof
      • 29.2.32 eval
      • 29.2.33 exec
      • 29.2.35 exists
      • 29.2.35 exit
      • 29.2.36 exp
      • 29.2.37. fcntl
      • 29.2.38 fileno
      • 29.2.39 flock
      • 29.2.40 fork
      • 29.2.41 format
      • 29.2.42 formline
      • 29.2.43 getc
      • 29.2.44 getgrent
      • 29.2.45 getgrgid
      • 29.2.46 getgrnam
      • 29.2.47 gethostbyaddr
      • 29.2.48 gethostbyname
      • 29.2.49 gethostent
      • 29.2.50. getlogin
      • 29.2.51 getnetbyaddr
      • 29.2.52 getnetbyname
      • 29.2.53. getnetent
      • 29.2.54. getpeername
      • 29.2.55. getpgrp
      • 29.2.56 getppid
      • 29.2.57. getpriority
      • 29.2.58 getprotobyname
      • 29.2.59 getprotobynumber
      • 29.2.60 getprotoent
      • 29.2.61 getpwent
      • 29.2.62 getpwnam
      • 29.2.63. getpwuid
      • 29.2.64. getservbyname
      • 29.2.65. getservbyport
      • 29.2.66. getservent
      • 29.2.67. getsockname
      • 29.2.68. getsockopt
      • 29.2.69 glob
      • 29.2.70. gmtime
      • 29.2.71. goto
      • 29.2.72. grep
      • 29.2.73. hex
      • 29.2.74 import
      • 29.2.75. index
      • 29.2.76. int
      • 29.2.77. ioctl
      • 29.2.78. join
      • 29.2.79 keys
      • 29.2.80. kill
      • 29.2.81. last
      • 29.2.82. lc
      • 29.2.83 lcfirst
      • 29.2.84. length
      • 29.2.85. link
      • 29.2.86. listen
      • 29.2.87. local
      • 29.2.88. localtime
      • 29.2.89 lock
      • 29.2.90. log
      • 29.2.91. lstat
      • 29.2.92. m//
      • 29.2.93. map
      • 29.2.94. mkdir
      • 29.2.95. msgctl
      • 29.2.96. msgget
      • 29.2.97. msgrcv
      • 29.2.98. msgsnd
      • 29.2.99. my
      • 29.2.100. new
      • 29.2.101. next
      • 29.2.102. no

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

29.2.30 each

    • each HASH

这个以一次一个键字/数值对的方式遍历一个散列。如果在列表环境里调用它,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

    • eof FILEHANDLE
    • eof()
    • 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 BLOCK
  • eval EXPR
  • 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 PATHNAME LIST
    • exec LIST

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

    • exist EXPR

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

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

    • exit EXPR
    • 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

  • exp EXPR
    • exp

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

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

29.2.37. fcntl

  • fcntl FILEHANDLE, FUNCTION, SCALAR

这个函数调用你的操作系统的文件控制函数,就是那些 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

  • fileno FILEHANDLE

这个函数返回在一个文件句柄下面的文件描述符。如果该文件句柄没有 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 FILEHANDLE OPERATION

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

这个函数通过调用 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

  • formline PICTURE, LIST

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

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

29.2.43 getc

  • getc FILEHANDLE
  • 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

  • getgrent
  • setgrent
  • endgrent

这些过程遍历你的 /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

  • getgrgid GID

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

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

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

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

29.2.46 getgrnam

  • getgrnam NAME

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

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

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

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

29.2.47 gethostbyaddr

  • gethostbyaddr ADDR, ADDRTYPE

这个函数把地址转换成名字(和改变地址)。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

  • gethostbyname NAME

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

   ($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

  • gethostent
  • sethostent STAYOPEN
  • endhostent

这个函数遍历你的 /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

  • getlogin

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

   $login = getlogin() || (getpwuid($