会员登陆  支付方式  联系我们  在线客服  网站地图       
首页 关于域浪 互联网数据中心业务、主机托管、主机租用、机架租用、带宽租用、机房介绍、机房对比、CCN网络加速 adsl专线、深圳adsl专线 虚拟主机、域名注册、数据库、企业邮局、动态域名 网络安全、网络临近平台、安全服务、防火去墙租用、安全产品、域浪抗DDOS防火墙、NetScreen防火墙 技术支持  
   当前位置:首页 >> 技术支持 >> PERL编程技巧 >> 函数 (O-Y)
 
精华文章
    函数 (O-Y)  
    函数 (E-N)  
    简单旧文档  
    可移植的 Perl  
    普通实践  
    安全  
    Perl 调试器  
    命令行接口  
    编译  
    线程  
    Unicode  
    Perl5的包和模块  
    Perl的面向对象编程  
    Perl5中的引用/指针  
    文件系统  
    格式化输出  
    关联数组/哈希表  
    子程序  
    控制结构  
    模式匹配  
    文件读写  
    列表和数组变量  
    操作符  
    简单变量  
    Perl是什么  
    深入研究表单提交方式...  
    GD.pm 图形模块安装指...  
    perl的进程管理简介  
  更多>>  
   PERL编程技巧
 函数 (O-Y)
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(OUTPUT, ">-") or die;       # 重新打开标准输出用于写

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



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





   open(DBASE, "+< database")

      or die "can't open existing database in update mode: $!";

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



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





   open(PRINTER, "| lpr -Plp1")   or die "cna't fork: $!";

   print PRINTER "stuff\n";

   close(PRINTER)         or die "lpr/close failed: $?/$!";

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





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

   while () { ... }

   close(NET)         or die "can't close netstat: $!/$?";

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



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



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





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

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

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





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

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

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





   defined($pid = open(FROM_CHILD, "-|"))

      or die "can't fork: $!";



   if ($pid) {

      @parent_lines = ;   # 父进程代码

   }

   else {

      print STDOUT @child_lines;      # 子进程代码

   }

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





   open FH,      "| tr   'a-z'   'A-Z'";   # 管道输出给 shell 命令

   open FH, "|-",      'tr',   'a-z',   'A-Z';   # 管道输出给光命令

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

以及这些; 





   open FH,      "cat    -n   'file' |";   # 从 shell 命令管道取来 

   open FH, "-|",      'cat',   '-n',   'file';   # 从光命令管道取来

   open FH, "-|", or exec   'cat',   '-n',   'file' or die;   # 从子进程取来

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



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



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



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





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

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

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





   somefunction("&main::LOGFILE");

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





   #! /usr/bin/perl

   open SAVEOUT, ">&STDOUT";

   open SAVEERR, ">&STDERR";



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

   open STDERR, ">&STDOUT"    or die "Can't dup stdout";



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

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



   print STDOUT "stdout 1\n";   # 这些 I/O 流也传播到

   print STDERR "stderr 1\n";   # 子进程



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

   

   close STDOUT;

   close STDERR;



   open STDOUT, ">&SAVEOUT";

   open STDERR, ">&SAVEERR";



   print STDOUT   "stdout 2\n";

   print STDERR   "stderr 2\n";

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





   $fd = $ENV{"MHCONTEXTFD"};

   open(MHCONTEXT, "<&=fdnum")

      or die "couldn't fdopen descriptor $fdnum: $!";

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





   use Fcntl qw(F_GETFE F_SETFD);

   $flags = fcntl(FH, F_SETFD, 0)

      or die "Can't clear close-on-exec flag on FH: $!\n";

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



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





   $path =~ s#^(\s)#./$!#;

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

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





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

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





   use Fcntl;

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

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



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



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





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

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



表29-2 I/O 纪律 



纪律  含义  

:raw  二进制模式;不做处理  

:text  缺省文本处理  

:def  use open 的缺省声明  

:latin1  文件应该是 ISO-8859-1  

:ctype  文件应该是 LC_CTYPE  

:utf8  文件应该是 UTF-8  

