基础知识

在幕后,IO::Path 与 一起使用 ; 一个你可以直接使用的类,如果你需要比 IO::Path 提供的更多控制。当与其他进程,例如通过 或 Proc::Async类型,您还可以处理 的*子类*:在IO::Pipe

最后,你有 ,以及 IO::Spec 及其子类,你很少直接使用它们。这些类为您提供了高级功能,例如将多个文件作为一个句柄进行操作,或者进行低级路径操作。

除了所有这些类之外,Raku 还提供了几个子程序,可以让您间接使用这些类。如果您喜欢函数式编程风格或 Raku 单行程序,这些就派上用场了。

虽然 及其子类也与输入和输出有关,但本指南并未涵盖它们。

导航路径

要将路径表示为文件或目录,请使用 IO::Path 类型。获取该类型对象的最简单方法是通过在它身上调用 方法强制将 Str 类型转为路径类型:

看起来这里似乎缺少某些东西 - 没有卷或绝对路径 - 但该信息实际上存在于对象中。你可以通过使用 方法看到它:

  1. »

这两个额外的属性 - SPEC 和 - CWD 指定路径应该使用的操作系统语义类型以及路径的“当前工作目录”,即如果它是相对路径,则它相对于该目录。

这意味着无论你如何制作一个路径,IO::Path 对象在技术上总是指一个绝对路径。这就是它的 和 .relative 方法返回 对象的原因,它们是字符串化路径的正确方法。

但是,不要急于将任何东西字符串化起来。将路径作为 IO::Path 对象传递。在路径上运行的所有例程都可以处理它们,因此不需要转换它们。

Writing new content

让我们制作一些文件并从中写入和读取数据!spurt 和 程序写和读取一块儿数据。除非您正在处理难以完全存储在内存中的非常大的文件,否则这两个例程都适合您。

  1. "my-file.txt".IO.spurt: "I ♥ Perl!";

上面的代码在当前目录中创建了一个名为 my-file.txt 的文件,然后将文本 I ♥ Perl! 写入其中。如果 Raku 是您的第一语言,请庆祝您任务完成了!尝试打开您使用其他程序创建的文件,以验证您使用程序编写的内容。如果您已经了解其他语言,您可能想知道本指南是否遗漏了处理编码或错误条件等问题。

但是,这就是您需要的所有代码。默认情况下,字符串将按 utf-8 编码进行编码,并通过 Failure 机制处理错误:这些是您可以使用常规条件处理的异常。在这种情况下,我们会让所有潜在的 在调用之后陷入沉没,因此它们包含的任何异常都将被抛出。

追加内容

  1. my $fh = 'my-file.txt'.IO.open: :a;
  2. $fh.print: "I count: ";
  3. $fh.print: "$_ " for ^10;
  4. $fh.close;

.open 方法调用打开我们的 ,并返回一个 IO::Handle。我们把 :a 作为参数传递,表示我们想要以追加模式打开文件。

在接下来的两行代码中,我们使用 上的 .print 常用方法打印包含 11 个文本('I count: ' 字符串和 10 个数字)的文本行。请注意, 机制再一次负责我们的所有错误检查。如果 .open 失败,它将返回一个 ,当我们尝试在其上调用 .print 方法时将抛出异常。

最后,我们通过调用它上面的 方法来关闭 IO::Handle。这样*做很重要*,特别是在大型程序或处理大量文件的程序中,因为许多系统对程序可以同时打开的文件数量有限制。如果您没有关闭句柄,最终您将达到该限制并且 调用将失败。请注意,与其他一些语言不同,Raku 不使用引用计数,因此当离开所定义的作用域时,文件句柄不会关闭。只有当它们被垃圾收集并且未能关闭句柄时,它们才会被关闭,这可能会导致程序在打开的句柄有机会在垃圾回收*之前*达到文件限制。

使用 IO::Path

我们在前面的章节中已经看到,在文件中写东西是 Raku 中的单行代码。从它们中读取,同样容易:

.slurp 方法读取文件的全部内容并将其作为单个 对象返回,如果请求二进制模式,则通过指定 :bin 命名参数将其作为 Buf 对象返回。

由于 将整个文件加载到内存中,因此它不适合处理大文件。

