class IO::Handle { }

IO::Handle 的实例封装一个句柄来操作输入/输出资源。通常无需直接创建 IO::Handle 实例,因为其他角色和方法会执行此操作。例如,IO::Path 对象提供一个 open 方法,该方法返回一个 IO::Handle

my $fh = '/tmp/log.txt'.IO.open;
say $fh.^name# OUTPUT: IO::Handle

第一行与以下代码段几乎等效

my $fh = IO::Handle.new:path'/tmp/log.txt'.IO.path ) ).open;

方法§

方法 open§

method open(IO::Handle:D:
      :$r:$w:$x:$a:$update,
      :$rw:$rx:$ra,
      :$mode is copy,
      :$create is copy,
      :$append is copy,
      :$truncate is copy,
      :$exclusive is copy,
      :$bin,
      :$enc is copy,
      :$chomp = $!chomp,
      :$nl-in is copy = $!nl-in,
      Str:D :$nl-out is copy = $!nl-out,
      :$out-buffer is copy,
    )

在其中一种模式下打开句柄。如果打开失败,则 失败并引发适当的异常。

有关 :$chomp:$nl-in:$nl-out:$enc 的接受值和行为,请参阅各个方法的说明。参数的默认值与调用者的属性相同,如果提供了其中任何一个,则属性将更新为新值。指定设置为 True:$bin 而不是 :$enc 以指示应以二进制模式打开句柄。将未定义的值指定为 :$enc 等效于根本不指定 :$enc。同时将定义的编码指定为 :$enc 和将 :$bin 设置为 true 将导致引发 X::IO::BinaryAndEncoding 异常。

打开模式默认为非独占、只读(与指定 :r 相同),并且可以通过以下参数的组合来控制

:r      same as specifying   :mode<ro>  same as specifying nothing

:w      same as specifying   :mode<wo>, :create, :truncate
:a      same as specifying   :mode<wo>, :create, :append
:x      same as specifying   :mode<wo>, :create, :exclusive

:update same as specifying   :mode<rw>
:rw     same as specifying   :mode<rw>, :create
:ra     same as specifying   :mode<rw>, :create, :append
:rx     same as specifying   :mode<rw>, :create, :exclusive

参数 :r:w:a:x 完全相同,与上表中最后三行所示的字母组合相同。对上述未列出的模式组合的支持取决于实现,应假定不受支持。也就是说,指定,例如,.open(:r :create).open(:mode<wo> :append :truncate) 可能有效,也可能导致宇宙内爆,具体取决于特定的实现。这也适用于以这种不受支持的模式打开的句柄的读/写操作。

模式详细信息如下

:mode<ro>  means "read only"
:mode<wo>  means "write only"
:mode<rw>  means "read and write"

:create    means the file will be created, if it does not exist
:truncate  means the file will be emptied, if it exists
:exclusive means .open will fail if the file already exists
:append    means writes will be done at the end of file's current contents

尝试打开目录、写入以只读模式打开的句柄或从以只写模式打开的句柄读取,或对以二进制模式打开的句柄使用文本读取方法将失败或引发异常。

