Skip to content

Perl 文件 I/O

Perl 中的文件 I/O (输入/输出) 涉及将文件句柄 (filehandle) 与外部实体(通常是文件)关联起来,然后使用 Perl 的运算符和函数来从该实体读取或向其写入。

文件句柄 (filehandle) 是 Perl 内部的一个名称,用于连接你的脚本与文件或设备。现代 Perl 使用词法文件句柄 (lexical filehandles)(存储文件句柄的变量),以更好地控制作用域和处理错误。

Perl 预定义了三个标准文件句柄:STDIN(标准输入),STDOUT(标准输出),以及 STDERR(标准错误)。

用于打开文件的主要函数是 open()。对于更低级别的控制,可以使用 sysopen()。

现代 open() 语法(三参数形式):

open(my $fh, '<', $filename) or die "Cannot open $filename for reading: $!";
open(my $fh, '>', $filename) or die "Cannot open $filename for writing: $!";
open(my $fh, '>>', $filename) or die "Cannot open $filename for appending: $!";

$fh 是一个词法文件句柄 (lexical filehandle)。$filename 是文件路径。第二个参数是模式 (mode)。如果 open() 失败,$! 包含系统错误消息。

强烈建议在脚本开头添加 use strict; 和 use warnings; 以进行健壮的错误检查。

以下是三参数 open() 函数常用的模式表格:

模式字符串描述
’<‘以读取模式打开(只读)。文件必须存在。
’>‘以写入模式打开。如果文件不存在则创建,如果存在则截断(清空)。
’>>‘以追加模式打开。如果文件不存在则创建。写入内容将添加到文件末尾。
’+<‘以读写模式打开。文件必须存在。
’+>‘以读写模式打开。如果文件不存在则创建,如果存在则截断。
’+>>‘以读写和追加模式打开。如果文件不存在则创建。写入内容将添加到文件末尾。

为了处理字符编码(例如 UTF-8),你可以在模式中添加层 (layer):

open(my $fh, '<:encoding(UTF-8)', $filename) or die "...: $!";
open(my $fh, '>:encoding(UTF-8)', $filename) or die "...: $!";

示例:从文件读取

#!/usr/bin/perl
use strict;
use warnings;
my $filename = 'file.txt';
open(my $fh, '<', $filename) or die "Could not open file '$filename': $!";
print "Reading from $filename:\n";
while (my $row = <$fh>) {
chomp $row; # 移除换行符
print "Line: $row\n";
}
close($fh);

示例:写入文件(如果存在则截断)

#!/usr/bin/perl
use strict;
use warnings;
my $filename = 'output.txt';
open(my $fh, '>', $filename) or die "Could not open file '$filename' for writing: $!";
print $fh "Hello, file!\n";
print $fh "This is a new line.\n";
close($fh);
print "Data written to $filename\n";

sysopen() 提供了对系统底层 open() 调用的访问。它需要 Fcntl 模块来获取常量。

sysopen(my $fh, $filename, $flags) or die "sysopen $filename: $!";
sysopen(my $fh, $filename, $flags, $perms) or die "sysopen $filename: $!";

$flags 是常量,例如 O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_TRUNC、O_APPEND(从 Fcntl 模块导入)。$perms 指定文件创建时的权限(例如 0666)。

使用 sysopen 的示例:

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl qw(:DEFAULT O_CREAT O_TRUNC); # 导入常量
my $filename = 'sysopened.txt';
my $permissions = 0644;
sysopen(my $fh, $filename, O_WRONLY | O_CREAT | O_TRUNC, $permissions)
or die "Cannot sysopen $filename: $!";
print $fh "Written via sysopen.\n";
close($fh);
print "Data written to $filename using sysopen.\n";

sysopen 模式常用的 Fcntl 常量:

常量描述
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_CREAT如果文件不存在则创建
O_APPEND追加到文件
O_TRUNC截断文件(清空)
O_EXCL与 O_CREAT 一起使用,如果文件已存在则失败
O_NONBLOCK非阻塞 I/O

close() 函数刷新缓冲区并将文件句柄与文件解除关联。

close($fh) or die "Could not close filehandle: $!";

检查 close() 的返回值是个好习惯,特别是对于输出文件句柄,因为刷新过程中可能发生错误。

一旦文件句柄打开,就可以从中读取或向其写入。

从文件句柄读取的主要方式是菱形运算符 <$fh>。在标量上下文 (scalar context) 中,它读取一行。在列表上下文 (list context) 中,它读取所有剩余的行。

#!/usr/bin/perl
use strict;
use warnings;
# 假设 'input.txt' 存在且有内容
my $filename_in = 'input.txt';
open(my $in_fh, '<', $filename_in) or die "Cannot open $filename_in: $!";
# 标量上下文:读取一行
my $first_line = <$in_fh>;
chomp $first_line if defined $first_line;
print "First line: $first_line\n";
# 列表上下文:将所有剩余行读入数组
my @remaining_lines = <$in_fh>;
close($in_fh);
print "Remaining lines:\n";
for my $line (@remaining_lines) {
chomp $line;
print "- $line\n";
}

<STDIN> 从标准输入读取一行。