:utf16  文件应该是 UTF-16  

:utf32  文件应该是 UTF-32  

:uni  直观 Unicode(UTF-*)  

:any  直观 Unicode/Latin1/LC_CTYPE  

:xml  使用文件中声明的编码  

:crlf  直观换行符  

:para  段落模式  

:slurp  吞噬模式  







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





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

   while ($para = )   { ... }

这样将设立纪律如下: 





如果该文件已经是 UTF-8,则读进一些 Unicode 的数据并转换成 Perl 内部的 UTF-8 格式。 

寻找行结尾的序列变体,把它们转换成 \n,并且 

把文件处理成段落大小的块,类似 $/ = "" 干的事情。 

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





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

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





29.2.105 opendir 



opendir DIRHANDLE, EXPR 

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





29.2.106 ord 



ord EXPR 

ord 

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





29.2.107 our 

*our TYPE EXPR : ATTRIBUTES 



our EXPR : ATTRIBUTES 

our TYPE EXPR 

our EXPR 

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



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



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





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

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



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





   

   package Foo;

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

   $bar = 582;



   package Bar;

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

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





   ($x, $y) = ("one", "two");

   print "before block, x is $x, y is $y\n";

   {

      our $x = 10;

      local our $y = 20;

      print "in block, x is $x, y is $y\n";

   }

   print "past block, x is $x, y is $y\n";

打印出: 



   before block, x is one, y is two

   in block x is 10, y is 20

   past block, x is 10, y is two

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





   use warnings;

   package Foo;

   our $bar;      # 为剩余的词法范围声明 $Foo::bar 

   $bar = 20;



   package Bar;

   our $bar = 30;      # 为剩余的词法范围声明 $Foo::bar

   print $bar;      # 打印 30



   our $bar;      # 发出警告

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





29.2.108. pack 

*pack TEMPLATE, LIST 





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



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



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





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

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



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





数据的类型(比如是整数还是浮点还是字串), 

数值的范围(比如你的整数是否放在一个,两个,四个,或者甚至八个字节里;或者你包装的是一个 8 位字符还是 Unicode 字符。), 

你的整数是有符号还是无符号,以及 

使用的编码(比如说本机,包装位和字节时小头在前,或者是大头在前)。 

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



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



字符  含义  

a  一个填充空的字节串  

A  一个填充空格的字节串  

b  一个位串,在每个字节里位的顺序都是升序  

B  一个位串,在每个字节里位的顺序都是降序  

c  一个有符号 char(8位整数)值  

C  一个无符号 char(8位整数)值;关于 Unicode 参阅 U  

d  本机格式的双精度浮点数  

f  本机格式的单精度浮点数  

h  一个十六进制串,低四位在前  

H  一个十六进制串,高四位在前  

i  一个有符号整数值,本机格式  

I  一个无符号整数值,本机格式  

l  一个有符号长整形,总是 32 位  

L  一个无符号长整形,总是 32 位  

n  一个 16位短整形,“网络”字节序(大头在前)  

N  一个 32 位短整形,“网络”字节序(大头在前)  

p  一个指向空结尾的字串的指针  

P  一个指向定长字串的指针  

q  一个有符号四倍(64位整数)值  

Q  一个无符号四倍(64位整数)值  

s  一个有符号短整数值,总是 16 位  

S  一个无符号短整数值,总是 16 位  

u  一个无编码的字串  

U  一个 Unicode 字符数字  

v  一个“VAX”字节序(小头在前)的 16 位短整数  

V  一个“VAX”字节序(小头在前)的 32 位短整数  

w  一个 BER 压缩的整数  

x  一个空字节(向前忽略一个字节)  

X  备份一个字节  

Z  一个空结束的(和空填充的)字节串  

