将一个内部 FILEHANDLE 与由 EXPR 指定的外部文件关联。随后,该文件句柄将允许您对该文件执行 I/O 操作,例如从其读取或写入其内容。
您也可以指定一个外部命令(加上可选的参数列表)或一个标量引用,而不是文件名,以便分别在命令或内存中的标量上打开文件句柄。
以下是关于 open
的详细参考。有关 open
基础知识的更温和介绍,请参阅 perlopentut 手册页。
大多数情况下,open
函数使用三个参数调用:必需的 FILEHANDLE(通常是一个空标量变量),后面跟着 MODE(通常是一个字面量,描述文件句柄将使用的 I/O 模式),然后是新文件句柄将引用的文件名。
从文件读取
open(my $fh, "<", "input.txt")
or die "Can't open < input.txt: $!";
# Process every line in input.txt
while (my $line = readline($fh)) {
#
# ... do something interesting with $line here ...
#
}
或写入文件
open(my $fh, ">", "output.txt")
or die "Can't open > output.txt: $!";
print $fh "This line gets printed into output.txt.\n";
有关这些常见文件句柄操作的摘要,请参阅 "perlintro 中的文件和 I/O"。
open
函数的第一个参数,在本参考中标记为 FILEHANDLE,通常是一个标量变量。(存在例外情况,在下面的“其他注意事项”中描述。)如果对 open
函数的调用成功,则作为 FILEHANDLE 提供的表达式将被分配一个打开的文件句柄。该文件句柄提供对指定外部文件的内部引用,方便地存储在 Perl 变量中,并准备用于 I/O 操作,例如读取和写入。
当使用三个或更多参数调用 open
函数时,第二个参数(此处标记为 MODE)定义打开模式。MODE 通常是一个字面量字符串,包含定义要创建的文件句柄的预期 I/O 角色的特殊字符:它是只读的,还是读写,等等。
如果 MODE 是 <
,则文件以输入(只读)模式打开。如果 MODE 是 >
,则文件以输出模式打开,现有文件首先被截断(“覆盖”),不存在的文件被新创建。如果 MODE 是 >>
,则文件以追加模式打开,如果必要,也会被创建。
您可以在 >
或 <
前面放置一个 +
,表示您希望对文件进行读写访问;因此 +<
通常是读写更新的首选模式——+>
模式会先覆盖文件。您通常不能使用任何读写模式来更新文本文件,因为它们具有可变长度的记录。有关更好的方法,请参阅 perlrun 中的 -i 开关。文件以 0666
的权限创建,并由进程的 umask
值修改。
这些不同的前缀对应于 fopen(3) 的 r
、r+
、w
、w+
、a
和 a+
模式。
不同模式在实际应用中的更多示例
# Open a file for concatenation
open(my $log, ">>", "/usr/spool/news/twitlog")
or warn "Couldn't open log file; discarding input";
# Open a file for reading and writing
open(my $dbase, "+<", "dbase.mine")
or die "Can't open 'dbase.mine' for update: $!";
open
函数在成功时返回非零值,否则返回未定义值。如果 open
操作涉及管道,则返回值恰好是子进程的进程 ID。
在打开文件时,如果请求失败,继续执行通常不是一个好主意,因此 open
通常与 die
一起使用。即使您希望代码在打开文件失败时执行其他操作,而不是 die
,您也应该始终检查打开文件的返回值。
您可以使用 open
函数的三参数形式来指定要应用于新文件句柄的 I/O 层(有时称为“规则”)。这些会影响输入和输出的处理方式(有关详细信息,请参阅 open 和 PerlIO)。例如
# loads PerlIO::encoding automatically
open(my $fh, "<:encoding(UTF-8)", $filename)
|| die "Can't open UTF-8 encoded $filename: $!";
这将打开包含 Unicode 字符的 UTF8 编码文件;请参阅 perluniintro。请注意,如果在三参数形式中指定了层,则存储在 ${^OPEN}
中的默认层(通常由 open 编译指令或 -CioD
开关设置)将被忽略。如果您指定一个冒号,后面没有名称,这些层也会被忽略。在这种情况下,将使用操作系统的默认层(在 Unix 上为 :raw,在 Windows 上为 :crlf)。
在某些系统(通常是基于 DOS 和 Windows 的系统)上,当您不处理文本文件时,需要使用 binmode
。为了便携性,最好在适当的时候始终使用它,并且在不合适的时候不要使用它。此外,人们可以将他们的 I/O 默认设置为 UTF8 编码的 Unicode,而不是字节。
undef
创建临时文件作为一种特殊情况,三参数形式使用读/写模式,第三个参数为 undef
open(my $tmp, "+>", undef) or die ...
将文件句柄打开到一个新创建的空匿名临时文件。(这在任何模式下都会发生,这使得 +>
成为唯一有用的和明智的模式。)您将需要使用 seek
来进行读取。
您可以直接将文件句柄打开到 Perl 标量,而不是打开到程序外部的文件或其他资源。为此,请将该标量的引用作为第三个参数提供给 open
,如下所示
open(my $memory, ">", \$var)
or die "Can't open memory file: $!";
print $memory "foo!\n"; # output will appear in $var
要将 STDOUT
或 STDERR
(重新)打开为内存文件,请先关闭它
close STDOUT;
open(STDOUT, ">", \$variable)
or die "Can't open STDOUT: $!";
内存文件的标量被视为八位字节字符串:除非文件以截断方式打开,否则标量可能不包含任何超过 0xFF 的代码点。
打开内存文件可能会因各种原因而失败。与任何其他 open
一样,请检查返回值以确定是否成功。
技术说明:此功能仅在 Perl 使用 PerlIO 构建时才有效 - 这是默认设置,除了使用较旧的(5.16 之前的)Perl 安装程序配置为不包含它(例如,通过 Configure -Uuseperlio
)。您可以通过运行 perl -V:useperlio
来查看您的 Perl 是否使用 PerlIO 构建。如果它显示 'define'
,则您有 PerlIO;否则没有。
有关 PerlIO 的详细信息,请参阅 perliol。
如果 MODE 为 |-
,则文件名被解释为一个命令,输出将被管道传输到该命令,如果 MODE 为 -|
,则文件名被解释为一个命令,该命令将输出管道传输到我们。在两个参数(和一个参数)形式中,应该用命令替换破折号(-
)。有关此的更多示例,请参阅 "在 perlipc 中使用 open() 进行 IPC"。(您不允许 open
到一个同时管道传输输入和输出的命令,但请参阅 IPC::Open2、IPC::Open3 以及 "在 perlipc 中进行与另一个进程的双向通信" 以了解替代方案。)
open(my $article_fh, "-|", "caesar <$article") # decrypt
# article
or die "Can't start caesar: $!";
open(my $article_fh, "caesar <$article |") # ditto
or die "Can't start caesar: $!";
open(my $out_fh, "|-", "sort >Tmp$$") # $$ is our process id
or die "Can't start sort: $!";
在采用三个或更多参数的管道打开形式中,如果指定了 LIST(命令名称后的额外参数),则如果平台支持,LIST 将成为调用命令的参数。对于非管道模式,具有三个以上参数的 open
的含义尚未定义,但实验性的“层”可能会赋予额外的 LIST 参数意义。
如果您在命令中打开一个管道 -
(即,使用 open
的单参数或双参数形式指定 |-
或 -|
),则会隐式执行 fork
,因此 open
会返回两次:在父进程中,它返回子进程的 pid;在子进程中,它返回(已定义的)0
。使用 defined($pid)
或 //
来确定打开是否成功。
例如,使用以下任一方法:
my $child_pid = open(my $from_kid, "-|")
// die "Can't fork: $!";
或
my $child_pid = open(my $to_kid, "|-")
// die "Can't fork: $!";
然后,使用
if ($child_pid) {
# am the parent:
# either write $to_kid or else read $from_kid
...
waitpid $child_pid, 0;
} else {
# am the child; use STDIN/STDOUT normally
...
exit;
}
对于父进程,文件句柄的行为正常,但对该文件句柄的 I/O 从子进程的 STDOUT/STDIN 管道进出。在子进程中,文件句柄未打开——I/O 从新的 STDOUT/STDIN 进出。通常,当您想要对管道命令的执行方式进行更多控制时,例如在运行 setuid 时,您不想扫描 shell 命令中的元字符,就会使用这种方法。
以下代码块或多或少等效
open(my $fh, "|tr '[a-z]' '[A-Z]'");
open(my $fh, "|-", "tr '[a-z]' '[A-Z]'");
open(my $fh, "|-") || exec 'tr', '[a-z]', '[A-Z]';
open(my $fh, "|-", "tr", '[a-z]', '[A-Z]');
open(my $fh, "cat -n '$file'|");
open(my $fh, "-|", "cat -n '$file'");
open(my $fh, "-|") || exec "cat", "-n", $file;
open(my $fh, "-|", "cat", "-n", $file);
每个代码块中的最后两个示例显示了管道作为“列表形式”,这种形式在所有平台上尚未得到支持。(如果您的平台具有真正的 fork
,例如 Linux 和 macOS,则可以使用列表形式;它在使用 Perl 5.22 或更高版本的 Windows 上也能正常工作。)您可能希望使用管道的列表形式,这样您就可以将文字参数传递给命令,而无需担心 shell 解释其中的任何 shell 元字符。但是,这也阻止您将管道打开到包含 shell 元字符的命令,例如
open(my $fh, "|cat -n | expand -4 | lpr")
|| die "Can't open pipeline to lpr: $!";
有关更多示例,请参阅 perlipc 中的“安全管道打开”。
您也可以按照 Bourne shell 的传统,指定以 >&
开头的 EXPR,在这种情况下,字符串的其余部分将被解释为要复制(如 dup(2))并打开的文件句柄(或文件描述符,如果为数字)。您可以在 >
、>>
、<
、+>
、+>>
和 +<
后使用 &
。您指定的模式应与原始文件句柄的模式匹配。(复制文件句柄不会考虑 I/O 缓冲区中任何现有的内容。)如果您使用三参数形式,则可以传递数字、文件句柄名称或正常的“对 glob 的引用”。
以下是一个使用各种方法保存、重定向和恢复 STDOUT
和 STDERR
的脚本
#!/usr/bin/perl
open(my $oldout, ">&STDOUT")
or die "Can't dup STDOUT: $!";
open(OLDERR, ">&", \*STDERR)
or die "Can't dup STDERR: $!";
open(STDOUT, '>', "foo.out")
or die "Can't redirect STDOUT: $!";
open(STDERR, ">&STDOUT")
or die "Can't dup STDOUT: $!";
select STDERR; $| = 1; # make unbuffered
select STDOUT; $| = 1; # make unbuffered
print STDOUT "stdout 1\n"; # this works for
print STDERR "stderr 1\n"; # subprocesses too
open(STDOUT, ">&", $oldout)
or die "Can't dup \$oldout: $!";
open(STDERR, ">&OLDERR")
or die "Can't dup OLDERR: $!";
print STDOUT "stdout 2\n";
print STDERR "stderr 2\n";
如果您指定 `'<&=X'`,其中 `X` 是文件描述符编号或文件句柄,那么 Perl 将执行等效于 C 的 fdopen(3) 的操作,该操作针对该文件描述符(并且不会调用 dup(2));这对于文件描述符来说更加节俭。例如
# open for input, reusing the fileno of $fd
open(my $fh, "<&=", $fd)
或
open(my $fh, "<&=$fd")
或
# open for append, using the fileno of $oldfh
open(my $fh, ">>&=", $oldfh)
在文件句柄上保持节俭也是有用的(除了节俭之外),例如当某些东西依赖于文件描述符时,例如使用 flock
进行锁定。如果您只执行 `open(my $A, ">>&", $B)`,那么文件句柄 `$A` 将不会与 `$B` 具有相同的文件描述符,因此 `flock($A)` 不会 `flock($B)`,反之亦然。但是,使用 `open(my $A, ">>&=", $B)`,文件句柄将共享相同的基础系统文件描述符。
请注意,在低于 5.8.0 的 Perl 版本中,Perl 使用标准 C 库的 fdopen(3) 来实现 `=` 功能。在许多 Unix 系统上,当文件描述符超过某个值(通常为 255)时,fdopen(3) 会失败。对于 5.8.0 及更高版本的 Perl,PerlIO 是(通常)默认的。
本节描述了在最佳实践之外调用 `open` 的方法;您可能会在旧代码中遇到这些用法。Perl 并不认为它们的用法已过时,但也不建议在新代码中使用,为了清晰和可读性。
在调用的一参数和两参数形式中,模式和文件名应连接在一起(按此顺序),最好用空格隔开。您可以在这些形式中省略模式(但不要这样做),当该模式为 `<` 时。如果文件名参数是已知的文字,则可以使用 open
的两参数形式。
open(my $dbase, "+<dbase.mine") # ditto
or die "Can't open 'dbase.mine' for update: $!";
在两参数(和一参数)形式中,打开 `<-` 或 `-` 将打开 STDIN,打开 `>-` 将打开 STDOUT。
新代码应该优先使用 `open` 的三参数形式,而不是旧的这种形式。将模式和文件名声明为两个不同的参数可以避免两者之间的混淆。
作为一种快捷方式,一个参数的调用会从与文件句柄同名的全局标量变量中获取文件名。
$ARTICLE = 100;
open(ARTICLE)
or die "Can't find article $ARTICLE: $!\n";
一种旧的风格是使用一个裸字作为文件句柄,例如
open(FH, "<", "input.txt")
or die "Can't open < input.txt: $!";
然后你就可以使用 `FH` 作为文件句柄,在 `close FH` 和 `<FH>` 等等中使用。注意,它是一个全局变量,所以当处理除 Perl 内置文件句柄(例如 STDOUT 和 STDIN)以外的文件句柄时,不推荐使用这种形式。事实上,当 `bareword_filehandles` 特性被禁用时,使用裸字作为文件句柄是一个错误。当在 `use v5.36.0` 或更高版本的范围内时,此特性默认情况下会被禁用。
当文件句柄的引用计数达到零时,它将被关闭。如果它是一个使用 my
声明的词法作用域变量,这通常意味着封闭作用域的结束。但是,这种自动关闭不会检查错误,因此最好显式地关闭文件句柄,特别是那些用于写入的文件句柄。
close($handle)
|| warn "close failed: $!";
Perl 会尝试在任何可能进行 fork 的操作之前刷新所有以输出方式打开的文件,但这可能在某些平台上不受支持(参见 perlport)。为了安全起见,你可能需要设置 $|
(在 English 中为 `$AUTOFLUSH`)或调用 IO::Handle
的 `autoflush` 方法来处理任何打开的句柄。
在支持文件上的 close-on-exec 标志的系统上,该标志将根据 $^F
的值被设置为新打开的文件描述符。参见 "$^F" in perlvar。
关闭任何管道文件句柄会导致父进程等待子进程完成,然后在 $?
和 ${^CHILD_ERROR_NATIVE}
中返回状态值。
如果 FILEHANDLE(open
调用中的第一个参数)是一个未定义的标量变量(或数组或哈希元素),则会自动创建新的文件句柄,这意味着该变量被分配了一个指向新分配的匿名文件句柄的引用。否则,如果 FILEHANDLE 是一个表达式,则其值就是真实的文件句柄。(这被认为是符号引用,因此 use strict "refs"
不应该生效。)
传递给 open
的一元和二元形式的文件名将删除前导和尾随空格,并遵循正常的重定向字符。此属性称为“魔法打开”,通常可以有效地使用。用户可以指定一个名为 "rsh cat file |" 的文件名,或者可以根据需要更改某些文件名。
$filename =~ s/(.*\.gz)\s*$/gzip -dc < $1|/;
open(my $fh, $filename)
or die "Can't open $filename: $!";
使用三元形式打开包含任意奇怪字符的文件,
open(my $fh, "<", $file)
|| die "Can't open $file: $!";
否则需要保护任何前导和尾随空格
$file =~ s#^(\s)#./$1#;
open(my $fh, "< $file\0")
|| die "Can't open $file: $!";
(这可能在某些奇怪的文件系统上不起作用)。应该认真选择 open
的 魔法 和 三元 形式
open(my $in, $ARGV[0]) || die "Can't open $ARGV[0]: $!";
将允许用户指定 "rsh cat file |"
形式的参数,但不会在文件名恰好包含尾随空格的情况下工作,而
open(my $in, "<", $ARGV[0])
|| die "Can't open $ARGV[0]: $!";
将具有完全相反的限制。(但是,一些 shell 支持语法 perl your_program.pl <( rsh cat file )
,它会生成一个可以正常打开的文件名。)
open
如果你想要一个“真正的”C open(2),那么你应该使用 sysopen
函数,它不涉及任何此类魔法(但使用与 Perl open
不同的文件模式,它对应于 C fopen(3))。这是另一种保护文件名免受解释的方法。例如
use IO::Handle;
sysopen(my $fh, $path, O_RDWR|O_CREAT|O_EXCL)
or die "Can't open $path: $!";
$fh->autoflush(1);
print $fh "stuff $$\n";
seek($fh, 0, 0);
print "File contains: ", readline($fh);
有关混合读写的一些详细信息,请参阅 seek
。