6.c 语言中,可以打开路径 '-',这将导致 open 在只读模式下打开(如果 closed$*IN 句柄或在只写模式下打开 $*OUT 句柄。在这种情况下,所有其他模式都将导致引发异常。

6.d 语言版本开始,不赞成使用路径 '-',并且它将在未来的语言版本中完全删除。

:out-buffer 控制输出缓冲,默认情况下表现得好像它是 Nil。有关详细信息,请参阅方法 out-buffer

注意(2017.09 之前的 Rakudo 版本):文件句柄在超出范围时不会刷新或关闭。虽然它们将在垃圾回收时关闭,但不能保证会运行垃圾回收。这意味着你应该对为写入打开的句柄使用显式 close,以避免数据丢失,并且还建议对为读取打开的句柄使用显式 close,以便你的程序不会同时打开太多文件,从而在进一步的 open 调用中引发异常。

注意(Rakudo 版本 2017.09 及更高版本):打开的文件句柄将在程序退出时自动关闭,但仍然强烈建议你显式地 close 打开的句柄。

方法 comb§

method comb(IO::Handle:D: Bool :$close|args --> Seq:D)

读取句柄并以与 Str.comb 相同的方式处理其内容,采用相同的参数,如果 $close 设置为真值,则在完成后关闭句柄。当调用此方法时,实现可能会完全吸收文件。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = 'path/to/file'.IO.open;
say "The file has {+$fh.comb: '':close} ♥s in it";

方法 chomp§

has $.chomp is rw = True

可以通过 .newopen 设置的属性之一。默认为 True。采用 Bool,指定在使用 .get.lines 方法时,是否应从内容中删除行分隔符(由 .nl-in 定义)。

routine get§

method get(IO::Handle:D: --> Str:D)
multi  get (IO::Handle $fh = $*ARGFILES --> Str:D)

从句柄读取单行输入,如果句柄的 .chomp 属性设置为 True,则删除尾随换行符(由 .nl-in 设置)。如果没有更多输入,则返回 Nil。如果没有提供句柄,则子例程形式默认为 $*ARGFILES

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

$*IN.get.say;              # Read one line from the standard input 
 
my $fh = open 'filename';
$fh.get.say;               # Read one line from a file 
$fh.close;
 
say get;                   # Read one line from $*ARGFILES 

routine getc§

method getc(IO::Handle:D: --> Str:D)
multi  getc (IO::Handle $fh = $*ARGFILES --> Str:D)

从输入流读取单个字符。当句柄 处于二进制模式 时尝试调用此方法,将导致抛出 X::IO::BinaryMode 异常。如果没有提供句柄,则子例程形式默认为 $*ARGFILES。如果没有更多输入,则返回 Nil,否则操作将阻塞,等待至少一个字符可用;适用以下警告

Buffering terminals§

仅当您已将终端设置为“无缓冲”时,使用 getc 从终端获取单个按键才可正常工作。否则,终端将等待敲击回车键或填满缓冲区,然后编译器才能获取单个字节的数据。

Waiting for potential combiners§

如果句柄的编码允许读取组合字符,raku 将等待更多数据可用,然后再提供字符。这意味着输入一个“e”,后跟一个组合尖音符,将为您提供一个带尖音符的 e,而不是提供一个“e”,并让下一个读取函数为您提供一个悬空组合符。但是,这也意味着当用户只输入一个“e”,并且无意也输入一个组合尖音符时,您的程序将在返回初始“e”之前等待另一个按键。

submethod DESTROY§

submethod DESTROY(IO::Handle:D:)

关闭文件句柄,除非其 native-descriptor2 或更低。这可确保不会无意中关闭标准文件句柄。

请注意,无法保证进行垃圾回收,因此您不得依赖 DESTROY 来关闭您写入的句柄,而应自行关闭。打开大量文件的程序也应显式关闭句柄,无论它们是否已打开以进行写入,因为在进行垃圾回收并关闭不再使用的句柄之前,可能会打开太多文件。

方法 gist§

method gist(IO::Handle:D: --> Str:D)

返回一个包含信息的字符串,其中包含 .path(如果有的话)句柄创建的路径,以及它是否 .opened

say IO::Handle.new# IO::Handle<(Any)>(closed) 
say "foo".IO.open;  # IO::Handle<"foo".IO>(opened) 

方法 eof§

method eof(IO::Handle:D: --> Bool:D)

非阻塞。如果读取操作已耗尽句柄的内容,则返回 True。对于 可寻址 句柄,这意味着当前位置在文件末尾或文件末尾之后,并且 寻址 一个已耗尽的句柄返回到文件内容中将导致 eof 再次返回 False

对于 不可寻址 句柄和打开为零大小文件(包括 /proc/ 中的特殊文件)的句柄,EOF 不会设置,直到读取操作无法读取任何字节。例如,在此代码中,第一个 read 消耗所有数据,但只有第二个 read 不读取任何内容,TTY 句柄上的 EOF 才会被设置

$ echo "x" | raku -e 'with $*IN { .read: 10000; .eof.say; .read: 10; .eof.say }'
False
True

方法 encoding§

multi method encoding(IO::Handle:D: --> Str:D)
multi method encoding(IO::Handle:D: $enc --> Str:D)

返回一个 Str,表示句柄当前使用的编码,默认为 "utf8"Nil 表示文件句柄当前处于二进制模式。指定可选的位置 $enc 参数会切换句柄使用的编码;指定 Nil 作为编码以将句柄置于二进制模式。

编码的接受值不区分大小写。可用的编码因实现和后端而异。在 Rakudo MoarVM 上支持以下内容

utf8
utf16
utf16le
utf16be
utf8-c8
iso-8859-1
windows-1251
windows-1252
windows-932
ascii

默认编码是 utf8,它会规范化为 Unicode NFC(规范形式规范)。在某些情况下,您可能希望确保不进行规范化;为此,您可以使用 utf8-c8。在使用 utf8-c8 之前,请阅读 Unicode:文件句柄和 I/O 以获取有关 utf8-c8NFC 的更多信息。

从 Rakudo 2018.04 开始,还支持 windows-932,它是 ShiftJIS 的一个变体。

实现也可以选择提供对别名的支持,例如 Rakudo 允许别名 latin-1 编码为 iso-8859-1 和带破折号的 utf 版本:utf-8utf-16

utf16、utf16le 和 utf16be§

与 utf8 不同,utf16 具有字节序——大端字节序或小端字节序。这与字节的顺序有关。计算机 CPU 也有字节序。Raku 的 utf16 格式说明符在编码时将使用主机系统的字节序。在解码时,它将查找字节顺序标记,如果存在,则使用它来设置字节序。如果没有字节顺序标记,它将假定文件使用与主机系统相同的字节顺序。字节顺序标记是代码点 U+FEFF,即零宽度不间断空格。在 utf16 编码的文件中,标准规定,如果它存在于文件开头,则应将其解释为字节顺序标记,而不是 U+FEFF 代码点。

虽然写入会导致在不同的字节序系统上写入不同的文件,但在 2018.10 发布时,在写入文件时将写入字节顺序标记,并且使用 utf16 编码创建的文件将能够在大小端字节序系统上读取。

使用 utf16be 或 utf16le 编码时使用字节顺序标记。所使用的字节序不受主机 CPU 类型的影响,对于 utf16be 而言为大端序,对于 utf16le 而言为小端序。

为了符合标准,文件开头处的 0xFEFF 字节被解释为零宽度不换行空格,而不是字节顺序标记。对于使用 utf16be 或 utf16le 编码的文件,不会写入字节顺序标记。

从 MoarVM 上的 Rakudo 2018.09 开始,支持 utf16、utf16leutf16be。在 2018.10 中,使用 utf16 写入文件会正确添加字节顺序标记 (BOM)。

示例§

with 'foo'.IO {
    .spurt: "First line is text, then:\nBinary";
    my $fh will leave {.close} = .open;
    $fh.get.say;         # OUTPUT: «First line is text, then:␤» 
    $fh.encoding: Nil;
    $fh.slurp.say;       # OUTPUT: «Buf[uint8]:0x<42 69 6e 61 72 79>␤» 
}

例程行§

sub          lines$what = $*ARGFILES|c)
multi method linesIO::Handle:D: $limit:$close )
multi method linesIO::Handle:D:         :$close )