@  用空字节填充绝对位置  





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



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



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



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



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



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



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





   unpack 'C/a',   "\04Gurusamy";      # 生成 'Guru'

   uppack 'a3/A* A*', '077 Bond J ';      # 生成 (' Bond', 'J')

   pack 'n/a* w/a*', 'hell', ','world';      #  生成 "hello, world"

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



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



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





   use Config;

   print $Config{shortsize},      "\n";

   print $Config{intsize},      "\n";

   print $Config{longsize},      "\n";

   print $Config{longlongsize},   "\n";

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



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



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



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



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





   struct foo {

      unsigned char c;

      float f;

   };

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



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





   $out = pack "CCCC", 65, 66, 67, 68;      # $out 等于"ABCD"

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

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





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

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





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

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





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

                     # 在大头在前的机器上是 "\0\1\0\2" 

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





   $out = pack "B32", "...(略)";

   $out = pack "H8", "5065726c";         # 都生成“Perl”

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





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

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





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

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

a 格式做空填充: 





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

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





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

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



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





   

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

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





   $string = join(" and ", @data);

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

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





29.2.109. package 



package NAMESPACE 

package 

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



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



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



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



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



下面是一个例子: 





   package main;      $sail = "hale and hearty";

   package Mizzen;   $sail = "tattered";

   package Whatever;

   print "My main sail is $main::sail.\n";

   print "My mizzen sail is $Mizzen::sail.\n";

它打印出: 





   My main sail is hale and hearty.

   My mizzen sail is tattered.

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



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



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





29.2.110 pipe 



pipe READHANDLE, WRITEHANDLE 

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



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



下面是一个小例子: 





   pipe(README, WRITEME);

   unless ($pid = fork) {   #    子进程

      defined $pid or die "can't fork: $!";

      close(README);

      for $i (1..5) { print WRITEME "line $i\n" }

      exit;

   }

   $SIG{CHLD} = sub { waitpid($pid, 0) };

   close(WRITEME);

   @strings = ;

   close(README);

   print "Got:\n", @strings;

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





29.2.111. pop 



pop ARRAY 

pop 

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





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

或者: 





   

   $tmp = splice @ARRAY, -1;

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



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



   ( LIST )[-1]



29.2.112 pos 



pos SCALAR 

pos 

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





   $graffito = "fee fie foe foo";

   while ($graffito =~ m/e/g) {

      print pos $graffito, "\n";

   }

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





   $graffito = "fee fie foe foo";

   pos $graffito = 4;   # 忽略 fee,开始 fie

   while ($graffito =~ m/e/g) {

      print pos $graffito, "\n";

   }

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





29.2.113 print 



print FILEHANDLE LIST 

print LIST 

print 

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





   print { $OK ? "STDOUT" : "STDERR" } "stuff\n";

   print { $iohandle[$i] } "stuff\n";

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





   print $a - 2;   # 向缺省文件句柄(通常是 STDOUT)打印 $a - 2

   print $a (-2);   # 向在 $a 中声明的文件句柄中打印 -2

   print $a -2;   #  也打印 -2(古怪的分析规则 :-))

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





   print OUT ;

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





   print OUT scalar ;

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





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

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

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



29.2.114 printf 



printf FILEHANDLE FORMAT, LIST 

printf FORMAT, LIST 

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





   print FILEHANDLE sprintf FORMAT, LIST

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



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



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



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





29.2.115. prototype 



prototype FUNCTION 

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



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





29.2.116 push 