getc($fh) 从文件句柄读取单个字符。出错或到达文件末尾时返回 undef。

read($fh, my $buffer, $length, $offset) 将 $length 字节读入 $buffer。如果指定了 $offset,则从该偏移量开始读入 $buffer。适用于处理二进制数据。

print $fh LIST 将 LIST 中的项写入指定的文件句柄。如果省略了 $fh,则打印到 STDOUT(或当前选定的默认输出句柄)。

#!/usr/bin/perl
use strict;
use warnings;
my $filename_out = 'greetings.txt';
open(my $out_fh, '>', $filename_out) or die "Cannot open $filename_out: $!";
print $out_fh "Hello, world!\n"; # 写入文件
print "Message written to $filename_out\n"; # 写入标准输出
close($out_fh);

一项常见的任务是将一个文件的内容复制到另一个文件。

#!/usr/bin/perl
use strict;
use warnings;
my $source_file = 'source.txt'; # 假设此文件存在
my $dest_file = 'destination.txt';
open(my $source_fh, '<', $source_file)
or die "Cannot open source file '$source_file': $!";
open(my $dest_fh, '>', $dest_file)
or die "Cannot open destination file '$dest_file': $!";
# 逐行复制
while (my $line = <$source_fh>) {
print $dest_fh $line;
}
close($source_fh);
close($dest_fh) or die "Error closing destination file '$dest_file': $!";
print "File '$source_file' copied to '$dest_file'\n";

对于二进制文件或较大文件,可以考虑使用 read() 分块读取以提高效率。

rename() 函数用于重命名文件。

#!/usr/bin/perl
use strict;
use warnings;
my $old_name = 'oldfile.txt';
my $new_name = 'newfile.txt';
# 为示例创建一个空的旧文件
open(my $fh, '>', $old_name) or die "Could not create $old_name: $!";
print $fh "This is the old file.\n";
close($fh);
if (rename($old_name, $new_name)) {
print "File '$old_name' renamed to '$new_name' successfully.\n";
} else {
warn "Could not rename '$old_name' to '$new_name': $!";
}

unlink() 函数删除一个或多个文件。

#!/usr/bin/perl
use strict;
use warnings;
my $file_to_delete = 'tempfile.txt';
# 为示例创建一个空的模拟文件
open(my $fh, '>', $file_to_delete) or die "Could not create $file_to_delete: $!";
print $fh "This file will be deleted.\n";
close($fh);
if (unlink($file_to_delete)) {
print "File '$file_to_delete' deleted successfully.\n";
} else {
warn "Could not delete '$file_to_delete': $!";
}

tell() 返回文件指针的当前位置(以字节为单位)。seek() 将文件指针移动到指定位置。

my $current_position = tell($fh);
seek($fh, $position, $whence);

$position 是偏移量。$whence 决定如何解释 $position:

  • 0:从文件开头开始定位 (SEEK_SET)。
  • 1:从当前位置开始定位 (SEEK_CUR)。
  • 2:从文件末尾开始定位 (SEEK_END)。

这些常量 (SEEK_SET, SEEK_CUR, SEEK_END) 可以从 Fcntl 或 IO::Seekable 模块导入。使用数字值 0, 1, 2 也同样常见。

# 示例:从开头定位到第 10 个字节
seek($fh, 10, 0) or die "Seek failed: $!";

Perl 提供了文件测试运算符(例如 -e, -f, -d)来检查文件或目录的各种属性。

#!/usr/bin/perl
use strict;
use warnings;
my $file = 'example.txt'; # 或任意文件/目录路径
# 创建一个用于测试的空文件
open (my $fh, '>', $file) or die "Could not create $file: $!";
print $fh "Some content\n";
close ($fh);
if (-e $file) {
print "'$file' exists.\n";
print "'$file' is a plain file.\n" if (-f _); # _ 指代最近一次 stat 操作的文件句柄
print "'$file' is a directory.\n" if (-d _);
print "'$file' is readable.\n" if (-r _);
print "'$file' is writable.\n" if (-w _);
print "'$file' is executable.\n" if (-x _);
print "'$file' size is: ", -s _, " bytes.\n" if (-s _);
print "'$file' is a text file.\n" if (-T _);
print "'$file' is a binary file.\n" if (-B _);
} else {
print "'$file' does not exist.\n";
}
unlink $file; # 清理测试用的空文件

常用文件测试运算符:

运算符定义
-e文件或目录存在。
-f是一个普通文件。
-d是一个目录。
-r对有效 UID/GID 可读。
-w对有效 UID/GID 可写。
-x对有效 UID/GID 可执行。
-s文件大小(字节)。返回大小,如果文件不存在则返回 undef。
-z文件大小为零(为空)。
-l是一个符号链接。
-T文件看起来像文本文件。
-B文件看起来像二进制文件。
-M文件的年龄(修改时间 - 脚本启动时间),以天为单位。
-A文件的年龄(访问时间 - 脚本启动时间),以天为单位。
-C文件的年龄(inode 改变时间 - 脚本启动时间),以天为单位。

对于更高级的文件和目录操作,可以考虑使用 CPAN 模块,例如 Path::Tiny 或 File::Spec。