内容

名称

PerlIO - PerlIO 层的按需加载器和 PerlIO::* 命名空间的根

概要

# support platform-native and CRLF text files
open(my $fh, "<:crlf", "my.txt") or die "open failed: $!";

# append UTF-8 encoded text
open(my $fh, ">>:encoding(UTF-8)", "some.log")
  or die "open failed: $!";

# portably open a binary file for reading
open(my $fh, "<", "his.jpg") or die "open failed: $!";
binmode($fh) or die "binmode failed: $!";

Shell:
  PERLIO=:perlio perl ....

描述

当在 openbinmode 层规范中遇到未定义的层 'foo' 时,C 代码执行等效于

use PerlIO 'foo';

PerlIO.pm 中的 Perl 代码随后尝试通过以下方式定位层:

require PerlIO::foo;

否则,PerlIO 包是其他与 PerlIO 相关的函数的占位符。

一般来说,PerlIO 层(以前有时称为“规则”)是应用于文件句柄的有序堆栈(指定为以空格或冒号分隔的列表,通常以冒号开头)。每个层对任何输入或输出执行某些操作,除非被绕过,例如使用 sysreadsyswrite。读取操作按设置的顺序(从左到右)遍历堆栈,写入操作按相反顺序遍历堆栈。

还有一些层实际上只是在较低层设置标志,或者修改当前堆栈但本身不会保留在堆栈上的层;这些被称为伪层。

打开句柄时,它将使用在 open() 调用中显式指定的任何层(或如果指定为冒号后没有后续层,则使用平台默认值)打开。

如果未显式指定层,则句柄将使用 ${^OPEN} 变量指定的层打开(通常通过使用 open 编译指示词为词法范围设置,或使用 -C 命令行开关或 PERL_UNICODE 环境变量为主程序范围设置)。

如果在 open() 调用或 ${^OPEN} 变量中未指定层,则句柄将使用为该体系结构配置的默认层堆栈打开;请参阅 "默认值以及如何覆盖它们"

某些层会在不存在的情况下自动插入所需的较低级别层;例如,:perlio 会在其下方插入 :unix 以进行低级别 IO,而 :encoding 会插入平台默认值以进行缓冲 IO。

binmode 函数可以在打开的句柄上调用,以将额外的层推入堆栈,这也会修改现有层。binmode 在没有层的情况下调用将删除或取消设置任何现有的转换字节流的层,使句柄适合二进制数据。

目前定义了以下层

:unix

最低级别层,它根据 UNIX/POSIX 数字文件描述符调用(open()、read()、write()、lseek()、close())提供基本的 PerlIO 操作。它即使在非 Unix 体系结构上也会使用,并且大多数其他层都在其之上运行。

:stdio

调用 freadfwritefseek/ftell 等的层。请注意,由于这是“真正的”stdio,它将忽略其下方的任何层,并像往常一样直接通过 C 库访问操作系统。此层实现低级别 IO 和缓冲,但在现代体系结构上很少使用。

:perlio

PerlIO 缓冲的从头开始实现。为 sv_gets 提供快速访问缓冲区,sv_gets 实现 Perl 的 readline/<>,并且通常尝试最大程度地减少数据复制。

:perlio 将在其下方插入一个 :unix 层以进行低级别 IO。

:crlf

一个实现 DOS/Windows 风格 CRLF 行结束符的层。在读取时,将 CR,LF 对转换为单个 "\n" 换行符。在写入时,将每个 "\n" 转换为 CR,LF 对。请注意,此层会静默拒绝在其自身之上被推入。

它目前模拟 MS-DOS,因为它不将 Control-Z 视为文件结束标记。

在像 DOS/Windows 这样的架构上,此层是默认层的一部分,它也像 :perlio 层一样工作,并且移除 CRLF 转换(例如使用 :raw)只会取消设置 CRLF 转换标志。从 Perl 5.14 开始,您也可以在稍后应用另一个 :crlf 层,例如当 CRLF 转换必须在编码层之后发生时。在其他架构上,它是一个普通的 CRLF 转换层,可以正常添加和移除。

# translate CRLF after encoding on Perl 5.14 or newer
binmode $fh, ":raw:encoding(UTF-16LE):crlf"
  or die "binmode failed: $!";