子形式默认采用 $*ARGFILES,它会将 lines 方法应用于作为第一个参数的对象,并将其余参数传递给它。

该方法将返回一个 Seq,其每个元素都是句柄中的一行(即由 .nl-in 划分的块)。如果句柄的 .chomp 属性设置为 True,则 .nl-in 指定的字符将从每一行中剥离。

读取最多 $limit 行,其中 $limit 可以是 IntInfWhatever(解释为 Inf)的非负数。如果 :$close 设置为 True,则会在文件结束或达到 $limit 时关闭句柄。如果未提供句柄,则子例程形式默认为 $*ARGFILES

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

注意:行是惰性读取的,因此请确保返回的 Seq完全具体化,或者在关闭句柄或尝试使用任何其他更改文件位置的方法时不再需要它。

say "The file contains ",
  '50GB-file'.IO.open.lines.grep(*.contains: 'Raku').elems,
  " lines that mention Raku";
# OUTPUT: «The file contains 72 lines that mention Raku␤» 

您可以在 /proc/* 文件(来自 6.d 版本)中使用 lines

say lines"/proc/$*PID/statm".IO ); # OUTPUT: «(58455 31863 8304 2 0 29705 0)␤» 

方法 lock§

method lock(IO::Handle:D:
            Bool:D :$non-blocking = FalseBool:D :$shared = False
            --> True)

如果文件句柄已打开,则对文件放置一个建议锁定。如果 :$non-blockingTrue,则如果无法获取锁定,将使用 X::IO::Lock fail;否则,将阻塞直到可以放置锁定。如果 :$sharedTrue,则将放置共享(读取)锁定;否则,将放置独占(写入)锁定。成功时,返回 True;如果无法放置锁定(例如,尝试对以写入模式打开的文件句柄放置共享锁定,或尝试对以读取模式打开的文件句柄放置独占锁定),则使用 X::IO::Lock fail

您可以再次使用 .lock 用另一个锁定替换现有锁定。要删除锁定,请 close 文件句柄或使用 unlock

# One program writes, the other reads, and thanks to locks either 
# will wait for the other to finish before proceeding to read/write 
 
# Writer 
given "foo".IO.open(:w{
    .lock;
    .spurt: "I ♥ Raku!";
    .close# closing the handle unlocks it; we could also use `unlock` for that 
}
 
# Reader 
given "foo".IO.open {
    .lock: :shared;
    .slurp.say# OUTPUT: «I ♥ Raku!␤» 
    .close;
}

方法 unlock§

method unlock(IO::Handle:D: --> True)

从文件句柄中移除一个 lock。如果无法移除,它将返回 True 或引发异常。

例程 words§

multi        words(IO::Handle:D $fh = $*ARGFILES$limit = Inf:$close --> Seq:D)
multi method words(IO::Handle:D: $limit = Inf:$close --> Seq:D)

类似于 Str.words,将句柄的流按连续的空白块(如 Unicode 所定义)分隔,并返回一个由所得“单词”组成的 Seq。接受一个可选的 $limit 参数,它可以是非负 IntInfWhatever(解释为 Inf),以指示仅返回最多 $limit 个单词。如果 Bool :$close 命名参数设置为 True,则在返回的 Seq 耗尽时自动关闭句柄。如果没有提供句柄,则子例程形式默认为 $*ARGFILES

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my %dict := bag $*IN.words;
say "Most common words: "%dict.sort(-*.value).head: 5;

注意:在调用 .words 时,实现可能会读取比必要更多的 data。也就是说,$handle.words(2) 可能读取多于两个“单词”的数据,并且对读取方法的后续调用可能无法从获取的两个单词之后的正确位置读取。在调用 .words 之后,文件位置应视为未定义。

方法 split§

method split(IO::Handle:D: :$close|c)

Slurp 句柄的内容,并对其调用 Str.split,转发任何给定的参数。如果 :$close 命名参数设置为 True,则在 slurp 之后将 关闭 调用者。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = 'path/to/file'.IO.open;
$fh.split: '':close# Returns file content split on ♥ 

方法 spurt§

multi method spurt(IO::Handle:D: Blob $data:$close = False)
multi method spurt(IO::Handle:D: Cool $data:$close = False)

将所有 $data 写入文件句柄,并在完成后关闭它(如果 $closeTrue)。对于 Cool $data,将使用句柄设置为使用的编码(IO::Handle.openIO::Handle.encoding)。

当句柄处于二进制模式时,喷射 Cool 或当句柄不在二进制模式时喷射 Blob 的行为是未定义的。

方法 print§

multi method print(**@text --> True)
multi method print(Junction:D --> True)

将给定的 @text 写入句柄,通过调用 .Str 方法将任何非 Str 对象强制转换为 StrJunction 参数 自动线程化,并且不保证打印字符串的顺序。请参阅 write 以写入字节。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = 'path/to/file'.IO.open: :w;
$fh.print: 'some text';
$fh.close;

方法 print-nl§

method print-nl(IO::Handle:D: --> True)

$.nl-out 属性的值写入句柄。默认情况下,此属性为 ,但请参阅 换行符页面 以了解它在不同平台和环境中遵循的规则。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = 'path/to/file'.IO.open: :w:nl-out("\r\n");
$fh.print: "some text";
$fh.print-nl# prints \r\n 
$fh.close;

方法 printf§

multi method printf(IO::Handle:D: Cool $format*@args)

根据给定的格式和参数格式化一个字符串,并将结果.print到文件句柄中。有关可接受的格式指令的详细信息,请参见sprintf

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = open 'path/to/file':w;
$fh.printf: "The value is %d\n"32;
$fh.close;

方法 out-buffer§

method out-buffer(--> Int:Dis rw

控制输出缓冲,可以通过open的参数进行设置。将一个int作为要使用的缓冲区大小(0是可以接受的)。可以采用一个BoolTrue表示使用默认的、实现定义的缓冲区大小;False表示禁用缓冲(相当于使用0作为缓冲区大小)。

最后,可以采用一个Nil来启用基于 TTY 的缓冲控制:如果句柄是一个 TTY,则禁用缓冲,否则,使用默认的、实现定义的缓冲区大小。

请参见flush以写入当前缓冲区中的数据。更改缓冲区大小会刷新文件句柄。

given 'foo'.IO.open: :w:1000out-buffer {
    .say: 'Hello world!'# buffered 
    .out-buffer = 42;       # buffer resized; previous print flushed 
    .say: 'And goodbye';
    .close# closing the handle flushes the buffer 
}

方法 put§

multi method put(**@text --> True)
multi method put(Junction:D --> True)

将给定的@text写入句柄,通过调用.Str方法将任何非Str对象强制转换为Str,并在末尾追加.nl-out的值。 Junction参数自动线程化,并且打印的字符串的顺序不能得到保证。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = 'path/to/file'.IO.open: :w;
$fh.put: 'some text';
$fh.close;

方法 say§

multi method say(IO::Handle:D: **@text --> True)

此方法与put相同,只是它通过调用.gist而不是.Str来对其参数进行字符串化。

尝试在句柄 处于二进制模式 时调用此方法将导致引发 X::IO::BinaryMode 异常。

my $fh = open 'path/to/file':w;
$fh.say(Complex.new(34));        # OUTPUT: «3+4i␤» 
$fh.close;

方法 read§

method read(IO::Handle:D: Int(Cool:D$bytes = 65536 --> Buf:D)

二进制读取;从文件句柄中读取并返回最多$bytes字节。$bytes默认为一个特定于实现的值(在 Rakudo 中,$*DEFAULT-READ-ELEMS的值,默认设置为65536)。即使句柄不是二进制模式,也可以调用此方法。

(my $file = 'foo'.IO).spurt: 'I ♥ Raku';
given $file.open {
    say .read: 6# OUTPUT: «Buf[uint8]:0x<49 20 e2 99 a5 20>␤» 
    .close;
}

方法 readchars§

method readchars(IO::Handle:D: Int(Cool:D$chars = 65536 --> Str:D)

读取字符;从文件句柄中读取并返回最多$chars个字符(字形)。$chars默认为一个特定于实现的值(在 Rakudo 中,$*DEFAULT-READ-ELEMS的值,默认设置为65536)。当句柄处于二进制模式时尝试调用此方法将导致抛出X::IO::BinaryMode异常。

(my $file = 'foo'.IO).spurt: 'I ♥ Raku';
given $file.open {
    say .readchars: 5# OUTPUT: «I ♥ R␤» 
    .close;
}

方法 write§

method write(IO::Handle:D: Blob:D $buf --> True)

$buf写入文件句柄。即使句柄不是二进制模式,也可以调用此方法。

方法 seek§

method seek(IO::Handle:D: Int:D $offsetSeekType:D $whence --> True)

将文件指针(即任何后续读取或写入操作将开始的位置)移动到由$offset指定的字节位置,相对于由$whence指定的位置,$whence可以是以下之一

  • SeekFromBeginning:文件的开头。

  • SeekFromCurrent:文件中的当前位置。

  • SeekFromEnd:文件的末尾。请注意,如果你想定位到文件末尾之前的位置,你需要指定一个负偏移量。

方法 tell§

method tell(IO::Handle:D: --> Int:D)

以字节为单位返回文件指针的当前位置。

方法 slurp-rest§

multi method slurp-rest(IO::Handle:D: :$bin! --> Buf)
multi method slurp-rest(IO::Handle:D: :$enc --> Str)

弃用通知:此方法在 6.d 版本中已弃用。不要在新建代码中使用它,而应使用 .slurp 方法

从当前文件位置(可能已由之前的读取或 seek 设置)返回文件的剩余内容。如果提供了副词 :bin,将返回一个 Buf;否则,返回将是一个具有可选编码 :encStr

方法 slurp§

method slurp(IO::Handle:D: :$close:$bin)

返回从当前文件指针到末尾的所有内容。如果调用者处于二进制模式或如果 $bin 设置为 True,将返回一个 Buf,否则将使用调用者的当前 .encoding 对内容进行解码并返回一个 Str

如果 :$close 设置为 True,将在完成读取时关闭句柄。

注意:Rakudo 上,此方法在 2017.04 版本中引入;$bin 参数在 2017.10 中添加。

方法 Supply§

multi method Supply(IO::Handle:D: :$size = 65536)

返回一个 Supply,它将以块的形式发出句柄的内容。如果句柄处于二进制模式,则这些块将是 Buf;如果不是,则使用与 IO::Handle.encoding 相同的编码解码的 Str

块的大小由可选的 :size 命名参数和二进制模式下的 65536 字节或非二进制模式下的 65536 个字符决定。

"foo".IO.open(:bin).Supply(:size<10>).tap: *.raku.say;
# OUTPUT: 
# Buf[uint8].new(73,32,226,153,165,32,80,101,114,108) 
# Buf[uint8].new(32,54,33,10) 
 
"foo".IO.open.Supply(:size<10>).tap: *.raku.say;
# OUTPUT: 
# "I ♥ Perl" 
# " 6!\n" 

方法 path§

method path(IO::Handle:D:)

对于在文件上打开的句柄,这将返回表示该文件的 IO::Path。对于标准 I/O 句柄 $*IN$*OUT$*ERR,它将返回一个 IO::Special 对象。

方法 IO§

method IO(IO::Handle:D:)

.path 的别名

方法 Str§

返回 .path 的值,强制转换为 Str

say "foo".IO.open.Str# OUTPUT: «foo␤» 

例程 close§

method close(IO::Handle:D: --> Bool:D)
multi  close(IO::Handle $fh)

关闭一个打开的文件句柄,成功时返回 True。如果文件句柄已经关闭,则不会引发错误,但如果你关闭了其中一个标准文件句柄(默认情况下:$*IN$*OUT$*ERR:任何具有 native-descriptor 2 或更低的文件句柄),你将无法重新打开它们。

given "foo/bar".IO.open(:w{
    .spurt: "I ♥ Raku!";
    .close;
}

使用 LEAVE phaser 来关闭句柄是一种常见的惯用法,它确保无论如何离开块,句柄都会关闭。

do {
    my $fh = open "path-to-file";
    LEAVE close $fh;
    # ... do stuff with the file 
}
 
sub do-stuff-with-the-file (IO $path-to-file{
  my $fh = $path-to-file.open;
 
  # stick a `try` on it, since this will get run even when the sub is 
  # called with wrong arguments, in which case the `$fh` will be an `Any` 
  LEAVE try close $fh;
 
  # ... do stuff with the file 
}

注意:与其他一些语言不同,Raku 不使用引用计数,因此 文件句柄在超出作用域时不会关闭。虽然它们会在垃圾回收时关闭,但不能保证垃圾回收会运行。这意味着你必须对用于写入的文件句柄使用显式的 close,以避免数据丢失,并且对用于读取的文件句柄也建议使用显式的 close,以便你的程序不会同时打开太多文件,从而在进一步的 open 调用中触发异常。

请注意,一些方法允许提供 :close 参数,以便在方法调用的操作完成后关闭句柄。作为一个更简单的替代方案,IO::Path 类型提供了许多读写方法,让你可以在不直接处理文件句柄的情况下使用文件。

方法 flush§

method flush(IO::Handle:D: --> True)

将刷新句柄,写入任何缓冲数据。成功时返回 True;否则,使用 X::IO::Flush 失败

given "foo".IO.open: :w {
    LEAVE .close;
    .print: 'something';
    'foo'.IO.slurp.say# (if the data got buffered) OUTPUT: «␤» 
    .flush;             # flush the handle 
    'foo'.IO.slurp.say# OUTPUT: «something␤» 
}

方法 native-descriptor§

method native-descriptor(IO::Handle:D:)

这返回一个操作系统将理解为“文件描述符”的值,并且适合传递给需要文件描述符作为参数的本机函数,例如 fcntlioctl

方法 nl-in§

method nl-in(--> Str:Dis rw

可以通过 .newopen 设置的属性之一。默认为 ["\x0A", "\r\n"]。获取一个 StrArrayStr,指定此句柄的输入行尾。如果 .chomp 属性设置为 True,则将在执行 chomp 的例程(例如 getlines)中去掉这些结尾。

with 'test'.IO {
    .spurt: '1foo2bar3foo'# write some data into our test file 
    my $fh will leave {.close} = .open# can also set .nl-in via .open arg 
    $fh.nl-in = [<foo bar>]; # set two possible line endings to use; 
    $fh.lines.say# OUTPUT: ("1", "2", "3").Seq 
}

方法 nl-out§

has Str:D $.nl-out is rw = "\n";

可以通过 .newopen 设置的属性之一。默认为 "\n"。获取一个 Str,指定此句柄的输出行尾,供方法 .put.say 使用。

with 'test'.IO {
    given .open: :w {
        .put: 42;
        .nl-out = 'foo';
        .put: 42;
        .close;
    }
    .slurp.raku.say# OUTPUT: «"42\n42foo"␤» 
}

方法 opened§

method opened(IO::Handle:D: --> Bool:D)

如果句柄已打开,则返回 True,否则返回 False

方法 t §

method t(IO::Handle:D: --> Bool:D)

如果句柄打开到 TTY,则返回 True,否则返回 False

创建自定义句柄§

从 6.d 语言(在 Rakudo 编译器版本 2018.08 中提供早期实现)开始,提供了一些帮助方法来简化自定义 IO::Handle 对象的创建。在子类中,只需实现这些方法即可影响所有相关功能。如果句柄要使用文本读/写方法,并且不使用标准 .open 方法,请务必在自定义句柄中调用 .encoding 方法以正确设置解码器/编码器

class IO::URL is IO::Handle {
    has $.URL is required;
    has Buf $!content;
    submethod TWEAK {
        use WWW# ecosystem module that will let us `get` a web page 
        use DOM::Tiny# ecosystem module that will parse out all text from HTML 
        $!content := Buf.new: DOM::Tiny.parse(get $!URL).all-text(:trim).encode;
        self.encoding: 'utf8'# set up encoder/decoder 
    }
 
    method open(|)  { self }       # block out some IO::Handle methods 
    method close(|) { self }       # that work with normal low-level file 
    method opened   { ! self.EOF } # handles, since we don't. This isn't 
    method lock(| --> True{ }    # necessary, but will make our handle 
    method unlock--> True{ }   # be more well-behaved if someone 
    # actually calls one of these methods. There are more of these you 
    # can handle, such as .tell, .seek, .flush, .native-descriptor, etc. 
 
    method WRITE(|) {
        # For this handle we'll just die on write. If yours can handle writes. 
        # The data to write will be given as a Blob positional argument. 
        die "Cannot write into IO::URL";
    }
    method READ(\bytes{
        # We splice off the requested number of bytes from the head of 
        # our content Buf. The handle's decoder will handle decoding them 
        # automatically, if textual read methods were called on the handle. 
        $!content.splice: 0bytes
    }
    method EOF {
        # For "end of file", we'll simply report whether we still have 
        # any bytes of the website we fetched on creation. 
        not $!content
    }
}
 
my $fh := IO::URL.new: :URL<raku.org>;
 
# .slurp and print all the content from the website. We can use all other 
# read methods, such as .lines, or .get, or .readchars. All of them work 
# correctly, even though we only defined .READ and .EOF 
$fh.slurp.say;

方法 WRITE§

method WRITE(IO::Handle:D: Blob:D \data --> Bool:D)

每当对句柄执行写操作时都会调用。即使调用了文本写方法,也始终将数据作为 Blob 接收。

class IO::Store is IO::Handle {
    has @.lines = [];
 
    submethod TWEAK {
        self.encoding: 'utf8'# set up encoder/decoder 
    }
 
    method WRITE(IO::Handle:D: Blob:D \data --> Bool:D{
        @!lines.push: data.decode();
        True;
    }
 
    method gist() {
        return @!lines.join("\n" );
    }
}
my $store = IO::Store.new();
my $output = $PROCESS::OUT;
$PROCESS::OUT = $store;
.say for <one two three>;
$PROCESS::OUT = $output;
say $store.lines(); # OUTPUT: «[one␤ two␤ three␤]» 

在此示例中,我们创建一个简单的 WRITE 重定向,它将写入文件句柄的任何内容存储到一个数组中。我们需要先保存标准输出,我们在 $output 中执行此操作,然后通过定义的 IO::Store/type/IO::Store 类存储所有 printsay 的内容。此类中应考虑两件事。默认情况下,IO::Handle 处于二进制模式,因此如果要使用文本,我们需要 TWEAK 对象。其次,如果成功,WRITE 操作应返回 True。如果失败,它将失败。

方法 READ§

method READ(IO::Handle:D: Int:D \bytes --> Buf:D)

每当对句柄执行读取操作时都会调用。接收请求读取的字节数。返回一个 Buf,其中包含可用于填充解码器缓冲区或直接从读取方法返回的字节。结果允许少于请求的字节数,包括没有字节。

如果你提供自己的 .READ,则很可能还需要提供自己的 .EOF,以便所有功能都能正确运行。

编译器可以在读取操作期间多次调用 .EOF 方法,以确定是否应调用 .READ。可以从 .READ 请求满足读取操作所需的更多字节,在这种情况下,IO::Handle 或其使用的解码器可能会缓冲额外数据,以满足任何后续读取操作,而无需进行另一个 .READ 调用。

class IO::Store is IO::Handle {
    has @.lines = [];
 
    submethod TWEAK {
      self.encoding: 'utf8'# set up encoder/decoder 
    }
 
    method WRITE(IO::Handle:D: Blob:D \data --> Bool:D{
      @!lines.push: data;
      True;
    }
 
    method whole() {
      my Buf $everything = Buf.new();
      for @!lines -> $b {
        $everything ~= $b;
      }
      return $everything;
    }
 
    method READ(IO::Handle:D: Int:D \bytes --> Buf:D{
      my Buf $everything := self.whole();
      return $everything;
    }
 
    method EOF {
      my $everything = self.whole();
      !$everything;
    }
}
 
my $store := IO::Store.new();
 
$store.print$_ ) for <one two three>;
say $store.read(3).decode# OUTPUT: «one␤» 
say $store.read(3).decode# OUTPUT: «two␤» 

在这种情况下,我们已经编程了两个 READEOF 方法,以及 WRITE,它将每行存储在一个数组中的一个元素中。read 方法实际上调用 READ,返回 3 个字节,对应于前两个元素中的三个字符。请注意,IO::Handle 基类负责光标,因为 READ 只是提供对对象整个内容的句柄;基类一次将 READ 1024 * 1024 个字节。如果你的对象计划保存比这更大的字节数,则必须自己处理内部光标。这就是为什么在这个示例中我们实际上不使用 bytes 参数的原因。

方法 EOF§

method EOF(IO::Handle:D: --> Bool:D)

指示是否已到达句柄的数据源的“文件结尾”;即无法通过调用 .READ 方法获取更多数据。请注意,这不同于 eof 方法,后者仅在 .EOF 返回 True 以及句柄使用的所有解码器缓冲区(如果有)也为空时才返回 True。有关示例实现,请参见 .READ

相关角色和类§

另请参见相关角色 IO 和相关类 IO::Path