IO::Path 类型提供了另外两种方便的方法: 与 .lines,这俩方法惰性地读取小块文件并返回(默认)不保留已消耗值的 对象。

这是一个示例,它在文本文件中查找提及 Perl 的行并将其打印出来。尽管文件本身太大而无法容纳到可用的RAM 中,但程序运行时不会出现任何问题,因为内容是以小块的形式处理的:

这是另一个打印文件中前 100 个单词的示例,没有完全加载它:

  1. .say for '500-PetaByte-File.txt'.IO.words: 100

请注意,我们通过传递 limit 参数给 而不是使用列表索引操作 来完成此操作。原因是在于底层仍然使用文件句柄,并且在完全使用返回的 之前,句柄将保持打开状态。如果没有引用 Seq,最终句柄将在垃圾收集运行期间关闭,但在大型程序中使用大量文件时,最好确保所有句柄立即关闭。所以,你应该始终确保 从IO::Path 的 和 .lines 方法是 ; 而 limit 参数可以帮助你。

Using IO::Handle

当然,您可以使用 类型从文件中读取,这样可以更好地控制您正在执行的操作:

  1. given 'some-file.txt'.IO.open {
  2. say .readchars: 8; # OUTPUT: «I ♥ Perl
  3. »
  4. .seek: 1, SeekFromCurrent;
  5. say .readchars: 15; # OUTPUT: «I ♥ Programming
  6. »
  7. .close

IO::Handle 给你 ,.readchars,,.getc,,.lines,,.comb, 和 .Supply 方法从中读取数据。有很多选择; 当你读取完时,需要关闭句柄。

错误的做事方法

本节介绍如何不执行 Raku IO。

您可能听说过 并看到过一些代码或书籍显示其用于拆分和连接路径片段的用法。它提供的一些例程名称甚至可能看起来与您在其他语言中使用的名称相似。

但是,除非您正在编写自己的 IO 框架,否则几乎不需要直接使用 $*SPEC。 提供低级别的东西,它的使用不仅会使你的代码难以阅读,你可能会引入安全问题(例如空字符)!

IO::Path 类型是 Raku 世界的主力。它满足所有路径操作需求,并提供快捷例程,让您避免处理文件句柄。用它而不是 这样的东西。

提示:您可以使用 / 连接路径部分并将其提供给 IO::Path 例程; 无论操作系统如何,他们仍然可以做正确的事情。

但是,将它用于 无法提供的东西是很好的。例如,.devnull 方法:

  1. {
  2. temp $*OUT = open :w, $*SPEC.devnull;
  3. say "In space no one can hear you scream!";
  4. }

不要使用 .Str 方法对 IO::Path 对象进行字符串化,除非您只是想将它们显示在某个地方以供参考或使用。.Str 方法返回 实例化的任何基本路径字符串。它不考虑 $.CWD 属性的值。例如,此代码已损坏:

  1. my $path = 'foo'.IO;
  2. chdir 'bar';
  3. # WRONG!! .Str DOES NOT USE $.CWD!
  4. run <tar -cvvf archive.tar>, $path.Str;

调用更改了当前目录的值,但我们创建的 $path 是相对于该更改之前的目录。

但是,IO::Path 对象*确实*知道它相对于哪个目录。我们只需要使用 或 .relative 来字符串化对象。两个例程都返回一个 对象; 它们不同之处在于结果是绝对路径还是相对路径。所以,我们可以像这样修复我们的代码:

  1. my $path = 'foo'.IO;
  2. chdir 'bar';
  3. # RIGHT!! .absolute does consider the value of $.CWD!
  4. run <tar -cvvf archive.tar>, $path.absolute;
  5. # Also good:
  6. run <tar -cvvf archive.tar>, $path.relative;

虽然通常不在视线范围内,但默认情况下,每个 对象都使用 $*CWD 的当前值来设置其 。这意味着有两件事需要注意。

这段代码是错误的:

my $*CWD 让 变为未定义的。然后 .IO coercer 继续并将其正创建的路径的 属性设置为 undefined 的 $*CWD 字符串化版本 ; 一个空字符串。

    更好的是,如果要在本地化的 $*CWD 中执行某些代码,请使用该 例程。