:utf8

伪层,声明流接受 Perl 的内部升级字符编码,这在 ASCII 机器上大约是 UTF-8,但在 EBCDIC 机器上是 UTF-EBCDIC。这允许 Perl 可以表示的任何字符从流中读取或写入流。

此层(实际上是在前一层设置一个标志,并且由任何 :encoding 层隐式设置)不会转换或验证字节序列。相反,它表明字节流将由其他层排列,以 Perl 的内部升级编码提供,Perl 代码(以及正确编写的 XS 代码)将解释为解码的 Unicode 字符。

注意:不要使用此层从 UTF-8 字节进行转换,因为无效的 UTF-8 或二进制数据会导致 Perl 字符串格式错误。在用于输出时,它不太可能产生无效的 UTF-8,但它会在 EBCDIC 系统上产生 UTF-EBCDIC。:encoding(UTF-8) 层(连字符很重要)是首选,因为它将确保在有效的 UTF-8 字节和有效的 Unicode 字符之间进行转换。

:bytes

这是 :utf8 伪层的逆运算。它关闭了下方层的标志,以便从该层读取的数据被认为是 Perl 的内部降级编码,因此被解释为 Latin-1 或 EBCDIC 的本机单字节编码。同样,在输出时,如果将“宽”字符(不在 0..255 范围内的代码点)写入此类流,Perl 将发出警告。

在使用 :encoding 层的句柄上推入此层非常危险,因为此类层假设使用 Perl 的内部升级编码,因此您可能会得到一个混乱的结果。相反,使用 :raw:pop 来移除编码层。

:raw

:raw 伪层被定义为等同于调用 binmode($fh) - 流被设置为适合传递二进制数据,即每个字节按原样传递。流仍然会被缓冲(但在 Perl 5.14 之前并非总是如此)。

在 Perl 5.6 和一些书籍中,:raw 层被记录为 :crlf 层的逆运算。情况不再如此 - 其他会改变流二进制性质的层也被禁用。如果你想要在通常进行 CRLF 转换的平台上使用 UNIX 行结束符,但仍然想要 UTF-8 或编码默认值,那么应该将 :perlio 添加到 PERLIO 环境变量中,或者使用该层显式打开句柄,以替换平台默认的 :crlf

:raw 的实现是一个伪层,当“压入”时,它会弹出自身以及任何会修改二进制数据流的层。(撤销 :utf8:crlf 的实现可能是通过清除标志而不是弹出层来完成的,但这只是一个实现细节。)

由于 :raw 通常会弹出层,因此通常只有在它作为层规范中唯一的或第一个元素时才有意义。当用作第一个元素时,它提供了一个已知的基准,例如:

open(my $fh,">:raw:encoding(UTF-8)",...)
  or die "open failed: $!";

将构建一个“二进制”流,无论平台默认值如何,但随后将启用 UTF-8 转换。

:pop

一个移除最顶层层的伪层。为 Perl 代码提供了一种操作层堆栈的方法。请注意,:pop 仅适用于真实层,不会撤销伪层或 :utf8 等标志的效果。一个可能的用例示例可能是

open(my $fh,...) or die "open failed: $!";
...
binmode($fh,":encoding(...)") or die "binmode failed: $!";
# next chunk is encoded
...
binmode($fh,":pop") or die "binmode failed: $!";
# back to un-encoded

需要一个更优雅(更安全)的接口。

自定义层

除了上述内置层之外,还可以编写自定义层,既可以在 C/XS 中编写,也可以在 Perl 中编写,作为名为 PerlIO::<layer name> 的模块。一些自定义层随 Perl 发行版一起提供。

:encoding

使用 :encoding(ENCODING) 透明地进行字符集和编码转换,例如从 Shift-JIS 到 Unicode。请注意,:encoding 还会启用 :utf8。有关更多信息,请参见 PerlIO::encoding

:mmap

一个使用 `mmap()` 实现文件“读取”的层,它将(整个)文件映射到进程的地址空间,然后将其用作 PerlIO 的“缓冲区”。在某些情况下,这对于大型文件可能更快,并且当多个进程读取同一个文件时,可能会导致更少的物理内存使用。