push ARRAY, LIST 

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





   foreach $value (listfunc()) {

      $array[++$#array] = $value;

   }

或者: 





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

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





   for(;;) {

      push @array, shift @array;

   }

又见 pop 和 unshift。 





29.2.117. q/STRING/ 



q/STRING/ 

qq/STRING/ 

qr/STRING/ 

qw/STRING/ 

qx/STRING/ 

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





29.2.118. quotemeta 



quotemeta EXPR 

quotemeta 

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





29.2.119 rand 



rand EXPR 

rand 

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



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





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

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





29.2.120 read 



read FILEHANDLE, SCALAR, LENGTH, OFFSET 

read FILEHANDLE, SCALAR, LENGTH 

这个函数试图从声明的 FILEHANDLE 中读取 LENGTH 字节的数据到 SCALAR 变量中。该函数返回读取的字节的数量,到文件结尾时返回 0。它在出错的时候返回 undef。SCALAR 将增长或者缩短到实际读取的长度。如果声明了 OFFSET,则决定该变量从那里开始输出字节,这样你就可以在一个字串中间读取数据。 



要从文件句柄 FROM 中拷贝数据到文件句柄 TO,你可以说: 





   while(read(FROM, $buf, 16384)) {

      print TO $buf;

   }

read 的反操作只是一个简单的 print,它已经知道你想写的字串长度并且可以写任意长度的字串。不要误用 write,因为它只用语 format。 



Perl 的 read 函数是用标准 I/O 的 fread(3) 函数实现的,所以实际的 read(2) 系统调用可能会读取超过 LENGTH 字节的数据填进缓冲区,而且 fread(3) 可能会做多次 read(2) 系统调用以填充缓冲区。要获得更好的控制,用 sysread 声明真正的系统调用。除非你想自找麻烦,否则请不要混合 read 和 sysread。不管你使用哪个,都要注意如果你从一个包含 Unicode 或者任何其他多字节编码的文件里读取,那么缓冲区的边界可能落在字符的中间。 





29.2.121. readdir 



readdir DIRHANDLE 

这个函数从一个用 opendir 打开的目录句柄读取目录记录(它们就是文件名)。在标量环境中,这个函数返回下一个目录记录(如果存在的话);否则,它返回 undef。在列表环境中,它返回在该目录中所有剩下的记录,如果剩下没有记录了,那么这个返回可能是一个空列表。比如: 





   opendir(THISDIR, ".") or die "serious dainbramage: $!";

   @allfiles = readdir THISDIR;

   closedir THISDIR;

   print "@allfiles\n";

上面的代码在一行里打印出当前目录的所有文件。如果你想避免“.”和“..”记录,使用下面的咒语中的一条(你认为最不好念的那条): 





   @allfiles = grep { $_ ne '.' and $_ ne '..' } readdir THISDIR;

   @allfiles = grep { not /^[.][.]?\z/ } readdir THISDIR;

   @allfiles = grep { not /^\.{1,2}\z/ } readdir THISDIR;

   @allfiles = grep !/^\.\.?\z/, readdir, THISDIR;

为了避免所有 .* 文件(象 ls 程序): 





   @allfiles = grep !/^\./, readdir THISDIR;

只拿出文本文件,说: 





   @textfiles = grep -T, readdir THISDIR;

不过我们再看看最后一个例子,因为如果 readdir 的结果不在当前目录里,那么我们需要在它的结果上把目录部分粘回去——象这样: 





   opendir(THATDIR, $path) or die "can't opendir $path: $!";

   @dotfile = grep { /^\./ && -f } map { "$path/$_" } readdir(THATDIR);

   closedir THATDIR;



29.2.122 readline 



readline FILEHANDLE 

这个函数是在内部实现 操作符的函数,但是你可以直接使用它。该函数从 FILEHANDLE 中读取下一条记录,FILEHANDLE 可以是一个文件句柄名称或者一个间接的文件句柄表达式,该表达式要么返回实际文件句柄的名字要么返回一个指向任何类似文件句柄的东西的引用(比如一个类型团)。(早于 5.6 版本的 Perl 只接受一个类型团。)在标量环境里,每次调用读取并返回下一条记录,直到到达文件结尾,这个时候后继的调用将返回 undef。在列表环境中, readline 读取记录直到文件结束,然后返回一个记录列表。这里说的“记录”,我们通常的意思是一行文本,但是通过改变 $/($INPUT_RECORD_SEPARATOR)的值可以让这个操作符用不同的方法给文本“分段”。还有,有些输入纪律,比如 :para(段落模式)将以块的方式返回记录而不是行。设置 :slurp 纪律(或者解除 $/ 的定义)可以令这样的一块就是整个文件。 



在标量环境中读取文件的时候,如果你碰巧读进了一个空文件,readline 第一次返回 "",然后在后继的每次调用都返回 undef。如果从一个 ARGV 文件句柄中读取数据,那么每个文件都返回一个块(同样,空文件返回 ""),当全部读取完成的时候再读取就返回 undef。 



操作符在第二章的“输入操作符”中有更详细的讨论。 





   $line = ;

   $line = readline(STDIN);      # 和上面一样

   $line = readline(*STDIN);      # 和上面一样

   $line = readline(\*STDIN);      # 和上面一样



   open my $fh, "<&=STDIN" or die;

   bless $fh => 'AnyOldClass';

   $line = readline($fh);      # 和上面一样



29.2.123 readlink 



readlink EXPR 

readlink 

这个函数返回一个符号链接指向的文件名。EXPR 应该计算出一个文件名,而且它的最后一个组成部分应该是一个符号链接。如果它不是符号链接,或者如果符号链接没有在文件系统里实现,或者如果发生某种系统错误,则返回 undef,并且你应该检查在 $! 里的错误代码。 



请注意这个返回的符号链接可能是相对于你声明的位置。比如,你可能说: 





   readlink "/usr/local/src/express/yourself.h"

而 readlink 可能会返回: 





   ../express.1.23/includes/yourself.h

除非你的当前目录碰巧是 /usr/local/src/express,否则这个东西可能不能直接当文件名使用。 





29.2.124. readpipe 



readpipe scalar EXPR 

readpipe LIST (建议中) 

它是实现 qx// 引起构造(也称之为反勾号操作符)的内部函数。偶尔它和很有用,比如说你需要一种特殊的声明你的 EXPR 的方法,而这种方法如果使用引起的形式的话并不方便。请注意的是我们以后可能改变它的接口以支持 LIST 参数,这样它就更象 exec 函数,所以,不要假设它会继续为 EXPR 提供标量环境。你应该自己给他 scalar,或者试着使用 LIST 形式。天知道到你阅读到这些内容的时候是不是就可以用了。 





29.2.125 recv 



recv SOCKET, SCALAR, LEN, FLAGS 

这个函数接收套接字上的一条信息。它试图从声明的 SOCKET 文件句柄中接收 LENGTH 个字节的数据到变量 SCALAR 中。该函数返回发送者的地址,或者如果有错误的话是 undef。SCALAR 将根据所读取的数据增长或者缩短到数据长度。该函数接受与 recv(2) 相同的标志。参阅第十六章的 “套接字”一节。 







29.2.126 redo 



redo LABEL 

redo 

redo 操作符在不经重新计算条件的情况下重新开始一个循环块。如果存在任何 continue 块也不会执行它们。如果省略了 LABEL,那么该操作符指向最内层的闭合循环。该操作符通常用于那些希望欺骗自己输入的程序: 



# 一个把那些用反斜杠续起来的行接起来的循环 



   while() {

      if (s/\\\n$// && defined($nextline =)) {

         $_ .= $nextline;

         redo;

      }

      print;      # 或者别的什么...

   }

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



一个块本身等效于一个只执行一次的循环。因此在这样的块里的 redo 将很有效地把它变成一个循环构造。参阅第四章里的“循环控制”。 





29.2.127 ref 



ref EXPR 

ref 

如果 EXPR 是一个引用,那么 ref 操作符返回一个真值,否则返回一个假。返回的值取决于该引用所引用的东西的类型。内建的类型包括: 





   SCALAR

   ARRAY

   HASH

   CODE

   GLOB

   REF

   LVALUE

   IO::Handle

如果这个引用的对象已经赐福到了一个包中,那么返回的就是该包的名字。你可以把 ref 当作一种 “类型是”操作符。 





   if (ref($r) eq "HASH") {

      print "r is a reference to a hash.\n";

   }

   elsif (ref($r) eq "Hump") {      # 烦人——见下文

      print "r is a reference to a Hump object.\n"

   }

   elsif (not ref $r) {

      print "r is not a reference at all.\n";

   }

我们认为测试你的对象的类名字是否等于任意特定的类名字是非常糟糕的 OO 风格,因为一个派生类会有不同的名字,但是应该允许访问基类的方法。这种情况最好用下面的 UNIVERSAL 方法: 





   if ($r->isa("Hump") ){

      print "r is a reference to a Hump object, or subclass.\n"

   }

最好是根本别测试,因为除非 OO 机制在一开始就觉得合适,否则它不会给你的方法发送对象。参阅第八章和第十二章获取更多细节。又见第三十一章里的 use attributes 用法里的 reftype 函数。 





29.2.128 rename 



rename OLDNAME, NEWNAME 

这个函数修改一个文件的名字。成功时返回真,否则返回假。它(通常)不能跨文件系统运行,不过在 Unix 系统上 mv 通常可以做这件事情。如果一个名叫 NEWNAME 的文件已经存在,那么它将被删除。非 Unix 系统上可能还有其他限制。 



参阅标准的 File::Copy 模块获取跨文件系统的重命名。 





29.2.129 require 



require VERSION 

require EXPR 

require 

这个函数断言在其参数上的某种依赖性。 



如果该参数是一个字串,require 装载并执行那些在放在独立文件内的 Perl 代码,该文件的名字是该字串给出的。这种情况类似在一个文件上做 do,只不过是 require 会检查一下,看看该库文件是否已经装载了,而且,如果它碰到任何问题都会抛出一个例外。(因此它可以用于表达文件依赖性而不用担心重复编译。)和它的表亲 do 和 use 类似,require 知道如何搜索保存在 @INC 数组里的包含文件并且在成功的时候更新 %INC。参阅第二十八章。 



该文件必须返回真作为最后的值以表示任何初始化代码的成功执行,因此我们习惯上用 1 结束这种文件;除非你确信它返回真。 



如果 require 的参数是一个版本号,比如 5.6.2 这样;require 实际上就是要求当前正在运行的 Perl 版本必须至少是那个版本。(Perl 还接受一个浮点数,比如 5.005_03,这样就和老版本的 Perl 兼容,但是现在我们不鼓励使用那种形式,因为来自其他文化的家伙们不理解它。)因此,一个需要 Perl 5.6 的脚本可以把下面这个做其第一行: 





   require 5.6.0;         # 或者 require v5.6.0

这样早期的 Perl 版本就会退出。不过,和所有 require 一样,这些事情是在运行时发生的。你可能更愿意说 use 5.6.0 以获得编译时间的检查。又见第二十八章里的 $PERL_VERSION。 



如果 require 的参数是一个光包的名字(参阅 package),rquire 假设它有一个自动的 .pm 后缀,这样就令它容易装载标准的模块。这个特性类似 use,只不过是发生在运行时间而不是编译时间,并且没有调用 import 模块。比如,要在不往当前包增加任何符号的情况下把 Socket.pm 拉进来,你可以说: 





   require Socket;         # 而不是 "use Socket;"

不过,你可以用下面的代码获得相同效果,而且如果找不到 Socket.pm 的话还可以得到编译时警告: 





   use Socket ();

在一个光名字上使用 require 还把任何包名字里的 :: 替换成你的系统的目录分隔符,通常是 /。换句话说,如果你试验下面的代码: 





   require Foo::Bar;      # 很好的空名

那么 require 函数在 @INC 数组里声明的目录里寻找 Foo/Bar.pm 文件。但是如果你试验这些: 





   $class = 'Foo::Bar';

   require $class;      # $class 不是一个光名

或者: 





   require "Foo::Bar";      # 引号文本不是光名

那么 require 函数将在 @INC 数组里寻找 Foo::Bar 文件并且回抱怨没有在那里找到 Foo::Bar。如果这样,你可以用: 





   eval "require $class";

又见 do FILE,use 命令,use lib 用法,以及标准的 FindBin? 模块。 





29.2.130 reset 



reset EXPR 

reset 

这个函数通用于(或者滥用于)循环的顶端或者在循环尾部的一个 continue 块里,用于清理全局变量或者重置 ?? 搜索,这样它们就又可以运转了。表达式解释成一个单字符的列表(连字符可以用做表示范围)。所有以这些字符之一开头的标量变量,数组,以及散列都恢复到它们最早的状态。如果省略了该表达式,那么匹配一次的搜索(?PATTERN?)被重置,重新进行匹配。该函数只为当前包重置变量或搜索。它总是返回真。 



要重置所有“X”变量,你可以说: 





   reset 'X';

要重置所有小写变量,说: 





   reset 'a-z';

最后,只重置 ?? 搜索,说: 





   reset;

我们不推荐你在 main 包里重置“A-Z”,因为你会把你的所有 ARGV,INC,ENV,和 SIG 数组和散列都摧毁。 



词法变量(由 my 创建)不受影响。reset 的使用已经模糊地废弃了,因为它很容易清空整个名字空间,而且 ?? 操作符本身也是模糊地废弃了。 



又见来自标准 Symbol 的 delete_package() 函数,以及在第二十三章的“安全隔仓”里记录的所有安全隔仓的内容。 





29.2.131. return 



return EXPR 

return 

这个操作符令当前子过程(或者是 eval 或 do FILE)马上带着声明的数值返回。试图在这些地方之外使用 return 将抛出一个例外。还要注意 eval 不能代表调用它的子过程做 return。 



EXPR 可能会在列表,标量,或者空环境中计算,具体是哪种环境由如何使用该返回值决定,而这个情况可能每次执行都会不一样。也就是说,你提供的表达式将在子过程调用的环境中计算。如果子过程是在标量环境中调用的,那么 EXPR 也在标量环境中计算。如果该子过程是在列表环境中调用的,那么 EXPR 也是在列表环境中计算并且返回一个数值列表。没有参数的 return 在标量环境中返回标量值 undef,在列表环境中返回一个空列表 (),而(自然)在空环境中什么也不返回。子过程调用的环境可以在子过程里用(错误命名的) wantarray 函数来判断。 







29.2.132 reverse 



reverse LIST 

在列表环境里,这个函数返回一个数值列表,该列表包含反序排列的 LIST 的元素。该函数可以用于创建递减序列: 





   for (reverse 1 .. 10) { ... }

因为如果散列作为 LIST 传递,那么它就会平面化为一个列表,所以 reverse 还可以用于反转一个散列,假设其值是唯一的: 



%barfoo = reverse %foobar; 



在标量环境里,该函数连接 LIST 的所有元素,然后返回这个连接出来的字串的一个字符一个字符的反序, 



一条小提示:反转一个由用户定义函数排序的列表的时候,可能更容易通过先以相反方向对该列表排序来实现。 





29.2.133. rewinddir 



rewinddir DIRHANDLE 

这个函数为操作 DIRHANDLE 的 readdir 过程把当前位置设置到目录的开头。该函数可能无法在所有支持 readdir 的系统上使用,如果系统没有实现 rewinddir,那么它会退出。它成功的时候返回真,否则返回假。 





29.2.134. rindex 



rindex STR, SUBSTR, POSITION 

rindex STR, SUBSTR 

这个函数运行起来和 index 很相象,只不过它返回在 STR 里最后发生的 SUBSTR 的位置(反向的 index)。如果没有找到 SUBSTR,那么该函数返回 $[-1。因为 $[ 现在实际上总是 0,而该函数实际上总是返回 -1。如果声明了 POSITION,那么它就是可以返回的最右端的位置。要想从后向前扫描一遍你的字串,说: 





   $pos = length $string;

   while (($pos = rindex $string, $lookfor, $pos) >= 0) {

      print "Found at $pos\n";

      $pos--;

   }



29.2.135. rmdir 



rmdir FILENAME 

rmdir 

如果 FILENAME 声明的目录是空的,那么此函数删除该目录。如果该函数成功,它返回真;否则,返回假。如果你想先删除该目录的内容,而又因为什么原因不想调用 shell 里的 rm -r,(比如说没有 shell,或者没有 rm 命令,因为你没办法获得 PPT。)那么可以看看 File::Path 模块。 





  • 下一篇文章: 函数 (E-N)
  • 域浪网络ISP经营许可证 深圳地址:深圳市罗湖区宝安北路国际商品交易大厦七楼C30室
    Tel:0755-82266883/82267566 Fax:0755-82261966
    邮编:518000 
                        Copyright © 2006-2008 elang.cn All Rights Reserved 深圳市域浪网络技术有限公司版权所有