第二十九章,函数 (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, "