绝大多数常见的 IO 工作都是由IO::Path类型完成的。如果您想以某种形式或形状读取或写入文件,这就是您想要的类。它抽象出文件句柄(或“文件描述符”)的细节,因此你甚至不必考虑它们。
在幕后,IO::Path 与 IO::Handle 一起使用 ; 一个你可以直接使用的类,如果你需要比 IO::Path 提供的更多控制。当与其他进程,例如通过 Proc 或 Proc::Async类型,您还可以处理IO::Handle 的*子类*:在IO::Pipe。
最后,你有 IO::CatHandle,以及 IO::Spec 及其子类,你很少直接使用它们。这些类为您提供了高级功能,例如将多个文件作为一个句柄进行操作,或者进行低级路径操作。
除了所有这些类之外,Raku 还提供了几个子程序,可以让您间接使用这些类。如果您喜欢函数式编程风格或 Raku 单行程序,这些就派上用场了。
虽然 IO::Socket 及其子类也与输入和输出有关,但本指南并未涵盖它们。
say 'my-file.txt'.IO; # OUTPUT: «"my-file.txt".IO»
看起来这里似乎缺少某些东西 - 没有卷或绝对路径 - 但该信息实际上存在于对象中。你可以通过使用 .perl
方法看到它:
say 'my-file.txt'.IO.perl;
# OUTPUT: «IO::Path.new("my-file.txt", :SPEC(IO::Spec::Unix), :CWD("/home/camelia"))»
这两个额外的属性 - SPEC
和 - CWD
指定路径应该使用的操作系统语义类型以及路径的“当前工作目录”,即如果它是相对路径,则它相对于该目录。
但是,不要急于将任何东西字符串化起来。将路径作为 IO::Path 对象传递。在路径上运行的所有例程都可以处理它们,因此不需要转换它们。
"my-file.txt".IO.spurt: "I ♥ Perl!";
上面的代码在当前目录中创建了一个名为 my-file.txt
的文件,然后将文本 I ♥ Perl!
写入其中。如果 Raku 是您的第一语言,请庆祝您任务完成了!尝试打开您使用其他程序创建的文件,以验证您使用程序编写的内容。如果您已经了解其他语言,您可能想知道本指南是否遗漏了处理编码或错误条件等问题。
如果您想在我们在上一节中创建的文件中添加更多内容,您可以注意 spurt
文档中提到的 :append
参数。但是,为了更好地控制,让我们自己使用 IO::Handle 来处理:
my $fh = 'my-file.txt'.IO.open: :a;
$fh.print: "I count: ";
$fh.print: "$_ " for ^10;
$fh.close;
.open
方法调用打开我们的 IO::Path,并返回一个 IO::Handle。我们把 :a
作为参数传递,表示我们想要以追加模式打开文件。
在接下来的两行代码中,我们使用 IO::Handle 上的 .print
常用方法打印包含 11 个文本('I count: '
字符串和 10 个数字)的文本行。请注意,Failure 机制再一次负责我们的所有错误检查。如果 .open
失败,它将返回一个 Failure,当我们尝试在其上调用 .print
方法时将抛出异常。
最后,我们通过调用它上面的 .close
方法来关闭 IO::Handle。这样*做很重要*,特别是在大型程序或处理大量文件的程序中,因为许多系统对程序可以同时打开的文件数量有限制。如果您没有关闭句柄,最终您将达到该限制并且 .open
调用将失败。请注意,与其他一些语言不同,Raku 不使用引用计数,因此当离开所定义的作用域时,文件句柄不会关闭。只有当它们被垃圾收集并且未能关闭句柄时,它们才会被关闭,这可能会导致程序在打开的句柄有机会在垃圾回收*之前*达到文件限制。
我们在前面的章节中已经看到,在文件中写东西是 Raku 中的单行代码。从它们中读取,同样容易:
say 'my-file.txt'.IO.slurp; # OUTPUT: «I ♥ Perl!»
say 'my-file.txt'.IO.slurp: :bin; # OUTPUT: «Buf[uint8]:0x<49 20 e2 99 a5 20 50 65 72 6c 21>»
由于 slurping 将整个文件加载到内存中,因此它不适合处理大文件。
这是一个示例,它在文本文件中查找提及 Perl 的行并将其打印出来。尽管文件本身太大而无法容纳到可用的RAM 中,但程序运行时不会出现任何问题,因为内容是以小块的形式处理的:
.say for '500-PetaByte-File.txt'.IO.lines.grep: *.contains: 'Perl';
这是另一个打印文件中前 100 个单词的示例,没有完全加载它:
.say for '500-PetaByte-File.txt'.IO.words: 100
当然,您可以使用 IO::Handle 类型从文件中读取,这样可以更好地控制您正在执行的操作:
given 'some-file.txt'.IO.open {
say .readchars: 8; # OUTPUT: «I ♥ Perl»
.seek: 1, SeekFromCurrent;
say .readchars: 15; # OUTPUT: «I ♥ Programming»
.close
}
IO::Handle 给你 .read,.readchars,.get,.getc,.words,.lines,.slurp,.comb,.split 和 .Supply 方法从中读取数据。有很多选择; 当你读取完时,需要关闭句柄。
与某些语言不同,当离开定义的作用域时,句柄不会自动关闭。相反,它将保持打开,直到被垃圾回收为止。为了使关闭更容易,一些方法允许您指定 :close
参数,您还可以使用 will leave
trait 或 Trait::IO
模块提供的 does auto-close
trait。
本节介绍如何不执行 Raku IO。
您可能听说过 $*SPEC
并看到过一些代码或书籍显示其用于拆分和连接路径片段的用法。它提供的一些例程名称甚至可能看起来与您在其他语言中使用的名称相似。
提示:您可以使用 /
连接路径部分并将其提供给 IO::Path
例程; 无论操作系统如何,他们仍然可以做正确的事情。
# WRONG!! TOO MUCH WORK!
my $fh = open $*SPEC.catpath: '', 'foo/bar', $file;
my $data = $fh.slurp;
$fh.close;
# RIGHT! Use IO::Path to do all the dirty work
my $data = 'foo/bar'.IO.add($file).slurp;
{
temp $*OUT = open :w, $*SPEC.devnull;
say "In space no one can hear you scream!";
}
say "Hello";
不要使用 .Str
方法对 IO::Path
对象进行字符串化,除非您只是想将它们显示在某个地方以供参考或使用。.Str
方法返回 IO::Path
实例化的任何基本路径字符串。它不考虑 $.CWD
属性的值。例如,此代码已损坏:
my $path = 'foo'.IO;
chdir 'bar';
# WRONG!! .Str DOES NOT USE $.CWD!
run <tar -cvvf archive.tar>, $path.Str;
chdir
调用更改了当前目录的值,但我们创建的 $path
是相对于该更改之前的目录。
但是,IO::Path
对象*确实*知道它相对于哪个目录。我们只需要使用 .absolute
或 .relative
来字符串化对象。两个例程都返回一个 Str
对象; 它们不同之处在于结果是绝对路径还是相对路径。所以,我们可以像这样修复我们的代码:
my $path = 'foo'.IO;
chdir 'bar';
# RIGHT!! .absolute does consider the value of $.CWD!
run <tar -cvvf archive.tar>, $path.absolute;
# Also good:
run <tar -cvvf archive.tar>, $path.relative;
这段代码是错误的:
# WRONG!!
my $*CWD = "foo".IO;
temp $*CWD = "foo".IO;