无法 `mmap()` 的文件将恢复为像 `:perlio` 层一样工作。写入也像 `:perlio` 层一样工作,因为用于写入的 `mmap()` 需要额外的维护(以扩展文件),这会抵消任何优势。

如果平台不支持 `mmap()`,则 `:mmap` 层将不存在。有关更多信息,请参阅 PerlIO::mmap

:via

:via(MODULE) 允许通过任意 Perl 模块应用转换,例如压缩/解压缩、加密/解密。有关更多信息,请参阅 PerlIO::via

:scalar

一个使用标量变量实现“内存中”文件的层,在打开此类句柄时自动用作平台默认值的替代。因此,预计标量将像文件一样工作,只包含或存储字节。有关更多信息,请参阅 PerlIO::scalar

原始的替代方案

要获得二进制流,另一种方法是使用

open(my $fh,"<","whatever") or die "open failed: $!";
binmode($fh) or die "binmode failed: $!";

这具有向后兼容旧版 Perl 的优点,这些旧版 Perl 没有使用 PerlIO 或 `:raw` 有错误(就像在 Perl 5.14 之前一样)。

要获得无缓冲流,请在 open 调用中指定无缓冲层(例如 `:unix`)

open(my $fh,"<:unix",$path) or die "open failed: $!";

默认值以及如何覆盖它们

如果平台类似于 MS-DOS 并且通常对文本文件进行 CRLF 到“\n”的转换,则默认层为

:unix:crlf

否则,如果 `Configure` 发现如何使用系统的 stdio 进行“快速”IO(在现代架构上并不常见),则默认层为

:stdio

否则,默认层为

:unix:perlio

请注意,“默认堆栈”取决于操作系统和 Perl 版本,以及 Perl 的编译时和运行时配置。默认值可以通过设置环境变量 PERLIO 为以空格或冒号分隔的层列表来覆盖,但是这不能用于设置需要加载模块(如 `:encoding`)的层。

这可以用来查看各种层的效应/错误,例如

cd .../perl/t
PERLIO=:stdio  ./perl harness
PERLIO=:perlio ./perl harness

有关 PERLIO 的各种值,请参阅 "PERLIO" in perlrun

下表总结了类 UNIX 和类 DOS 平台上的默认层,以及 `$ENV{PERLIO}` 的设置。

PERLIO     UNIX-like                   DOS-like
------     ---------                   --------
unset / "" :unix:perlio / :stdio [1]   :unix:crlf
:stdio     :stdio                      :stdio
:perlio    :unix:perlio                :unix:perlio

# [1] ":stdio" if Configure found out how to do "fast stdio" (depends
# on the stdio implementation) and in Perl 5.8, else ":unix:perlio"

查询文件句柄的层

以下返回文件句柄上的PerlIO 层名称

my @layers = PerlIO::get_layers($fh); # Or FH, *FH, "FH".

这些层按 open() 或 binmode() 调用使用它们的顺序返回,并且没有冒号。

默认情况下,将返回文件句柄输入端的层;要获取输出端,请使用可选的 output 参数

my @layers = PerlIO::get_layers($fh, output => 1);

(通常,文件句柄两端的层是相同的,但例如对于套接字,可能存在差异。)

没有 set_layers(),get_layers() 也不会返回一个反映堆栈的绑定数组,或者类似的东西。这不是偶然或无意的。PerlIO 层堆栈比仅仅一个堆栈更复杂(例如,请参见 :raw 的行为)。您应该使用 open() 和 binmode() 来操作堆栈。

以下是实现细节,请闭上眼睛。

默认情况下,层的参数在层名称后用括号返回,某些层(如 :utf8)不是真正的层,而是真正层的标志;要将所有这些都单独返回,请使用可选的 details 参数

my @layer_and_args_and_flags = PerlIO::get_layers($fh, details => 1);

结果将最多是层数的三倍:第一个元素将是名称,第二个元素是参数(未指定的参数将是 undef),第三个元素是标志,第四个元素是名称,依此类推。

现在您可以睁开眼睛了。

作者

Nick Ing-Simmons <[email protected]>

另请参见

"binmode" 在 perlfunc 中"open" 在 perlfunc 中perlunicodeperliolEncode