类 IO

IO 类的一个实例(通常称为)表示底层操作系统中的输入/输出流。IO 类是 Ruby 中输入和输出的基础。

File 是 Ruby 核心类中唯一一个继承自 IO 的类。Ruby 标准库中的一些类也继承自 IO;这些类包括 TCPSocket 和 UDPSocket。

全局常量 ARGF(也可以访问为 $<)提供了一个类似 IO 的流,允许访问 ARGV 中找到的所有文件路径(如果 ARGV 为空,则在 STDIN 中找到)。ARGF 本身不是 IO 的子类。

类 StringIO 提供了一个类似 IO 的流,用于处理 String。StringIO 本身不是 IO 的子类。

基于 IO 的重要对象包括

  • $stdin。

  • $stdout。

  • $stderr。

  • File 的实例。

可以使用以下方法创建 IO 实例

  • IO.new:为给定的整数文件描述符返回一个新的 IO 对象。

  • IO.open:将一个新的 IO 对象传递给给定的块。

  • IO.popen:返回一个新的 IO 对象,该对象连接到新启动的子进程的 $stdin 和 $stdout。

  • Kernel#open:返回一个新的 IO 对象,该对象连接到给定的源:流、文件或子进程。

File 流类似,IO 流具有

  • 读/写模式,可以是只读、只写或读/写;请参见 读/写模式

  • 数据模式,可以是纯文本或二进制;请参见 数据模式

  • 内部和外部编码;请参见 编码

与其他 IO 流一样,它具有

  • 一个位置,它决定了在流中下一个读或写操作发生的位置;参见 Position

  • 一个行号,它是一个特殊的、面向行的“位置”(不同于上面提到的位置);参见 Line Number

扩展 io/console

扩展 io/console 提供了大量与控制台交互的方法;需要它会为 IO 类添加大量方法。

示例文件

这里许多示例使用这些变量

# English text with newlines.
text = <<~EOT
  First line
  Second line

  Fourth line
  Fifth line
EOT

# Russian text.
russian = "\u{442 435 441 442}" # => "тест"

# Binary data.
data = "\u9990\u9991\u9992\u9993\u9994"

# Text file.
File.write('t.txt', text)

# File with Russian text.
File.write('t.rus', russian)

# File with binary data.
f = File.new('t.dat', 'wb:UTF-16')
f.write(data)
f.close

打开选项

许多 IO 方法接受可选的关键字参数,这些参数决定了如何打开一个新的流

  • :mode: 流模式。

  • :flags: Integer 文件打开标志;如果也给出了 mode,则两者进行按位或运算。

  • :external_encoding: 流的外部编码。

  • :internal_encoding: 流的内部编码。'-' 是默认内部编码的同义词。如果值为 nil,则不进行转换。

  • :encoding: 指定外部和内部编码为 'extern:intern'

  • :textmode: 如果为真值,则指定模式为纯文本,否则为二进制。

  • :binmode: 如果为真值,则指定模式为二进制,否则为纯文本。

  • :autoclose: 如果为真值,则指定当流关闭时 fd 将关闭;否则它保持打开状态。

  • :path: 如果提供字符串值,它将在 inspect 中使用,并且可作为 path 方法使用。

还提供 String#encode 中提供的选项,这些选项可以控制外部和内部编码之间的转换。

基本 IO

可以使用这些方法执行基本的流 IO,这些方法通常对多字节字符串进行操作

  • IO#read: 读取并返回流中剩余的一些或所有字节。

  • IO#write: 将零个或多个字符串写入流;每个给定的对象,如果还不是字符串,则通过 to_s 转换。

位置

一个 IO 流有一个非负整数位置,它是下一个读或写操作将要发生的字节偏移量。一个新的流的位置为零(行号也为零);方法 rewind 将位置(和行号)重置为零。

相关方法

  • IO#tell (别名为 #pos): 返回流中当前位置(以字节为单位)。

  • IO#pos=: 将流的位置设置为给定的整数 new_position(以字节为单位)。

  • IO#seek: 将流的位置设置为给定的整数 offset(以字节为单位),相对于给定的位置 whence(指示开头、结尾或当前位置)。

  • IO#rewind: 将流定位到开头(并重置行号)。

打开和关闭流

新的 IO 流可以打开以供读取、打开以供写入,或两者兼而有之。

当被垃圾收集器回收时,流会自动关闭。

尝试在关闭的流上进行读写操作会导致异常。

相关方法

流结束

您可以查询流是否位于其末尾

  • IO#eof? (也称为 #eof): 返回流是否已到达流结束。

您可以使用方法 IO#seek 将位置重新定位到流结束。

f = File.new('t.txt')
f.eof? # => false
f.seek(0, :END)
f.eof? # => true
f.close

或者通过读取所有流内容(这比使用 IO#seek 速度慢)

f.rewind
f.eof? # => false
f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
f.eof? # => true

行 IO

您可以使用以下方法逐行读取 IO 流

每个读取器方法都接受

  • 可选的行分隔符 sep;请参阅 行分隔符

  • 可选的行大小限制 limit;请参阅 行限制

对于每个读取器方法,读取可能从行中间开始,具体取决于流的位置;请参阅 位置

f = File.new('t.txt')
f.pos = 27
f.each_line {|line| p line }
f.close

输出

"rth line\n"
"Fifth line\n"

您可以使用以下方法逐行写入 IO 流

行分隔符

每个方法都使用行分隔符,它是用于分隔行的字符串

默认的行分隔符由全局变量$/给出,其默认值为"\n"。要读取的下一行是当前位置到下一个行分隔符的所有数据

f = File.new('t.txt')
f.gets # => "First line\n"
f.gets # => "Second line\n"
f.gets # => "\n"
f.gets # => "Fourth line\n"
f.gets # => "Fifth line\n"
f.close

您可以指定不同的行分隔符

f = File.new('t.txt')
f.gets('l')   # => "First l"
f.gets('li')  # => "ine\nSecond li"
f.gets('lin') # => "ne\n\nFourth lin"
f.gets        # => "e\n"
f.close

有两个特殊的行分隔符

  • nil:整个流被读入一个字符串

    f = File.new('t.txt')
    f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
    f.close
    
  • ''(空字符串):读取下一段(段落由两个连续的行分隔符分隔)

    f = File.new('t.txt')
    f.gets('') # => "First line\nSecond line\n\n"
    f.gets('') # => "Fourth line\nFifth line\n"
    f.close
    

行限制

这些方法中的每一个都使用一个行限制,它指定返回的字节数可能不会(比给定的limit)长很多;

多字节字符不会被拆分,因此一行可能略长于给定的限制。

如果没有给出limit,则行仅由sep确定。

# Text with 1-byte characters.
File.open('t.txt') {|f| f.gets(1) }  # => "F"
File.open('t.txt') {|f| f.gets(2) }  # => "Fi"
File.open('t.txt') {|f| f.gets(3) }  # => "Fir"
File.open('t.txt') {|f| f.gets(4) }  # => "Firs"
# No more than one line.
File.open('t.txt') {|f| f.gets(10) } # => "First line"
File.open('t.txt') {|f| f.gets(11) } # => "First line\n"
File.open('t.txt') {|f| f.gets(12) } # => "First line\n"

# Text with 2-byte characters, which will not be split.
File.open('t.rus') {|f| f.gets(1).size } # => 1
File.open('t.rus') {|f| f.gets(2).size } # => 1
File.open('t.rus') {|f| f.gets(3).size } # => 2
File.open('t.rus') {|f| f.gets(4).size } # => 2

行分隔符和行限制

给出参数seplimit,结合两种行为

  • 返回由行分隔符sep确定的下一行。

  • 但返回的字节数不超过限制允许的字节数。

示例

File.open('t.txt') {|f| f.gets('li', 20) } # => "First li"
File.open('t.txt') {|f| f.gets('li', 2) }  # => "Fi"

行号

可读的 IO 流有一个非负整数行号

相关方法

除非通过调用方法IO#lineno=修改,否则行号是根据给定的行分隔符sep,由某些面向行的读取方法读取的行数

新的流最初具有行号零(和位置零);方法rewind将行号(和位置)重置为零

f = File.new('t.txt')
f.lineno # => 0
f.gets   # => "First line\n"
f.lineno # => 1
f.rewind
f.lineno # => 0
f.close

从流中读取行通常会改变其行号

f = File.new('t.txt', 'r')
f.lineno   # => 0
f.readline # => "This is line one.\n"
f.lineno   # => 1
f.readline # => "This is the second line.\n"
f.lineno   # => 2
f.readline # => "Here's the third line.\n"
f.lineno   # => 3
f.eof?     # => true
f.close

在流中迭代行通常会改变其行号

File.open('t.txt') do |f|
  f.each_line do |line|
    p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}"
  end
end

输出

"position=11 eof?=false lineno=1"
"position=23 eof?=false lineno=2"
"position=24 eof?=false lineno=3"
"position=36 eof?=false lineno=4"
"position=47 eof?=true lineno=5"

与流的 位置 不同,行号不会影响下一次读取或写入的位置

f = File.new('t.txt')
f.lineno = 1000
f.lineno # => 1000
f.gets   # => "First line\n"
f.lineno # => 1001
f.close

与行号相关联的是全局变量 $.

  • 当打开流时,$. 未设置;其值来自进程中之前的活动

    $. = 41
    f = File.new('t.txt')
    $. = 41
    # => 41
    f.close
    
  • 当读取流时,$. 将设置为该流的行号

    f0 = File.new('t.txt')
    f1 = File.new('t.dat')
    f0.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
    $.           # => 5
    f1.readlines # => ["\xFE\xFF\x99\x90\x99\x91\x99\x92\x99\x93\x99\x94"]
    $.           # => 1
    f0.close
    f1.close
    
  • 方法 IO#rewindIO#seek 不会影响 $.

    f = File.new('t.txt')
    f.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
    $.          # => 5
    f.rewind
    f.seek(0, :SET)
    $.          # => 5
    f.close
    

字符 IO

您可以使用以下方法逐个字符处理 IO 流

  • IO#getc: 从流中读取并返回下一个字符。

  • IO#readchar: 与 getc 相似,但在流结束时会引发异常。

  • IO#ungetc: 将字符或整数推回(“取消移位”)到流中。

  • IO#putc: 将字符写入流。

  • IO#each_char: 读取流中剩余的每个字符,并将字符传递给给定的块。

字节 IO

您可以使用以下方法逐字节处理 IO 流

  • IO#getbyte: 返回下一个 8 位字节,作为 0..255 范围内的整数。

  • IO#readbyte: 与 getbyte 相似,但在流结束时会引发异常。

  • IO#ungetbyte: 将字节推回(“取消移位”)到流中。

  • IO#each_byte: 读取流中剩余的每个字节,并将字节传递给给定的块。

码点 IO

您可以逐个码点处理 IO 流

这里的内容

首先,看看其他地方。IO 类

这里,IO 类提供了对以下操作有用的方法:

创建

  • ::new (别名为 ::for_fd): 为给定的整数文件描述符创建并返回一个新的 IO 对象。

  • ::open: 创建一个新的 IO 对象。

  • ::pipe: 创建一对连接的读写 IO 对象。

  • ::popen: 创建一个 IO 对象来与子进程交互。

  • ::select: 选择哪些给定的 IO 实例已准备好进行读取、写入或有待处理的异常。

读取

  • ::binread: 返回一个二进制字符串,其中包含给定文件中的所有字节或部分字节。

  • ::read: 返回一个字符串,其中包含给定文件中的所有字节或部分字节。

  • ::readlines: 返回一个字符串数组,这些字符串是给定文件中的行。

  • getbyte: 返回从 self 读取的下一个 8 位字节,作为一个整数。

  • getc: 返回从 self 读取的下一个字符,作为一个字符串。

  • gets: 返回从 self 读取的行。

  • pread: 返回从 self 读取的所有字节或接下来的 n 个字节,不更新接收器的偏移量。

  • read: 返回从 self 读取的所有剩余字节或接下来的 n 个字节,对于给定的 n

  • read_nonblock: 从 self 读取接下来的 n 个字节,对于给定的 n,在非阻塞模式下。

  • readbyte: 返回从 self 读取的下一个字节;与 getbyte 相同,但在文件末尾时会引发异常。

  • readchar: 从 self 读取下一个字符并返回;与 getc 相同,但在文件末尾时会抛出异常。

  • readline: 从 self 读取下一行并返回;与 getline 相同,但在文件末尾时会抛出异常。

  • readlines: 返回从 self 读取的所有行的数组。

  • readpartial: 从 self 返回最多指定数量的字节。

写入

  • ::binwrite: 将给定的字符串以二进制模式写入给定文件路径的文件。

  • ::write: 将给定的字符串写入 self

  • <<: 将给定的字符串追加到 self

  • print: 将最后读取的行或给定的对象打印到 self

  • printf: 根据给定的格式字符串和对象写入 self

  • putc: 将一个字符写入 self

  • puts: 将行写入 self,确保行以换行符结尾。

  • pwrite: 将给定的字符串写入给定的偏移量,不更新接收者的偏移量。

  • write: 将一个或多个给定的字符串写入 self

  • write_nonblock: 以非阻塞模式将一个或多个给定的字符串写入 self

定位

  • lineno: 返回 self 中的当前行号。

  • lineno=: 设置 self 中的行号。

  • pos (别名为 tell): 返回 self 中的当前字节偏移量。

  • pos=: 设置 self 中的字节偏移量。

  • reopen: 将 self 与新的或现有的 IO 流重新关联。

  • rewind: 将 self 定位到输入的开头。

  • seek: 设置 self 相对于给定位置的偏移量。

迭代

  • ::foreach: 将给定文件的每一行都生成到块中。

  • each (别名为 each_line): 使用 self 中的每一行连续调用给定的块。

  • each_byte: 使用 self 中的每个连续字节作为整数调用给定的块。

  • each_char: 以字符串形式将self中的每个连续字符传递给给定的块。

  • each_codepoint: 以整数形式将self中的每个连续码点传递给给定的块。

设置

查询

  • autoclose?: 返回self是否自动关闭。

  • binmode?: 返回self是否处于二进制模式。

  • close_on_exec?: 返回self的关闭执行标志。

  • closed?: 返回self是否已关闭。

  • eof? (别名为 eof): 返回self是否处于流末尾。

  • external_encoding: 返回self的外部编码对象。

  • fileno (别名为 to_i): 返回self的整数文件描述符。

  • internal_encoding: 返回self的内部编码对象。

  • pid: 返回与self关联的子进程的进程 ID,如果self是由 ::popen 创建的。

  • stat: 返回包含self状态信息的 File::Stat 对象。

  • sync: 返回self是否处于同步模式。

  • tty? (别名为 isatty): 返回self是否为终端。

缓冲

  • fdatasync: 立即将self中所有缓冲数据写入磁盘。

  • flush: 将self中的任何缓冲数据刷新到底层操作系统。

  • fsync: 立即将self中所有缓冲数据和属性写入磁盘。

  • ungetbyte: 使用给定的整数字节或字符串将缓冲区预置到self

  • ungetc: 使用给定的字符串将缓冲区预置到self

低级访问

  • ::sysopen: 打开由路径给定的文件,返回整数文件描述符。

  • advise: 宣布以特定方式访问self中的数据的意图。

  • fcntl: 将低级命令传递给由给定文件描述符指定的文件。

  • ioctl: 将低级命令传递给由给定文件描述符指定的设备。

  • sysread: 使用低级读取从 self 读取最多接下来的 n 个字节。

  • sysseek: 设置self的偏移量。

  • syswrite: 使用低级写入将给定的字符串写入self

其他

  • ::copy_stream: 将数据从源复制到目标,每个都是文件路径或类似 IO 的对象。

  • ::try_convert: 返回一个新的 IO 对象,该对象是通过转换给定对象得到的。

  • inspect: 返回self的字符串表示形式。

常量

EWOULDBLOCKWaitReadable

EAGAINWaitReadable

EWOULDBLOCKWaitWritable

EAGAINWaitWritable

PRIORITY

用于 IO#wait 的优先级事件掩码。

READABLE

用于 IO#wait 的可读事件掩码。

SEEK_CUR

从当前位置设置 I/O 位置

SEEK_DATA

将 I/O 位置设置为包含数据的下一个位置

SEEK_END

从末尾设置 I/O 位置

SEEK_HOLE

将 I/O 位置设置为下一个空洞

SEEK_SET

从开头设置 I/O 位置

WRITABLE

用于 IO#wait 的可写事件掩码。

公共类方法

binread(path, length = nil, offset = 0) → string or nil 点击切换源代码

行为类似于 IO.read,但流以二进制模式打开,使用 ASCII-8BIT 编码。

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

static VALUE
rb_io_s_binread(int argc, VALUE *argv, VALUE io)
{
    VALUE offset;
    struct foreach_arg arg;
    enum {
        fmode = FMODE_READABLE|FMODE_BINMODE,
        oflags = O_RDONLY
#ifdef O_BINARY
                |O_BINARY
#endif
    };
    struct rb_io_encoding convconfig = {NULL, NULL, 0, Qnil};

    rb_scan_args(argc, argv, "12", NULL, NULL, &offset);
    FilePathValue(argv[0]);
    convconfig.enc = rb_ascii8bit_encoding();
    arg.io = rb_io_open_generic(io, argv[0], oflags, fmode, &convconfig, 0);
    if (NIL_P(arg.io)) return Qnil;
    arg.argv = argv+1;
    arg.argc = (argc > 1) ? 1 : 0;
    if (!NIL_P(offset)) {
        struct seek_arg sarg;
        int state = 0;
        sarg.io = arg.io;
        sarg.offset = offset;
        sarg.mode = SEEK_SET;
        rb_protect(seek_before_access, (VALUE)&sarg, &state);
        if (state) {
            rb_io_close(arg.io);
            rb_jump_tag(state);
        }
    }
    return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io);
}
binwrite(path, string, offset = 0) → integer 点击切换源代码

行为类似于 IO.write,但流以二进制模式打开,使用 ASCII-8BIT 编码。

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

static VALUE
rb_io_s_binwrite(int argc, VALUE *argv, VALUE io)
{
    return io_s_write(argc, argv, io, 1);
}
copy_stream(src, dst, src_length = nil, src_offset = 0) → integer 点击切换源代码

从给定的 src 复制到给定的 dst,返回复制的字节数。

  • 给定的 src 必须是以下之一

    • 可读文件的路径,从中读取源数据。

    • 类似 IO 的对象,打开以供读取,并且能够响应方法 :readpartial 或方法 :read

  • 给定的 dst 必须是以下之一

    • 可写文件的路径,将数据写入其中。

    • 类似 IO 的对象,打开以供写入,并且能够响应方法 :write

此处的示例使用文件 t.txt 作为源

File.read('t.txt')
# => "First line\nSecond line\n\nThird line\nFourth line\n"
File.read('t.txt').size # => 47

如果只给出参数 srcdst,则会复制整个源流

# Paths.
IO.copy_stream('t.txt', 't.tmp')  # => 47

# IOs (recall that a File is also an IO).
src_io = File.open('t.txt', 'r') # => #<File:t.txt>
dst_io = File.open('t.tmp', 'w') # => #<File:t.tmp>
IO.copy_stream(src_io, dst_io)   # => 47
src_io.close
dst_io.close

如果参数 src_length 是一个非负整数,则最多复制这么多字节

IO.copy_stream('t.txt', 't.tmp', 10) # => 10
File.read('t.tmp')                   # => "First line"

如果还给出了参数 src_offset,则从该偏移量开始读取源流

IO.copy_stream('t.txt', 't.tmp', 11, 11) # => 11
IO.read('t.tmp')                         # => "Second line"
static VALUE
rb_io_s_copy_stream(int argc, VALUE *argv, VALUE io)
{
    VALUE src, dst, length, src_offset;
    struct copy_stream_struct st;

    MEMZERO(&st, struct copy_stream_struct, 1);

    rb_scan_args(argc, argv, "22", &src, &dst, &length, &src_offset);

    st.src = src;
    st.dst = dst;

    st.src_fptr = NULL;
    st.dst_fptr = NULL;

    if (NIL_P(length))
        st.copy_length = (rb_off_t)-1;
    else
        st.copy_length = NUM2OFFT(length);

    if (NIL_P(src_offset))
        st.src_offset = (rb_off_t)-1;
    else
        st.src_offset = NUM2OFFT(src_offset);

    rb_ensure(copy_stream_body, (VALUE)&st, copy_stream_finalize, (VALUE)&st);

    return OFFT2NUM(st.total);
}
for_fd(fd, mode = 'r', **opts) → io 点击切换源代码

IO.new 的同义词。

static VALUE
rb_io_s_for_fd(int argc, VALUE *argv, VALUE klass)
{
    VALUE io = rb_obj_alloc(klass);
    rb_io_initialize(argc, argv, io);
    return io;
}
foreach(path, sep = $/, **opts) {|line| block } → nil 点击切换源代码
foreach(path, limit, **opts) {|line| block } → nil
foreach(path, sep, limit, **opts) {|line| block } → nil
foreach(...) → an_enumerator

使用从流中读取的每一行调用块。

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

第一个参数必须是字符串,它是文件的路径。

仅给出参数path时,会解析给定path文件中的行,解析方式由默认的行分隔符决定,并使用每个后续行调用块。

File.foreach('t.txt') {|line| p line }

输出:与上面相同。

对于命令和路径这两种形式,其余参数相同。

给出参数sep时,会解析由该行分隔符决定的行(参见 行分隔符)。

File.foreach('t.txt', 'li') {|line| p line }

输出

"First li"
"ne\nSecond li"
"ne\n\nThird li"
"ne\nFourth li"
"ne\n"

每段

File.foreach('t.txt', '') {|paragraph| p paragraph }

输出

"First line\nSecond line\n\n"
"Third line\nFourth line\n"

给出参数limit时,会解析由默认行分隔符和给定行长度限制决定的行(参见 行限制)。

File.foreach('t.txt', 7) {|line| p line }

输出

"First l"
"ine\n"
"Second "
"line\n"
"\n"
"Third l"
"ine\n"
"Fourth l"
"line\n"

给出参数seplimit时,会解析由给定行分隔符和给定行长度限制决定的行(参见 行分隔符和行限制)。

可选关键字参数opts指定

如果没有给出块,则返回一个 Enumerator

static VALUE
rb_io_s_foreach(int argc, VALUE *argv, VALUE self)
{
    VALUE opt;
    int orig_argc = argc;
    struct foreach_arg arg;
    struct getline_arg garg;

    argc = rb_scan_args(argc, argv, "12:", NULL, NULL, NULL, &opt);
    RETURN_ENUMERATOR(self, orig_argc, argv);
    extract_getline_args(argc-1, argv+1, &garg);
    open_key_args(self, argc, argv, opt, &arg);
    if (NIL_P(arg.io)) return Qnil;
    extract_getline_opts(opt, &garg);
    check_getline_args(&garg.rs, &garg.limit, garg.io = arg.io);
    return rb_ensure(io_s_foreach, (VALUE)&garg, rb_io_close, arg.io);
}
new(fd, mode = 'r', **opts) → io click to toggle source

从文件描述符创建并返回一个新的 IO 对象(文件流)。

IO.new 可能对与低级库的交互有用。对于更高级别的交互,使用 File.open 创建文件流可能更简单。

参数fd 必须是一个有效的文件描述符(整数)。

path = 't.tmp'
fd = IO.sysopen(path) # => 3
IO.new(fd)            # => #<IO:fd 3>

新的 IO 对象不继承编码(因为整数文件描述符没有编码)。

fd = IO.sysopen('t.rus', 'rb')
io = IO.new(fd)
io.external_encoding # => #<Encoding:UTF-8> # Not ASCII-8BIT.

可选参数mode(默认为 'r')必须指定一个有效的模式;参见 访问模式

IO.new(fd, 'w')         # => #<IO:fd 3>
IO.new(fd, File::WRONLY) # => #<IO:fd 3>

可选关键字参数opts指定

示例

IO.new(fd, internal_encoding: nil) # => #<IO:fd 3>
IO.new(fd, autoclose: true)        # => #<IO:fd 3>
static VALUE
rb_io_initialize(int argc, VALUE *argv, VALUE io)
{
    VALUE fnum, vmode;
    rb_io_t *fp;
    int fd, fmode, oflags = O_RDONLY;
    struct rb_io_encoding convconfig;
    VALUE opt;
#if defined(HAVE_FCNTL) && defined(F_GETFL)
    int ofmode;
#else
    struct stat st;
#endif


    argc = rb_scan_args(argc, argv, "11:", &fnum, &vmode, &opt);
    rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &fmode, &convconfig);

    fd = NUM2INT(fnum);
    if (rb_reserved_fd_p(fd)) {
        rb_raise(rb_eArgError, "The given fd is not accessible because RubyVM reserves it");
    }
#if defined(HAVE_FCNTL) && defined(F_GETFL)
    oflags = fcntl(fd, F_GETFL);
    if (oflags == -1) rb_sys_fail(0);
#else
    if (fstat(fd, &st) < 0) rb_sys_fail(0);
#endif
    rb_update_max_fd(fd);
#if defined(HAVE_FCNTL) && defined(F_GETFL)
    ofmode = rb_io_oflags_fmode(oflags);
    if (NIL_P(vmode)) {
        fmode = ofmode;
    }
    else if ((~ofmode & fmode) & FMODE_READWRITE) {
        VALUE error = INT2FIX(EINVAL);
        rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError));
    }
#endif
    VALUE path = Qnil;

    if (!NIL_P(opt)) {
        if (rb_hash_aref(opt, sym_autoclose) == Qfalse) {
            fmode |= FMODE_EXTERNAL;
        }

        path = rb_hash_aref(opt, RB_ID2SYM(idPath));
        if (!NIL_P(path)) {
            StringValue(path);
            path = rb_str_new_frozen(path);
        }
    }

    MakeOpenFile(io, fp);
    fp->self = io;
    fp->fd = fd;
    fp->mode = fmode;
    fp->encs = convconfig;
    fp->pathv = path;
    fp->timeout = Qnil;
    clear_codeconv(fp);
    io_check_tty(fp);
    if (fileno(stdin) == fd)
        fp->stdio_file = stdin;
    else if (fileno(stdout) == fd)
        fp->stdio_file = stdout;
    else if (fileno(stderr) == fd)
        fp->stdio_file = stderr;

    if (fmode & FMODE_SETENC_BY_BOM) io_set_encoding_by_bom(io);
    return io;
}
open(fd, mode = 'r', **opts) → io click to toggle source
open(fd, mode = 'r', **opts) {|io| ... } → object

使用给定参数通过 IO.new 创建一个新的 IO 对象。

如果没有给出块,则返回 IO 对象。

如果给出块,则使用 IO 对象调用块并返回块的值。

static VALUE
rb_io_s_open(int argc, VALUE *argv, VALUE klass)
{
    VALUE io = rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS);

    if (rb_block_given_p()) {
        return rb_ensure(rb_yield, io, io_close, io);
    }

    return io;
}
pipe(**opts) → [read_io, write_io] click to toggle source
pipe(enc, **opts) → [read_io, write_io]
pipe(ext_enc, int_enc, **opts) → [read_io, write_io]
pipe(**opts) {|read_io, write_io| ...} → object
pipe(enc, **opts) {|read_io, write_io| ...} → object
pipe(ext_enc, int_enc, **opts) {|read_io, write_io| ...} → object

创建一对相互连接的管道端点,read_iowrite_io

如果给出参数enc_string,它必须是一个包含以下内容的字符串:

  • 要作为外部编码使用的编码名称。

  • 要作为外部和内部编码使用的两个编码的冒号分隔名称。

如果给出参数int_enc,它必须是一个 Encoding 对象或编码名称字符串,用于指定要使用的内部编码;如果也给出参数ext_enc,它必须是一个 Encoding 对象或编码名称字符串,用于指定要使用的外部编码。

read_io 读取的字符串带有外部编码标记;如果也指定了内部编码,则字符串将转换为该编码并带有该编码标记。

如果指定了任何编码,可选的哈希参数将指定转换选项。

可选关键字参数opts指定

如果没有给出块,则返回数组中的两个端点。

IO.pipe # => [#<IO:fd 4>, #<IO:fd 5>]

如果给出了块,则使用两个端点调用块;关闭两个端点并返回块的值。

IO.pipe {|read_io, write_io| p read_io; p write_io }

输出

#<IO:fd 6>
#<IO:fd 7>

并非所有平台都可用。

在下面的示例中,两个进程关闭了它们不使用的管道端。这不仅仅是美观上的细节。如果还有任何写入器与管道保持打开状态,则管道的读取端不会生成文件结束条件。在父进程的情况下,如果它没有首先发出 wr.close,则 rd.read 永远不会返回。

rd, wr = IO.pipe

if fork
  wr.close
  puts "Parent got: <#{rd.read}>"
  rd.close
  Process.wait
else
  rd.close
  puts 'Sending message to parent'
  wr.write "Hi Dad"
  wr.close
end

产生

Sending message to parent
Parent got: <Hi Dad>
static VALUE
rb_io_s_pipe(int argc, VALUE *argv, VALUE klass)
{
    int pipes[2], state;
    VALUE r, w, args[3], v1, v2;
    VALUE opt;
    rb_io_t *fptr, *fptr2;
    struct io_encoding_set_args ies_args;
    int fmode = 0;
    VALUE ret;

    argc = rb_scan_args(argc, argv, "02:", &v1, &v2, &opt);
    if (rb_pipe(pipes) < 0)
        rb_sys_fail(0);

    args[0] = klass;
    args[1] = INT2NUM(pipes[0]);
    args[2] = INT2FIX(O_RDONLY);
    r = rb_protect(io_new_instance, (VALUE)args, &state);
    if (state) {
        close(pipes[0]);
        close(pipes[1]);
        rb_jump_tag(state);
    }
    GetOpenFile(r, fptr);

    ies_args.fptr = fptr;
    ies_args.v1 = v1;
    ies_args.v2 = v2;
    ies_args.opt = opt;
    rb_protect(io_encoding_set_v, (VALUE)&ies_args, &state);
    if (state) {
        close(pipes[1]);
        io_close(r);
        rb_jump_tag(state);
    }

    args[1] = INT2NUM(pipes[1]);
    args[2] = INT2FIX(O_WRONLY);
    w = rb_protect(io_new_instance, (VALUE)args, &state);
    if (state) {
        close(pipes[1]);
        if (!NIL_P(r)) rb_io_close(r);
        rb_jump_tag(state);
    }
    GetOpenFile(w, fptr2);
    rb_io_synchronized(fptr2);

    extract_binmode(opt, &fmode);

    if ((fmode & FMODE_BINMODE) && NIL_P(v1)) {
        rb_io_ascii8bit_binmode(r);
        rb_io_ascii8bit_binmode(w);
    }

#if DEFAULT_TEXTMODE
    if ((fptr->mode & FMODE_TEXTMODE) && (fmode & FMODE_BINMODE)) {
        fptr->mode &= ~FMODE_TEXTMODE;
        setmode(fptr->fd, O_BINARY);
    }
#if RUBY_CRLF_ENVIRONMENT
    if (fptr->encs.ecflags & ECONV_DEFAULT_NEWLINE_DECORATOR) {
        fptr->encs.ecflags |= ECONV_UNIVERSAL_NEWLINE_DECORATOR;
    }
#endif
#endif
    fptr->mode |= fmode;
#if DEFAULT_TEXTMODE
    if ((fptr2->mode & FMODE_TEXTMODE) && (fmode & FMODE_BINMODE)) {
        fptr2->mode &= ~FMODE_TEXTMODE;
        setmode(fptr2->fd, O_BINARY);
    }
#endif
    fptr2->mode |= fmode;

    ret = rb_assoc_new(r, w);
    if (rb_block_given_p()) {
        VALUE rw[2];
        rw[0] = r;
        rw[1] = w;
        return rb_ensure(rb_yield, ret, pipe_pair_close, (VALUE)rw);
    }
    return ret;
}
popen(env = {}, cmd, mode = 'r', **opts) → io 点击切换源代码
popen(env = {}, cmd, mode = 'r', **opts) {|io| ... } → object

将给定的命令 cmd 作为子进程执行,其 $stdin 和 $stdout 连接到新的流 io

如果使用不可信输入调用此方法,则可能存在潜在的安全漏洞;请参阅 命令注入

如果没有给出块,则返回新的流,该流根据给定的 mode 可能对读、写或两者都打开。应显式关闭流(最终)以避免资源泄漏。

如果给出了块,则将流传递给块(同样,对读、写或两者都打开);当块退出时,流将关闭,并且块的值将分配给全局变量 $? 并返回。

可选参数 mode 可以是任何有效的 IO 模式。请参阅 访问模式

必需参数 cmd 决定以下哪种情况发生

  • 进程分叉。

  • 指定程序在 shell 中运行。

  • 指定程序使用指定参数运行。

  • 指定程序使用指定参数和指定 argv0 运行。

下面将详细介绍每种情况。

可选的哈希参数 env 指定要添加到子进程环境变量的名称/值对。

IO.popen({'FOO' => 'bar'}, 'ruby', 'r+') do |pipe|
  pipe.puts 'puts ENV["FOO"]'
  pipe.close_write
  pipe.gets
end => "bar\n"

可选关键字参数opts指定

分叉进程

当参数 cmd 是 1 个字符的字符串 '-' 时,会导致进程分叉。

IO.popen('-') do |pipe|
  if pipe
    $stderr.puts "In parent, child pid is #{pipe.pid}\n"
  else
    $stderr.puts "In child, pid is #{$$}\n"
  end
end

输出

In parent, child pid is 26253
In child, pid is 26253

请注意,这并非所有平台都支持。

Shell 子进程

当参数 cmd 为单个字符串(但不是 '-')时,名为 cmd 的程序将作为 shell 命令运行。

IO.popen('uname') do |pipe|
  pipe.readlines
end

输出

["Linux\n"]

另一个示例

IO.popen('/bin/sh', 'r+') do |pipe|
  pipe.puts('ls')
  pipe.close_write
  $stderr.puts pipe.readlines.size
end

输出

213

程序子进程

当参数 cmd 为字符串数组时,名为 cmd[0] 的程序将使用 cmd 中的所有元素作为其参数运行。

IO.popen(['du', '..', '.']) do |pipe|
  $stderr.puts pipe.readlines.size
end

输出

1111

带有 argv0 的程序子进程

当参数 cmd 为一个数组,其第一个元素是一个包含两个元素的字符串数组,而其余元素(如果有)为字符串时

  • cmd[0][0](嵌套数组中的第一个字符串)是运行的程序的名称。

  • cmd[0][1](嵌套数组中的第二个字符串)被设置为程序的 argv[0]

  • cmd[1..-1](外部数组中的字符串)是程序的参数。

示例(将 $0 设置为 'foo')

IO.popen([['/bin/sh', 'foo'], '-c', 'echo $0']).read # => "foo\n"

一些特殊示例

# Set IO encoding.
IO.popen("nkf -e filename", :external_encoding=>"EUC-JP") {|nkf_io|
  euc_jp_string = nkf_io.read
}

# Merge standard output and standard error using Kernel#spawn option. See Kernel#spawn.
IO.popen(["ls", "/", :err=>[:child, :out]]) do |io|
  ls_result_with_error = io.read
end

# Use mixture of spawn options and IO options.
IO.popen(["ls", "/"], :err=>[:child, :out]) do |io|
  ls_result_with_error = io.read
end

 f = IO.popen("uname")
 p f.readlines
 f.close
 puts "Parent is #{Process.pid}"
 IO.popen("date") {|f| puts f.gets }
 IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f.inspect}"}
 p $?
 IO.popen(%w"sed -e s|^|<foo>| -e s&$&;zot;&", "r+") {|f|
   f.puts "bar"; f.close_write; puts f.gets
 }

输出(来自上一节)

["Linux\n"]
Parent is 21346
Thu Jan 15 22:41:19 JST 2009
21346 is here, f is #<IO:fd 3>
21352 is here, f is nil
#<Process::Status: pid 21352 exit 0>
<foo>bar;zot;

引发 IO.pipeKernel.spawn 引发的异常。

static VALUE
rb_io_s_popen(int argc, VALUE *argv, VALUE klass)
{
    VALUE pname, pmode = Qnil, opt = Qnil, env = Qnil;

    if (argc > 1 && !NIL_P(opt = rb_check_hash_type(argv[argc-1]))) --argc;
    if (argc > 1 && !NIL_P(env = rb_check_hash_type(argv[0]))) --argc, ++argv;
    switch (argc) {
      case 2:
        pmode = argv[1];
      case 1:
        pname = argv[0];
        break;
      default:
        {
            int ex = !NIL_P(opt);
            rb_error_arity(argc + ex, 1 + ex, 2 + ex);
        }
    }
    return popen_finish(rb_io_popen(pname, pmode, env, opt), klass);
}
read(path, length = nil, offset = 0, **opts) → string or nil 点击切换源代码

打开流,读取并返回部分或全部内容,然后关闭流;如果未读取任何字节,则返回 nil

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

第一个参数必须是字符串,它是文件的路径。

仅给出参数 path 时,以文本模式读取并返回给定路径下文件的全部内容。

IO.read('t.txt')
# => "First line\nSecond line\n\nThird line\nFourth line\n"

在 Windows 上,文本模式在遇到某些特殊字节时可能会终止读取,并留下文件中未读取的字节。如果要读取文件中的所有字节,请考虑使用 IO.binread

给出参数 length 时,如果可用,则返回 length 个字节。

IO.read('t.txt', 7) # => "First l"
IO.read('t.txt', 700)
# => "First line\r\nSecond line\r\n\r\nFourth line\r\nFifth line\r\n"

给出参数 lengthoffset 时,如果可用,则从给定 offset 开始返回 length 个字节。

IO.read('t.txt', 10, 2)   # => "rst line\nS"
IO.read('t.txt', 10, 200) # => nil

可选关键字参数opts指定

static VALUE
rb_io_s_read(int argc, VALUE *argv, VALUE io)
{
    VALUE opt, offset;
    long off;
    struct foreach_arg arg;

    argc = rb_scan_args(argc, argv, "13:", NULL, NULL, &offset, NULL, &opt);
    if (!NIL_P(offset) && (off = NUM2LONG(offset)) < 0) {
        rb_raise(rb_eArgError, "negative offset %ld given", off);
    }
    open_key_args(io, argc, argv, opt, &arg);
    if (NIL_P(arg.io)) return Qnil;
    if (!NIL_P(offset)) {
        struct seek_arg sarg;
        int state = 0;
        sarg.io = arg.io;
        sarg.offset = offset;
        sarg.mode = SEEK_SET;
        rb_protect(seek_before_access, (VALUE)&sarg, &state);
        if (state) {
            rb_io_close(arg.io);
            rb_jump_tag(state);
        }
        if (arg.argc == 2) arg.argc = 1;
    }
    return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io);
}
readlines(path, sep = $/, **opts) → array 点击切换源代码
readlines(path, limit, **opts) → array
readlines(path, sep, limit, **opts) → 数组

返回从流中读取的所有行的数组。

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

第一个参数必须是字符串,它是文件的路径。

仅给出参数 path 时,将根据默认行分隔符解析给定 path 文件中的行,并将这些行以数组形式返回。

IO.readlines('t.txt')
# => ["First line\n", "Second line\n", "\n", "Third line\n", "Fourth line\n"]

给出参数sep时,会解析由该行分隔符决定的行(参见 行分隔符)。

# Ordinary separator.
IO.readlines('t.txt', 'li')
# =>["First li", "ne\nSecond li", "ne\n\nThird li", "ne\nFourth li", "ne\n"]
# Get-paragraphs separator.
IO.readlines('t.txt', '')
# => ["First line\nSecond line\n\n", "Third line\nFourth line\n"]
# Get-all separator.
IO.readlines('t.txt', nil)
# => ["First line\nSecond line\n\nThird line\nFourth line\n"]

给出参数limit时,会解析由默认行分隔符和给定行长度限制决定的行(参见 行限制)。

IO.readlines('t.txt', 7)
# => ["First l", "ine\n", "Second ", "line\n", "\n", "Third l", "ine\n", "Fourth ", "line\n"]

给出参数seplimit时,会解析由给定行分隔符和给定行长度限制决定的行(参见 行分隔符和行限制)。

可选关键字参数opts指定

static VALUE
rb_io_s_readlines(int argc, VALUE *argv, VALUE io)
{
    VALUE opt;
    struct foreach_arg arg;
    struct getline_arg garg;

    argc = rb_scan_args(argc, argv, "12:", NULL, NULL, NULL, &opt);
    extract_getline_args(argc-1, argv+1, &garg);
    open_key_args(io, argc, argv, opt, &arg);
    if (NIL_P(arg.io)) return Qnil;
    extract_getline_opts(opt, &garg);
    check_getline_args(&garg.rs, &garg.limit, garg.io = arg.io);
    return rb_ensure(io_s_readlines, (VALUE)&garg, rb_io_close, arg.io);
}
select(read_ios, write_ios = [], error_ios = [], timeout = nil) → 数组或 nil 点击切换源代码

调用系统调用 select(2),它监控多个文件描述符,等待一个或多个文件描述符准备好进行某种类型的 I/O 操作。

并非所有平台都已实现。

每个参数 read_ioswrite_ioserror_ios 都是 IO 对象的数组。

参数 timeout 是以秒为单位的整数超时间隔。

该方法监控所有三个数组中给出的 IO 对象,等待某些对象准备好;返回一个 3 元素数组,其元素为

  • read_ios 中准备读取的对象数组。

  • write_ios 中准备写入的对象数组。

  • error_ios 中有待处理异常的对象数组。

如果在给定的 timeout 内没有对象准备好,则返回 nil

IO.select 窥视 IO 对象的缓冲区以测试可读性。如果 IO 缓冲区不为空,IO.select 会立即通知可读性。此“窥视”仅针对 IO 对象。它不会发生在类似 IO 的对象(如 OpenSSL::SSL::SSLSocket)上。

使用 IO.select 的最佳方法是在调用非阻塞方法(如 read_nonblockwrite_nonblock 等)之后调用它。这些方法会引发一个异常,该异常由 IO::WaitReadableIO::WaitWritable 扩展。这些模块通知调用者应如何使用 IO.select 等待。如果引发了 IO::WaitReadable,则调用者应等待读取。如果引发了 IO::WaitWritable,则调用者应等待写入。

因此,可以使用 read_nonblock 和 IO.select 模拟阻塞读取 (readpartial),如下所示

begin
  result = io_like.read_nonblock(maxlen)
rescue IO::WaitReadable
  IO.select([io_like])
  retry
rescue IO::WaitWritable
  IO.select(nil, [io_like])
  retry
end

特别是,对于像 OpenSSL::SSL::SSLSocket 这样的 IO 类对象,非阻塞方法和 IO.select 的组合是首选。它具有 to_io 方法来返回底层的 IO 对象。 IO.select 调用 to_io 来获取要等待的文件描述符。

这意味着 IO.select 通知的可读性并不意味着从 OpenSSL::SSL::SSLSocket 对象中可读。

最可能的情况是 OpenSSL::SSL::SSLSocket 缓冲了一些数据。IO.select 无法看到缓冲区。因此,当 OpenSSL::SSL::SSLSocket#readpartial 不阻塞时,IO.select 可能会阻塞。

但是,还存在一些更复杂的情况。

SSL 是一种协议,它是一系列记录。记录由多个字节组成。因此,SSL 的远程端发送部分记录,IO.select 通知可读性,但 OpenSSL::SSL::SSLSocket 无法解密一个字节,并且 OpenSSL::SSL::SSLSocket#readpartial 将阻塞。

此外,远程端可以请求 SSL 重新协商,这将强制本地 SSL 引擎写入一些数据。这意味着 OpenSSL::SSL::SSLSocket#readpartial 可能会调用 write 系统调用,并且它可能会阻塞。在这种情况下,OpenSSL::SSL::SSLSocket#read_nonblock 会引发 IO::WaitWritable 而不是阻塞。因此,调用者应该像上面的示例一样等待可写性就绪。

非阻塞方法和 IO.select 的组合对于 tty、管道套接字和套接字等流也很有用,当多个进程从一个流中读取时。

最后,Linux 内核开发人员不保证 select(2) 的可读性意味着即使对于单个进程,后续 read(2) 的可读性;请参阅 select(2)

IO#readpartial 之前调用 IO.select 通常效果很好。但是,这不是使用 IO.select 的最佳方式。

select(2) 通知的可写性不显示有多少字节可写。 IO#write 方法会阻塞,直到写入给定的整个字符串。因此,IO#write(两个或多个字节) 可能会在 IO.select 通知可写性后阻塞。需要 IO#write_nonblock 来避免阻塞。

可以使用 write_nonblockIO.select 模拟阻塞写入 (write),如下所示:对于 OpenSSL::SSL::SSLSocket 中的 SSL 重新协商,也应该拯救 IO::WaitReadable

while 0 < string.bytesize
  begin
    written = io_like.write_nonblock(string)
  rescue IO::WaitReadable
    IO.select([io_like])
    retry
  rescue IO::WaitWritable
    IO.select(nil, [io_like])
    retry
  end
  string = string.byteslice(written..-1)
end

示例

rp, wp = IO.pipe
mesg = "ping "
100.times {
  # IO.select follows IO#read.  Not the best way to use IO.select.
  rs, ws, = IO.select([rp], [wp])
  if r = rs[0]
    ret = r.read(5)
    print ret
    case ret
    when /ping/
      mesg = "pong\n"
    when /pong/
      mesg = "ping "
    end
  end
  if w = ws[0]
    w.write(mesg)
  end
}

输出

ping pong
ping pong
ping pong
(snipped)
ping
static VALUE
rb_f_select(int argc, VALUE *argv, VALUE obj)
{
    VALUE scheduler = rb_fiber_scheduler_current();
    if (scheduler != Qnil) {
        // It's optionally supported.
        VALUE result = rb_fiber_scheduler_io_selectv(scheduler, argc, argv);
        if (!UNDEF_P(result)) return result;
    }

    VALUE timeout;
    struct select_args args;
    struct timeval timerec;
    int i;

    rb_scan_args(argc, argv, "13", &args.read, &args.write, &args.except, &timeout);
    if (NIL_P(timeout)) {
        args.timeout = 0;
    }
    else {
        timerec = rb_time_interval(timeout);
        args.timeout = &timerec;
    }

    for (i = 0; i < numberof(args.fdsets); ++i)
        rb_fd_init(&args.fdsets[i]);

    return rb_ensure(select_call, (VALUE)&args, select_end, (VALUE)&args);
}
sysopen(path, mode = 'r', perm = 0666) → integer click to toggle source

以给定的模式和权限打开给定路径的文件;返回整数文件描述符。

如果文件可读,则文件必须存在;如果文件可写且不存在,则会以给定的权限创建文件。

File.write('t.tmp', '')  # => 0
IO.sysopen('t.tmp')      # => 8
IO.sysopen('t.tmp', 'w') # => 9
static VALUE
rb_io_s_sysopen(int argc, VALUE *argv, VALUE _)
{
    VALUE fname, vmode, vperm;
    VALUE intmode;
    int oflags, fd;
    mode_t perm;

    rb_scan_args(argc, argv, "12", &fname, &vmode, &vperm);
    FilePathValue(fname);

    if (NIL_P(vmode))
        oflags = O_RDONLY;
    else if (!NIL_P(intmode = rb_check_to_integer(vmode, "to_int")))
        oflags = NUM2INT(intmode);
    else {
        SafeStringValue(vmode);
        oflags = rb_io_modestr_oflags(StringValueCStr(vmode));
    }
    if (NIL_P(vperm)) perm = 0666;
    else              perm = NUM2MODET(vperm);

    RB_GC_GUARD(fname) = rb_str_new4(fname);
    fd = rb_sysopen(fname, oflags, perm);
    return INT2NUM(fd);
}
try_convert(object) → new_io or nil click to toggle source

尝试通过方法 to_ioobject 转换为 IO 对象;如果成功,则返回新的 IO 对象,否则返回 nil

IO.try_convert(STDOUT)   # => #<IO:<STDOUT>>
IO.try_convert(ARGF)     # => #<IO:<STDIN>>
IO.try_convert('STDOUT') # => nil
static VALUE
rb_io_s_try_convert(VALUE dummy, VALUE io)
{
    return rb_io_check_io(io);
}
write(path, data, offset = 0, **opts) → integer click to toggle source

打开流,将给定的 data 写入流,并关闭流;返回写入的字节数。

当从 IO 类(而不是 IO 的子类)调用时,如果使用不受信任的输入调用此方法,则此方法可能存在潜在的安全漏洞;请参阅 命令注入

第一个参数必须是字符串,它是文件的路径。

仅给出参数 path 时,将给定的 data 写入该路径的文件。

IO.write('t.tmp', 'abc')    # => 3
File.read('t.tmp')          # => "abc"

如果 offset 为零(默认值),则文件将被覆盖。

IO.write('t.tmp', 'A')      # => 1
File.read('t.tmp')          # => "A"

如果 offset 在文件内容内,则文件将被部分覆盖。

IO.write('t.tmp', 'abcdef') # => 3
File.read('t.tmp')          # => "abcdef"
# Offset within content.
IO.write('t.tmp', '012', 2) # => 3
File.read('t.tmp')          # => "ab012f"

如果 offset 超出文件内容,则文件将用空字符 "\u0000" 填充。

IO.write('t.tmp', 'xyz', 10) # => 3
File.read('t.tmp')           # => "ab012f\u0000\u0000\u0000\u0000xyz"

可选关键字参数opts指定

static VALUE
rb_io_s_write(int argc, VALUE *argv, VALUE io)
{
    return io_s_write(argc, argv, io, 0);
}

公共实例方法

self << object → self click to toggle source

将给定的 object 写入 selfself 必须以写入模式打开(参见 访问模式);返回 self;如果 object 不是字符串,则通过方法 to_s 转换。

$stdout << 'Hello' << ', ' << 'World!' << "\n"
$stdout << 'foo' << :bar << 2 << "\n"

输出

Hello, World!
foobar2
VALUE
rb_io_addstr(VALUE io, VALUE str)
{
    rb_io_write(io, str);
    return io;
}
advise(advice, offset = 0, len = 0) → nil click to toggle source

调用 Posix 系统调用 posix_fadvise(2),它宣布打算以特定方式访问当前文件中的数据。

参数和结果与平台相关。

相关数据由以下内容指定:

  • offset:数据的第一个字节的偏移量。

  • len:要访问的字节数;如果 len 为零,或大于剩余字节数,则将访问所有剩余字节。

参数 advice 是以下符号之一:

  • :normal: 应用程序没有关于其对指定数据的访问模式的建议。 如果没有为打开的文件提供建议,则这是默认假设。

  • :sequential: 应用程序期望按顺序访问指定数据(较低的偏移量在较高的偏移量之前读取)。

  • :random: 指定数据将以随机顺序访问。

  • :noreuse: 指定数据将只访问一次。

  • :willneed: 指定数据将在不久的将来访问。

  • :dontneed: 指定数据在不久的将来不会被访问。

并非所有平台都已实现。

static VALUE
rb_io_advise(int argc, VALUE *argv, VALUE io)
{
    VALUE advice, offset, len;
    rb_off_t off, l;
    rb_io_t *fptr;

    rb_scan_args(argc, argv, "12", &advice, &offset, &len);
    advice_arg_check(advice);

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);

    off = NIL_P(offset) ? 0 : NUM2OFFT(offset);
    l   = NIL_P(len)    ? 0 : NUM2OFFT(len);

#ifdef HAVE_POSIX_FADVISE
    return do_io_advise(fptr, advice, off, l);
#else
    ((void)off, (void)l);       /* Ignore all hint */
    return Qnil;
#endif
}
autoclose = bool → true 或 false 点击切换源代码

设置自动关闭标志。

f = File.open(File::NULL)
IO.for_fd(f.fileno).close
f.gets # raises Errno::EBADF

f = File.open(File::NULL)
g = IO.for_fd(f.fileno)
g.autoclose = false
g.close
f.gets # won't cause Errno::EBADF
static VALUE
rb_io_set_autoclose(VALUE io, VALUE autoclose)
{
    rb_io_t *fptr;
    GetOpenFile(io, fptr);
    if (!RTEST(autoclose))
        fptr->mode |= FMODE_EXTERNAL;
    else
        fptr->mode &= ~FMODE_EXTERNAL;
    return autoclose;
}
autoclose? → true 或 false 点击切换源代码

如果ios的底层文件描述符将在其最终化或调用close时关闭,则返回true,否则返回false

static VALUE
rb_io_autoclose_p(VALUE io)
{
    rb_io_t *fptr = RFILE(io)->fptr;
    rb_io_check_closed(fptr);
    return RBOOL(!(fptr->mode & FMODE_EXTERNAL));
}
binmode → self 点击切换源代码

将流的数据模式设置为二进制(参见数据模式)。

流的数据模式不能从二进制更改为文本。

static VALUE
rb_io_binmode_m(VALUE io)
{
    VALUE write_io;

    rb_io_ascii8bit_binmode(io);

    write_io = GetWriteIO(io);
    if (write_io != io)
        rb_io_ascii8bit_binmode(write_io);
    return io;
}
binmode? → true 或 false 点击切换源代码

如果流处于二进制模式,则返回true,否则返回false。 参见数据模式

static VALUE
rb_io_binmode_p(VALUE io)
{
    rb_io_t *fptr;
    GetOpenFile(io, fptr);
    return RBOOL(fptr->mode & FMODE_BINMODE);
}
close → nil 点击切换源代码

如果流同时打开用于读写,则关闭用于读写;返回nil。 参见打开和关闭流

如果流是打开用于写入的,则在关闭之前将任何缓冲的写入刷新到操作系统。

如果流是由IO.popen打开的,则设置全局变量$?(子进程退出状态)。

示例

IO.popen('ruby', 'r+') do |pipe|
  puts pipe.closed?
  pipe.close
  puts $?
  puts pipe.closed?
end

输出

false
pid 13760 exit 0
true

相关:IO#close_readIO#close_writeIO#closed?

static VALUE
rb_io_close_m(VALUE io)
{
    rb_io_t *fptr = rb_io_get_fptr(io);
    if (fptr->fd < 0) {
        return Qnil;
    }
    rb_io_close(io);
    return Qnil;
}
close_on_exec = bool → true 或 false 点击切换源代码

设置 close-on-exec 标志。

f = File.open(File::NULL)
f.close_on_exec = true
system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory
f.closed?                #=> false

从 Ruby 2.0.0 开始,Ruby 默认情况下会为所有文件描述符设置 close-on-exec 标志。因此,您无需自行设置。此外,如果另一个线程使用 fork() 和 exec()(例如通过 system() 方法),取消设置 close-on-exec 标志会导致文件描述符泄漏。如果您确实需要将文件描述符继承到子进程,请使用 spawn() 的参数,例如 fd=>fd。

static VALUE
rb_io_set_close_on_exec(VALUE io, VALUE arg)
{
    int flag = RTEST(arg) ? FD_CLOEXEC : 0;
    rb_io_t *fptr;
    VALUE write_io;
    int fd, ret;

    write_io = GetWriteIO(io);
    if (io != write_io) {
        GetOpenFile(write_io, fptr);
        if (fptr && 0 <= (fd = fptr->fd)) {
            if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
            if ((ret & FD_CLOEXEC) != flag) {
                ret = (ret & ~FD_CLOEXEC) | flag;
                ret = fcntl(fd, F_SETFD, ret);
                if (ret != 0) rb_sys_fail_path(fptr->pathv);
            }
        }

    }

    GetOpenFile(io, fptr);
    if (fptr && 0 <= (fd = fptr->fd)) {
        if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
        if ((ret & FD_CLOEXEC) != flag) {
            ret = (ret & ~FD_CLOEXEC) | flag;
            ret = fcntl(fd, F_SETFD, ret);
            if (ret != 0) rb_sys_fail_path(fptr->pathv);
        }
    }
    return Qnil;
}
close_on_exec? → true 或 false 点击切换源代码

如果流将在 exec 时关闭,则返回 true,否则返回 false

f = File.open('t.txt')
f.close_on_exec? # => true
f.close_on_exec = false
f.close_on_exec? # => false
f.close
static VALUE
rb_io_close_on_exec_p(VALUE io)
{
    rb_io_t *fptr;
    VALUE write_io;
    int fd, ret;

    write_io = GetWriteIO(io);
    if (io != write_io) {
        GetOpenFile(write_io, fptr);
        if (fptr && 0 <= (fd = fptr->fd)) {
            if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
            if (!(ret & FD_CLOEXEC)) return Qfalse;
        }
    }

    GetOpenFile(io, fptr);
    if (fptr && 0 <= (fd = fptr->fd)) {
        if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv);
        if (!(ret & FD_CLOEXEC)) return Qfalse;
    }
    return Qtrue;
}
close_read → nil 点击切换源代码

如果打开用于读取,则关闭用于读取的流;返回 nil。请参阅 打开和关闭的流

如果流是通过 IO.popen 打开的,并且也已关闭写入,则设置全局变量 $?(子进程退出状态)。

示例

IO.popen('ruby', 'r+') do |pipe|
  puts pipe.closed?
  pipe.close_write
  puts pipe.closed?
  pipe.close_read
  puts $?
  puts pipe.closed?
end

输出

false
false
pid 14748 exit 0
true

相关:IO#closeIO#close_writeIO#closed?

static VALUE
rb_io_close_read(VALUE io)
{
    rb_io_t *fptr;
    VALUE write_io;

    fptr = rb_io_get_fptr(rb_io_taint_check(io));
    if (fptr->fd < 0) return Qnil;
    if (is_socket(fptr->fd, fptr->pathv)) {
#ifndef SHUT_RD
# define SHUT_RD 0
#endif
        if (shutdown(fptr->fd, SHUT_RD) < 0)
            rb_sys_fail_path(fptr->pathv);
        fptr->mode &= ~FMODE_READABLE;
        if (!(fptr->mode & FMODE_WRITABLE))
            return rb_io_close(io);
        return Qnil;
    }

    write_io = GetWriteIO(io);
    if (io != write_io) {
        rb_io_t *wfptr;
        wfptr = rb_io_get_fptr(rb_io_taint_check(write_io));
        wfptr->pid = fptr->pid;
        fptr->pid = 0;
        RFILE(io)->fptr = wfptr;
        /* bind to write_io temporarily to get rid of memory/fd leak */
        fptr->tied_io_for_writing = 0;
        RFILE(write_io)->fptr = fptr;
        rb_io_fptr_cleanup(fptr, FALSE);
        /* should not finalize fptr because another thread may be reading it */
        return Qnil;
    }

    if ((fptr->mode & (FMODE_DUPLEX|FMODE_WRITABLE)) == FMODE_WRITABLE) {
        rb_raise(rb_eIOError, "closing non-duplex IO for reading");
    }
    return rb_io_close(io);
}
close_write → nil 点击切换源代码

如果打开用于写入,则关闭用于写入的流;返回 nil。请参阅 打开和关闭的流

在关闭之前将任何缓冲的写入刷新到操作系统。

如果流是通过 IO.popen 打开的,并且也已关闭读取,则设置全局变量 $?(子进程退出状态)。

IO.popen('ruby', 'r+') do |pipe|
  puts pipe.closed?
  pipe.close_read
  puts pipe.closed?
  pipe.close_write
  puts $?
  puts pipe.closed?
end

输出

false
false
pid 15044 exit 0
true

相关:IO#closeIO#close_readIO#closed?

static VALUE
rb_io_close_write(VALUE io)
{
    rb_io_t *fptr;
    VALUE write_io;

    write_io = GetWriteIO(io);
    fptr = rb_io_get_fptr(rb_io_taint_check(write_io));
    if (fptr->fd < 0) return Qnil;
    if (is_socket(fptr->fd, fptr->pathv)) {
#ifndef SHUT_WR
# define SHUT_WR 1
#endif
        if (shutdown(fptr->fd, SHUT_WR) < 0)
            rb_sys_fail_path(fptr->pathv);
        fptr->mode &= ~FMODE_WRITABLE;
        if (!(fptr->mode & FMODE_READABLE))
            return rb_io_close(write_io);
        return Qnil;
    }

    if ((fptr->mode & (FMODE_DUPLEX|FMODE_READABLE)) == FMODE_READABLE) {
        rb_raise(rb_eIOError, "closing non-duplex IO for writing");
    }

    if (io != write_io) {
        fptr = rb_io_get_fptr(rb_io_taint_check(io));
        fptr->tied_io_for_writing = 0;
    }
    rb_io_close(write_io);
    return Qnil;
}
closed? → true 或 false 点击切换源代码

如果流已关闭读取和写入,则返回 true,否则返回 false。请参阅 打开和关闭的流

IO.popen('ruby', 'r+') do |pipe|
  puts pipe.closed?
  pipe.close_read
  puts pipe.closed?
  pipe.close_write
  puts pipe.closed?
end

输出

false
false
true

相关:IO#close_readIO#close_writeIO#close

VALUE
rb_io_closed_p(VALUE io)
{
    rb_io_t *fptr;
    VALUE write_io;
    rb_io_t *write_fptr;

    write_io = GetWriteIO(io);
    if (io != write_io) {
        write_fptr = RFILE(write_io)->fptr;
        if (write_fptr && 0 <= write_fptr->fd) {
            return Qfalse;
        }
    }

    fptr = rb_io_get_fptr(io);
    return RBOOL(0 > fptr->fd);
}
each -> enumerator 点击切换源代码

对从流中读取的每行剩余内容调用块;返回 self。如果已到达流末尾,则不执行任何操作;参见 行 IO

如果未提供任何参数,则根据行分隔符 $/ 读取行。

f = File.new('t.txt')
f.each_line {|line| p line }
f.each_line {|line| fail 'Cannot happen' }
f.close

输出

"First line\n"
"Second line\n"
"\n"
"Fourth line\n"
"Fifth line\n"

如果仅提供字符串参数 sep,则根据行分隔符 sep 读取行;参见 行分隔符

f = File.new('t.txt')
f.each_line('li') {|line| p line }
f.close

输出

"First li"
"ne\nSecond li"
"ne\n\nFourth li"
"ne\nFifth li"
"ne\n"

sep 的两个特殊值将被认可。

f = File.new('t.txt')
# Get all into one string.
f.each_line(nil) {|line| p line }
f.close

输出

"First line\nSecond line\n\nFourth line\nFifth line\n"

f.rewind
# Get paragraphs (up to two line separators).
f.each_line('') {|line| p line }

输出

"First line\nSecond line\n\n"
"Fourth line\nFifth line\n"

如果仅提供整数参数 limit,则限制每行的字节数;参见 行限制

f = File.new('t.txt')
f.each_line(8) {|line| p line }
f.close

输出

"First li"
"ne\n"
"Second l"
"ine\n"
"\n"
"Fourth l"
"ine\n"
"Fifth li"
"ne\n"

给出参数seplimit,结合两种行为

  • 根据行分隔符 sep 调用下一行。

  • 但返回的字节数不超过限制允许的字节数。

可选关键字参数 chomp 指定是否省略行分隔符。

f = File.new('t.txt')
f.each_line(chomp: true) {|line| p line }
f.close

输出

"First line"
"Second line"
""
"Fourth line"
"Fifth line"

如果没有给出块,则返回一个 Enumerator

static VALUE
rb_io_each_line(int argc, VALUE *argv, VALUE io)
{
    VALUE str;
    struct getline_arg args;

    RETURN_ENUMERATOR(io, argc, argv);
    prepare_getline_args(argc, argv, &args, io);
    if (args.limit == 0)
        rb_raise(rb_eArgError, "invalid limit: 0 for each_line");
    while (!NIL_P(str = rb_io_getline_1(args.rs, args.limit, args.chomp, io))) {
        rb_yield(str);
    }
    return io;
}
也称为:each_line
each_byte {|byte| ... } → self 点击切换源代码
each_byte → enumerator

对流中的每个字节 (0..255) 调用给定的块;返回 self。参见 字节 IO

f = File.new('t.rus')
a = []
f.each_byte {|b| a << b }
a # => [209, 130, 208, 181, 209, 129, 209, 130]
f.close

如果没有给出块,则返回一个 Enumerator

相关:IO#each_charIO#each_codepoint

static VALUE
rb_io_each_byte(VALUE io)
{
    rb_io_t *fptr;

    RETURN_ENUMERATOR(io, 0, 0);
    GetOpenFile(io, fptr);

    do {
        while (fptr->rbuf.len > 0) {
            char *p = fptr->rbuf.ptr + fptr->rbuf.off++;
            fptr->rbuf.len--;
            rb_yield(INT2FIX(*p & 0xff));
            rb_io_check_byte_readable(fptr);
            errno = 0;
        }
        READ_CHECK(fptr);
    } while (io_fillbuf(fptr) >= 0);
    return io;
}
each_char {|c| ... } → self 点击切换源代码
each_char → enumerator

对流中的每个字符调用给定的块;返回 self。参见 字符 IO

f = File.new('t.rus')
a = []
f.each_char {|c| a << c.ord }
a # => [1090, 1077, 1089, 1090]
f.close

如果没有给出块,则返回一个 Enumerator

相关:IO#each_byteIO#each_codepoint

static VALUE
rb_io_each_char(VALUE io)
{
    rb_io_t *fptr;
    rb_encoding *enc;
    VALUE c;

    RETURN_ENUMERATOR(io, 0, 0);
    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);

    enc = io_input_encoding(fptr);
    READ_CHECK(fptr);
    while (!NIL_P(c = io_getc(fptr, enc))) {
        rb_yield(c);
    }
    return io;
}
each_codepoint {|c| ... } → self 点击切换源代码
each_codepoint → enumerator

对流中的每个码点调用给定的块;返回 self

f = File.new('t.rus')
a = []
f.each_codepoint {|c| a << c }
a # => [1090, 1077, 1089, 1090]
f.close

如果没有给出块,则返回一个 Enumerator

相关:IO#each_byteIO#each_char

static VALUE
rb_io_each_codepoint(VALUE io)
{
    rb_io_t *fptr;
    rb_encoding *enc;
    unsigned int c;
    int r, n;

    RETURN_ENUMERATOR(io, 0, 0);
    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);

    READ_CHECK(fptr);
    if (NEED_READCONV(fptr)) {
        SET_BINARY_MODE(fptr);
        r = 1;          /* no invalid char yet */
        for (;;) {
            make_readconv(fptr, 0);
            for (;;) {
                if (fptr->cbuf.len) {
                    if (fptr->encs.enc)
                        r = rb_enc_precise_mbclen(fptr->cbuf.ptr+fptr->cbuf.off,
                                                  fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
                                                  fptr->encs.enc);
                    else
                        r = ONIGENC_CONSTRUCT_MBCLEN_CHARFOUND(1);
                    if (!MBCLEN_NEEDMORE_P(r))
                        break;
                    if (fptr->cbuf.len == fptr->cbuf.capa) {
                        rb_raise(rb_eIOError, "too long character");
                    }
                }
                if (more_char(fptr) == MORE_CHAR_FINISHED) {
                    clear_readconv(fptr);
                    if (!MBCLEN_CHARFOUND_P(r)) {
                        enc = fptr->encs.enc;
                        goto invalid;
                    }
                    return io;
                }
            }
            if (MBCLEN_INVALID_P(r)) {
                enc = fptr->encs.enc;
                goto invalid;
            }
            n = MBCLEN_CHARFOUND_LEN(r);
            if (fptr->encs.enc) {
                c = rb_enc_codepoint(fptr->cbuf.ptr+fptr->cbuf.off,
                                     fptr->cbuf.ptr+fptr->cbuf.off+fptr->cbuf.len,
                                     fptr->encs.enc);
            }
            else {
                c = (unsigned char)fptr->cbuf.ptr[fptr->cbuf.off];
            }
            fptr->cbuf.off += n;
            fptr->cbuf.len -= n;
            rb_yield(UINT2NUM(c));
            rb_io_check_byte_readable(fptr);
        }
    }
    NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr);
    enc = io_input_encoding(fptr);
    while (io_fillbuf(fptr) >= 0) {
        r = rb_enc_precise_mbclen(fptr->rbuf.ptr+fptr->rbuf.off,
                                  fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc);
        if (MBCLEN_CHARFOUND_P(r) &&
            (n = MBCLEN_CHARFOUND_LEN(r)) <= fptr->rbuf.len) {
            c = rb_enc_codepoint(fptr->rbuf.ptr+fptr->rbuf.off,
                                 fptr->rbuf.ptr+fptr->rbuf.off+fptr->rbuf.len, enc);
            fptr->rbuf.off += n;
            fptr->rbuf.len -= n;
            rb_yield(UINT2NUM(c));
        }
        else if (MBCLEN_INVALID_P(r)) {
            goto invalid;
        }
        else if (MBCLEN_NEEDMORE_P(r)) {
            char cbuf[8], *p = cbuf;
            int more = MBCLEN_NEEDMORE_LEN(r);
            if (more > numberof(cbuf)) goto invalid;
            more += n = fptr->rbuf.len;
            if (more > numberof(cbuf)) goto invalid;
            while ((n = (int)read_buffered_data(p, more, fptr)) > 0 &&
                   (p += n, (more -= n) > 0)) {
                if (io_fillbuf(fptr) < 0) goto invalid;
                if ((n = fptr->rbuf.len) > more) n = more;
            }
            r = rb_enc_precise_mbclen(cbuf, p, enc);
            if (!MBCLEN_CHARFOUND_P(r)) goto invalid;
            c = rb_enc_codepoint(cbuf, p, enc);
            rb_yield(UINT2NUM(c));
        }
        else {
            continue;
        }
        rb_io_check_byte_readable(fptr);
    }
    return io;

  invalid:
    rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(enc));
    UNREACHABLE_RETURN(Qundef);
}
each_line(*args)

对从流中读取的每行剩余内容调用块;返回 self。如果已到达流末尾,则不执行任何操作;参见 行 IO

如果未提供任何参数,则根据行分隔符 $/ 读取行。

f = File.new('t.txt')
f.each_line {|line| p line }
f.each_line {|line| fail 'Cannot happen' }
f.close

输出

"First line\n"
"Second line\n"
"\n"
"Fourth line\n"
"Fifth line\n"

如果仅提供字符串参数 sep,则根据行分隔符 sep 读取行;参见 行分隔符

f = File.new('t.txt')
f.each_line('li') {|line| p line }
f.close

输出

"First li"
"ne\nSecond li"
"ne\n\nFourth li"
"ne\nFifth li"
"ne\n"

sep 的两个特殊值将被认可。

f = File.new('t.txt')
# Get all into one string.
f.each_line(nil) {|line| p line }
f.close

输出

"First line\nSecond line\n\nFourth line\nFifth line\n"

f.rewind
# Get paragraphs (up to two line separators).
f.each_line('') {|line| p line }

输出

"First line\nSecond line\n\n"
"Fourth line\nFifth line\n"

如果仅提供整数参数 limit,则限制每行的字节数;参见 行限制

f = File.new('t.txt')
f.each_line(8) {|line| p line }
f.close

输出

"First li"
"ne\n"
"Second l"
"ine\n"
"\n"
"Fourth l"
"ine\n"
"Fifth li"
"ne\n"

给出参数seplimit,结合两种行为

  • 根据行分隔符 sep 调用下一行。

  • 但返回的字节数不超过限制允许的字节数。

可选关键字参数 chomp 指定是否省略行分隔符。

f = File.new('t.txt')
f.each_line(chomp: true) {|line| p line }
f.close

输出

"First line"
"Second line"
""
"Fourth line"
"Fifth line"

如果没有给出块,则返回一个 Enumerator

别名:each
eof → true 或 false 点击切换源代码

如果流处于其末尾,则返回 true,否则返回 false;请参阅 位置

f = File.open('t.txt')
f.eof           # => false
f.seek(0, :END) # => 0
f.eof           # => true
f.close

除非流以读取方式打开,否则会引发异常;请参阅 模式

如果 self 是一个流,例如管道或套接字,则此方法会阻塞,直到另一端发送一些数据或关闭它。

r, w = IO.pipe
Thread.new { sleep 1; w.close }
r.eof? # => true # After 1-second wait.

r, w = IO.pipe
Thread.new { sleep 1; w.puts "a" }
r.eof?  # => false # After 1-second wait.

r, w = IO.pipe
r.eof?  # blocks forever

请注意,此方法会将数据读取到输入字节缓冲区。因此,IO#sysread 可能不会像您使用 IO#eof? 时预期的那样工作,除非您首先调用 IO#rewind(对于某些流不可用)。

VALUE
rb_io_eof(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);

    if (READ_CHAR_PENDING(fptr)) return Qfalse;
    if (READ_DATA_PENDING(fptr)) return Qfalse;
    READ_CHECK(fptr);
#if RUBY_CRLF_ENVIRONMENT
    if (!NEED_READCONV(fptr) && NEED_NEWLINE_DECORATOR_ON_READ(fptr)) {
        return RBOOL(eof(fptr->fd));;
    }
#endif
    return RBOOL(io_fillbuf(fptr) < 0);
}
也称为:eof?
eof?()

如果流处于其末尾,则返回 true,否则返回 false;请参阅 位置

f = File.open('t.txt')
f.eof           # => false
f.seek(0, :END) # => 0
f.eof           # => true
f.close

除非流以读取方式打开,否则会引发异常;请参阅 模式

如果 self 是一个流,例如管道或套接字,则此方法会阻塞,直到另一端发送一些数据或关闭它。

r, w = IO.pipe
Thread.new { sleep 1; w.close }
r.eof? # => true # After 1-second wait.

r, w = IO.pipe
Thread.new { sleep 1; w.puts "a" }
r.eof?  # => false # After 1-second wait.

r, w = IO.pipe
r.eof?  # blocks forever

请注意,此方法会将数据读取到输入字节缓冲区。因此,IO#sysread 可能不会像您使用 IO#eof? 时预期的那样工作,除非您首先调用 IO#rewind(对于某些流不可用)。

别名:eof
external_encoding → 编码或 nil 点击切换源代码

返回表示流编码的 Encoding 对象,如果流处于写入模式且未指定编码,则返回 nil

请参阅 编码

static VALUE
rb_io_external_encoding(VALUE io)
{
    rb_io_t *fptr = RFILE(rb_io_taint_check(io))->fptr;

    if (fptr->encs.enc2) {
        return rb_enc_from_encoding(fptr->encs.enc2);
    }
    if (fptr->mode & FMODE_WRITABLE) {
        if (fptr->encs.enc)
            return rb_enc_from_encoding(fptr->encs.enc);
        return Qnil;
    }
    return rb_enc_from_encoding(io_read_encoding(fptr));
}
fcntl(integer_cmd, argument) → integer 点击切换源代码

调用 Posix 系统调用 fcntl(2),它提供了一种机制来发出低级命令以控制或查询面向文件的 I/O 流。参数和结果与平台相关。

如果 argument 是一个数字,则直接传递其值;如果它是一个字符串,则将其解释为字节的二进制序列。(Array#pack 可能是一种构建此字符串的有用方法。)

并非所有平台都已实现。

static VALUE
rb_io_fcntl(int argc, VALUE *argv, VALUE io)
{
    VALUE req, arg;

    rb_scan_args(argc, argv, "11", &req, &arg);
    return rb_fcntl(io, req, arg);
}
fdatasync → 0 点击切换源代码

通过操作系统的 fdatasync(2)(如果支持)立即将流中缓冲的所有数据写入磁盘,否则通过 fsync(2)(如果支持)写入磁盘;否则引发异常。

static VALUE
rb_io_fdatasync(VALUE io)
{
    rb_io_t *fptr;

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);

    if (io_fflush(fptr) < 0)
        rb_sys_fail_on_write(fptr);

    if ((int)rb_thread_io_blocking_region(nogvl_fdatasync, fptr, fptr->fd) == 0)
        return INT2FIX(0);

    /* fall back */
    return rb_io_fsync(io);
}
fileno → integer 点击切换源代码

返回流的整数文件描述符。

$stdin.fileno             # => 0
$stdout.fileno            # => 1
$stderr.fileno            # => 2
File.open('t.txt').fileno # => 10
f.close
static VALUE
rb_io_fileno(VALUE io)
{
    rb_io_t *fptr = RFILE(io)->fptr;
    int fd;

    rb_io_check_closed(fptr);
    fd = fptr->fd;
    return INT2FIX(fd);
}
也称为:to_i
flush → self 点击切换源代码

self 中缓冲的数据刷新到操作系统(但不一定刷新操作系统中缓冲的数据)。

$stdout.print 'no newline' # Not necessarily flushed.
$stdout.flush              # Flushed.
VALUE
rb_io_flush(VALUE io)
{
    return rb_io_flush_raw(io, 1);
}
fsync → 0 点击切换源代码

立即将流中缓冲的所有数据写入磁盘,通过操作系统的 fsync(2)

注意以下区别

  • IO#sync=: 确保数据从流的内部缓冲区中刷新,但不保证操作系统实际将数据写入磁盘。

  • IO#fsync: 确保数据从内部缓冲区中刷新,并将数据写入磁盘。

如果操作系统不支持 fsync(2),则会引发异常。

static VALUE
rb_io_fsync(VALUE io)
{
    rb_io_t *fptr;

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);

    if (io_fflush(fptr) < 0)
        rb_sys_fail_on_write(fptr);
    if ((int)rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd) < 0)
        rb_sys_fail_path(fptr->pathv);
    return INT2FIX(0);
}
getbyte → 整数或 nil 点击切换源代码

从流中读取并返回下一个字节(范围为 0..255);如果已到达流末尾,则返回 nil。参见 字节 IO

f = File.open('t.txt')
f.getbyte # => 70
f.close
f = File.open('t.rus')
f.getbyte # => 209
f.close

相关:IO#readbyte(可能会引发 EOFError)。

VALUE
rb_io_getbyte(VALUE io)
{
    rb_io_t *fptr;
    int c;

    GetOpenFile(io, fptr);
    rb_io_check_byte_readable(fptr);
    READ_CHECK(fptr);
    VALUE r_stdout = rb_ractor_stdout();
    if (fptr->fd == 0 && (fptr->mode & FMODE_TTY) && RB_TYPE_P(r_stdout, T_FILE)) {
        rb_io_t *ofp;
        GetOpenFile(r_stdout, ofp);
        if (ofp->mode & FMODE_TTY) {
            rb_io_flush(r_stdout);
        }
    }
    if (io_fillbuf(fptr) < 0) {
        return Qnil;
    }
    fptr->rbuf.off++;
    fptr->rbuf.len--;
    c = (unsigned char)fptr->rbuf.ptr[fptr->rbuf.off-1];
    return INT2FIX(c & 0xff);
}
getc → 字符或 nil 点击切换源代码

从流中读取并返回下一个 1 字符串;如果已到达流末尾,则返回 nil。参见 字符 IO

f = File.open('t.txt')
f.getc     # => "F"
f.close
f = File.open('t.rus')
f.getc.ord # => 1090
f.close

相关:IO#readchar(可能会引发 EOFError)。

static VALUE
rb_io_getc(VALUE io)
{
    rb_io_t *fptr;
    rb_encoding *enc;

    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);

    enc = io_input_encoding(fptr);
    READ_CHECK(fptr);
    return io_getc(fptr, enc);
}
gets(sep = $/, chomp: false) → 字符串或 nil 点击切换源代码
gets(limit, chomp: false) → 字符串或 nil
gets(sep, limit, chomp: false) → 字符串或 nil

从流中读取并返回一行;将返回值分配给 $_。参见 行 IO

如果没有给出参数,则返回由行分隔符 $/ 确定的下一行,或者如果不存在则返回 nil

f = File.open('t.txt')
f.gets # => "First line\n"
$_     # => "First line\n"
f.gets # => "\n"
f.gets # => "Fourth line\n"
f.gets # => "Fifth line\n"
f.gets # => nil
f.close

仅给出字符串参数 sep 时,返回由行分隔符 sep 确定的下一行,或者如果不存在则返回 nil;参见 行分隔符

f = File.new('t.txt')
f.gets('l')   # => "First l"
f.gets('li')  # => "ine\nSecond li"
f.gets('lin') # => "ne\n\nFourth lin"
f.gets        # => "e\n"
f.close

sep 的两个特殊值将被认可。

f = File.new('t.txt')
# Get all.
f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
f.rewind
# Get paragraph (up to two line separators).
f.gets('')  # => "First line\nSecond line\n\n"
f.close

仅给出整数参数 limit 时,限制行中的字节数;参见 行限制

# No more than one line.
File.open('t.txt') {|f| f.gets(10) } # => "First line"
File.open('t.txt') {|f| f.gets(11) } # => "First line\n"
File.open('t.txt') {|f| f.gets(12) } # => "First line\n"

给出参数seplimit,结合两种行为

  • 返回由行分隔符 sep 确定的下一行,或者如果不存在则返回 nil

  • 但返回的字节数不超过限制允许的字节数。

可选关键字参数 chomp 指定是否省略行分隔符。

f = File.open('t.txt')
# Chomp the lines.
f.gets(chomp: true) # => "First line"
f.gets(chomp: true) # => "Second line"
f.gets(chomp: true) # => ""
f.gets(chomp: true) # => "Fourth line"
f.gets(chomp: true) # => "Fifth line"
f.gets(chomp: true) # => nil
f.close
static VALUE
rb_io_gets_m(int argc, VALUE *argv, VALUE io)
{
    VALUE str;

    str = rb_io_getline(argc, argv, io);
    rb_lastline_set(str);

    return str;
}
inspect → 字符串 点击切换源代码

返回 self 的字符串表示形式

f = File.open('t.txt')
f.inspect # => "#<File:t.txt>"
f.close
static VALUE
rb_io_inspect(VALUE obj)
{
    rb_io_t *fptr;
    VALUE result;
    static const char closed[] = " (closed)";

    fptr = RFILE(obj)->fptr;
    if (!fptr) return rb_any_to_s(obj);
    result = rb_str_new_cstr("#<");
    rb_str_append(result, rb_class_name(CLASS_OF(obj)));
    rb_str_cat2(result, ":");
    if (NIL_P(fptr->pathv)) {
        if (fptr->fd < 0) {
            rb_str_cat(result, closed+1, strlen(closed)-1);
        }
        else {
            rb_str_catf(result, "fd %d", fptr->fd);
        }
    }
    else {
        rb_str_append(result, fptr->pathv);
        if (fptr->fd < 0) {
            rb_str_cat(result, closed, strlen(closed));
        }
    }
    return rb_str_cat2(result, ">");
}
internal_encoding → 编码或 nil 点击切换源代码

如果指定了转换,则返回表示内部字符串编码的 Encoding 对象,否则返回 nil

请参阅 编码

static VALUE
rb_io_internal_encoding(VALUE io)
{
    rb_io_t *fptr = RFILE(rb_io_taint_check(io))->fptr;

    if (!fptr->encs.enc2) return Qnil;
    return rb_enc_from_encoding(io_read_encoding(fptr));
}
ioctl(integer_cmd, argument) → integer 点击切换源代码

调用 Posix 系统调用 ioctl(2),该调用向 I/O 设备发出低级命令。

向 I/O 设备发出低级命令。参数和返回值是平台相关的。调用的效果是平台相关的。

如果参数 argument 是一个整数,则直接传递;如果它是一个字符串,则将其解释为字节的二进制序列。

并非所有平台都已实现。

static VALUE
rb_io_ioctl(int argc, VALUE *argv, VALUE io)
{
    VALUE req, arg;

    rb_scan_args(argc, argv, "11", &req, &arg);
    return rb_ioctl(io, req, arg);
}
isatty → true 或 false 点击切换源代码

如果流与终端设备 (tty) 相关联,则返回 true,否则返回 false

f = File.new('t.txt').isatty    #=> false
f.close
f = File.new('/dev/tty').isatty #=> true
f.close
static VALUE
rb_io_isatty(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    return RBOOL(isatty(fptr->fd) != 0);
}
别名:tty?
lineno → integer 点击切换源代码

返回流的当前行号;请参见 行号

static VALUE
rb_io_lineno(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);
    return INT2NUM(fptr->lineno);
}
lineno = integer → integer 点击切换源代码

设置并返回流的行号;请参见 行号

static VALUE
rb_io_set_lineno(VALUE io, VALUE lineno)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);
    fptr->lineno = NUM2INT(lineno);
    return lineno;
}
path → string 或 nil 点击切换源代码

返回与 IO 关联的路径,如果与 IO 没有关联的路径,则返回 nil。不能保证该路径在文件系统中存在。

$stdin.path # => "<STDIN>"

File.open("testfile") {|f| f.path} # => "testfile"
VALUE
rb_io_path(VALUE io)
{
    rb_io_t *fptr = RFILE(io)->fptr;

    if (!fptr)
        return Qnil;

    return rb_obj_dup(fptr->pathv);
}
别名:to_path
pid → integer 或 nil 点击切换源代码

返回与流关联的子进程的进程 ID,该进程 ID 将由 IO#popen 设置,如果流不是由 IO#popen 创建,则返回 nil

pipe = IO.popen("-")
if pipe
  $stderr.puts "In parent, child pid is #{pipe.pid}"
else
  $stderr.puts "In child, pid is #{$$}"
end

输出

In child, pid is 26209
In parent, child pid is 26209
static VALUE
rb_io_pid(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    if (!fptr->pid)
        return Qnil;
    return PIDT2NUM(fptr->pid);
}
pos()

返回self中当前位置(以字节为单位)(参见 位置)。

f = File.open('t.txt')
f.tell # => 0
f.gets # => "First line\n"
f.tell # => 12
f.close

相关:IO#pos=IO#seek

别名:tell
pos = new_position → new_position 点击切换源代码

跳转到给定的new_position(以字节为单位);参见 位置

f = File.open('t.txt')
f.tell     # => 0
f.pos = 20 # => 20
f.tell     # => 20
f.close

相关:IO#seekIO#tell

static VALUE
rb_io_set_pos(VALUE io, VALUE offset)
{
    rb_io_t *fptr;
    rb_off_t pos;

    pos = NUM2OFFT(offset);
    GetOpenFile(io, fptr);
    pos = io_seek(fptr, pos, SEEK_SET);
    if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv);

    return OFFT2NUM(pos);
}
pread(maxlen, offset) → string 点击切换源代码
pread(maxlen, offset, out_string) → string

行为类似于 IO#readpartial,但它

  • 从给定的offset(以字节为单位)读取。

  • 忽略并不会修改流的位置(参见 位置)。

  • 绕过流中的任何用户空间缓冲。

由于此方法不会干扰流的状态(特别是其位置),因此pread允许多个线程和进程使用相同的 IO 对象在不同的偏移量处进行读取。

f = File.open('t.txt')
f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n"
f.pos  # => 52
# Read 12 bytes at offset 0.
f.pread(12, 0) # => "First line\n"
# Read 9 bytes at offset 8.
f.pread(9, 8)  # => "ne\nSecon"
f.close

在某些平台上不可用。

static VALUE
rb_io_pread(int argc, VALUE *argv, VALUE io)
{
    VALUE len, offset, str;
    rb_io_t *fptr;
    ssize_t n;
    struct prdwr_internal_arg arg = {.io = io};
    int shrinkable;

    rb_scan_args(argc, argv, "21", &len, &offset, &str);
    arg.count = NUM2SIZET(len);
    arg.offset = NUM2OFFT(offset);

    shrinkable = io_setstrbuf(&str, (long)arg.count);
    if (arg.count == 0) return str;
    arg.buf = RSTRING_PTR(str);

    GetOpenFile(io, fptr);
    rb_io_check_byte_readable(fptr);

    arg.fd = fptr->fd;
    rb_io_check_closed(fptr);

    rb_str_locktmp(str);
    n = (ssize_t)rb_ensure(pread_internal_call, (VALUE)&arg, rb_str_unlocktmp, str);

    if (n < 0) {
        rb_sys_fail_path(fptr->pathv);
    }
    io_set_read_length(str, n, shrinkable);
    if (n == 0 && arg.count > 0) {
        rb_eof_error();
    }

    return str;
}
print(*objects) → nil 点击切换源代码

将给定的对象写入流;返回nil。如果输出记录分隔符$OUTPUT_RECORD_SEPARATOR$\)不为nil,则追加它。参见 行 IO

给定参数objects,对于每个对象

  • 如果对象不是字符串,则通过其方法to_s进行转换。

  • 写入流。

  • 如果不是最后一个对象,则写入输出字段分隔符$OUTPUT_FIELD_SEPARATOR$,),如果它不为nil

使用默认分隔符

f = File.open('t.tmp', 'w+')
objects = [0, 0.0, Rational(0, 1), Complex(0, 0), :zero, 'zero']
p $OUTPUT_RECORD_SEPARATOR
p $OUTPUT_FIELD_SEPARATOR
f.print(*objects)
f.rewind
p f.read
f.close

输出

nil
nil
"00.00/10+0izerozero"

使用指定的分隔符

$\ = "\n"
$, = ','
f.rewind
f.print(*objects)
f.rewind
p f.read

输出

"0,0.0,0/1,0+0i,zero,zero\n"

如果没有给出参数,则写入$_的内容(通常是最近的用户输入)

f = File.open('t.tmp', 'w+')
gets # Sets $_ to the most recent user input.
f.print
f.close
printf(format_string, *objects) → nil 点击切换源代码

格式化并将objects写入流。

有关format_string的详细信息,请参见 格式规范

VALUE
rb_io_printf(int argc, const VALUE *argv, VALUE out)
{
    rb_io_write(out, rb_f_sprintf(argc, argv));
    return Qnil;
}
putc(object) → object 点击切换源代码

将一个字符写入流。参见 字符 IO

如果object是数字,则在必要时转换为整数,然后写入代码为最低有效字节的字符;如果object是字符串,则写入第一个字符

$stdout.putc "A"
$stdout.putc 65

输出

AA
static VALUE
rb_io_putc(VALUE io, VALUE ch)
{
    VALUE str;
    if (RB_TYPE_P(ch, T_STRING)) {
        str = rb_str_substr(ch, 0, 1);
    }
    else {
        char c = NUM2CHR(ch);
        str = rb_str_new(&c, 1);
    }
    rb_io_write(io, str);
    return ch;
}
puts(*objects) → nil 点击切换源代码

将给定的objects写入流,该流必须处于写入打开状态;返回nil。\ 在每个不以换行符序列结尾的对象之后写入一个换行符。如果在没有参数的情况下调用,则写入一个换行符。参见 行 IO

请注意,每个添加的换行符都是字符"\n"<//tt>,而不是输出记录分隔符 (<tt>$\)。

对每个对象的处理

  • 字符串:写入字符串。

  • 既不是字符串也不是数组:写入object.to_s

  • 数组:写入数组的每个元素;数组可以嵌套。

为了使这些示例简短,我们定义了这个辅助方法

def show(*objects)
  # Puts objects to file.
  f = File.new('t.tmp', 'w+')
  f.puts(objects)
  # Return file content.
  f.rewind
  p f.read
  f.close
end

# Strings without newlines.
show('foo', 'bar', 'baz')     # => "foo\nbar\nbaz\n"
# Strings, some with newlines.
show("foo\n", 'bar', "baz\n") # => "foo\nbar\nbaz\n"

# Neither strings nor arrays:
show(0, 0.0, Rational(0, 1), Complex(9, 0), :zero)
# => "0\n0.0\n0/1\n9+0i\nzero\n"

# Array of strings.
show(['foo', "bar\n", 'baz']) # => "foo\nbar\nbaz\n"
# Nested arrays.
show([[[0, 1], 2, 3], 4, 5])  # => "0\n1\n2\n3\n4\n5\n"
VALUE
rb_io_puts(int argc, const VALUE *argv, VALUE out)
{
    VALUE line, args[2];

    /* if no argument given, print newline. */
    if (argc == 0) {
        rb_io_write(out, rb_default_rs);
        return Qnil;
    }
    for (int i = 0; i < argc; i++) {
        // Convert the argument to a string:
        if (RB_TYPE_P(argv[i], T_STRING)) {
            line = argv[i];
        }
        else if (rb_exec_recursive(io_puts_ary, argv[i], out)) {
            continue;
        }
        else {
            line = rb_obj_as_string(argv[i]);
        }

        // Write the line:
        int n = 0;
        if (RSTRING_LEN(line) == 0) {
            args[n++] = rb_default_rs;
        }
        else {
            args[n++] = line;
            if (!rb_str_end_with_asciichar(line, '\n')) {
                args[n++] = rb_default_rs;
            }
        }

        rb_io_writev(out, n, args);
    }

    return Qnil;
}
pwrite(object, offset) → integer 点击切换源代码

行为类似于 IO#write,除了它

  • 在给定的offset(以字节为单位)处写入。

  • 忽略并不会修改流的位置(参见 位置)。

  • 绕过流中的任何用户空间缓冲。

由于此方法不会干扰流的状态(特别是其位置),因此pwrite 允许多个线程和进程使用相同的 IO 对象在不同的偏移量处写入。

f = File.open('t.tmp', 'w+')
# Write 6 bytes at offset 3.
f.pwrite('ABCDEF', 3) # => 6
f.rewind
f.read # => "\u0000\u0000\u0000ABCDEF"
f.close

在某些平台上不可用。

static VALUE
rb_io_pwrite(VALUE io, VALUE str, VALUE offset)
{
    rb_io_t *fptr;
    ssize_t n;
    struct prdwr_internal_arg arg = {.io = io};
    VALUE tmp;

    if (!RB_TYPE_P(str, T_STRING))
        str = rb_obj_as_string(str);

    arg.offset = NUM2OFFT(offset);

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);
    rb_io_check_writable(fptr);
    arg.fd = fptr->fd;

    tmp = rb_str_tmp_frozen_acquire(str);
    arg.buf = RSTRING_PTR(tmp);
    arg.count = (size_t)RSTRING_LEN(tmp);

    n = (ssize_t)rb_thread_io_blocking_call(internal_pwrite_func, &arg, fptr->fd, RB_WAITFD_OUT);
    if (n < 0) rb_sys_fail_path(fptr->pathv);
    rb_str_tmp_frozen_release(str, tmp);

    return SSIZET2NUM(n);
}
read(maxlen = nil, out_string = nil) → new_string, out_string, or nil 点击切换源代码

从流中读取字节;流必须处于读取打开状态(参见 访问模式)。

  • 如果maxlennil,则使用流的数据模式读取所有字节。

  • 否则,以二进制模式读取最多maxlen个字节。

返回一个字符串(新的字符串或给定的out_string),其中包含读取的字节。字符串的编码取决于maxLenout_string

  • maxlennil:使用self的内部编码(无论是否给出了out_string)。

  • maxlen不为nil

    • 给出了out_string:不修改out_string的编码。

    • 未给出out_string:使用 ASCII-8BIT。

没有参数out_string

当省略参数out_string时,返回值是一个新的字符串。

f = File.new('t.txt')
f.read
# => "First line\nSecond line\n\nFourth line\nFifth line\n"
f.rewind
f.read(30) # => "First line\r\nSecond line\r\n\r\nFou"
f.read(30) # => "rth line\r\nFifth line\r\n"
f.read(30) # => nil
f.close

如果maxlen为零,则返回一个空字符串。

带有参数out_string

当给出参数out_string时,返回值为out_string,其内容将被替换。

f = File.new('t.txt')
s = 'foo'      # => "foo"
f.read(nil, s) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
s              # => "First line\nSecond line\n\nFourth line\nFifth line\n"
f.rewind
s = 'bar'
f.read(30, s)  # => "First line\r\nSecond line\r\n\r\nFou"
s              # => "First line\r\nSecond line\r\n\r\nFou"
s = 'baz'
f.read(30, s)  # => "rth line\r\nFifth line\r\n"
s              # => "rth line\r\nFifth line\r\n"
s = 'bat'
f.read(30, s)  # => nil
s              # => ""
f.close

请注意,此方法的行为类似于 C 中的 fread() 函数。这意味着它会重试调用 read(2) 系统调用以使用指定的 maxlen 读取数据(或直到 EOF)。

即使流处于非阻塞模式,也会保留此行为。(此方法与其他方法一样,对非阻塞标志不敏感。)

如果您需要类似于单个 read(2) 系统调用的行为,请考虑使用 readpartialread_nonblocksysread

相关:IO#write

static VALUE
io_read(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;
    long n, len;
    VALUE length, str;
    int shrinkable;
#if RUBY_CRLF_ENVIRONMENT
    int previous_mode;
#endif

    rb_scan_args(argc, argv, "02", &length, &str);

    if (NIL_P(length)) {
        GetOpenFile(io, fptr);
        rb_io_check_char_readable(fptr);
        return read_all(fptr, remain_size(fptr), str);
    }
    len = NUM2LONG(length);
    if (len < 0) {
        rb_raise(rb_eArgError, "negative length %ld given", len);
    }

    shrinkable = io_setstrbuf(&str,len);

    GetOpenFile(io, fptr);
    rb_io_check_byte_readable(fptr);
    if (len == 0) {
        io_set_read_length(str, 0, shrinkable);
        return str;
    }

    READ_CHECK(fptr);
#if RUBY_CRLF_ENVIRONMENT
    previous_mode = set_binary_mode_with_seek_cur(fptr);
#endif
    n = io_fread(str, 0, len, fptr);
    io_set_read_length(str, n, shrinkable);
#if RUBY_CRLF_ENVIRONMENT
    if (previous_mode == O_TEXT) {
        setmode(fptr->fd, O_TEXT);
    }
#endif
    if (n == 0) return Qnil;

    return str;
}
read_nonblock(maxlen [, options]) → string 点击切换源代码
read_nonblock(maxlen, outbuf [, options]) → outbuf

ios 中读取最多 maxlen 字节,使用 read(2) 系统调用,在底层文件描述符上设置 O_NONBLOCK 之后。

如果存在可选的 outbuf 参数,它必须引用一个 String,该字符串将接收数据。即使在方法调用之前 outbuf 不为空,它也只包含接收到的数据。

read_nonblock 只是调用 read(2) 系统调用。它会导致 read(2) 系统调用引起的所有错误:Errno::EWOULDBLOCK、Errno::EINTR 等。调用者应该注意这些错误。

如果异常是 Errno::EWOULDBLOCK 或 Errno::EAGAIN,它将由 IO::WaitReadable 扩展。因此,可以使用 IO::WaitReadable 来拯救异常以重试 read_nonblock。

read_nonblock 在 EOF 时会导致 EOFError

在某些平台上,例如 Windows,非阻塞模式在除套接字之外的 IO 对象上不受支持。在这种情况下,将引发 Errno::EBADF。

如果读取字节缓冲区不为空,read_nonblock 将像 readpartial 一样从缓冲区读取。在这种情况下,不会调用 read(2) 系统调用。

read_nonblock 引发 IO::WaitReadable 类型的异常时,在 io 可读之前不应调用 read_nonblock 以避免繁忙循环。这可以通过以下方式完成。

# emulates blocking read (readpartial).
begin
  result = io.read_nonblock(maxlen)
rescue IO::WaitReadable
  IO.select([io])
  retry
end

虽然 IO#read_nonblock 不会引发 IO::WaitWritable。OpenSSL::Buffering#read_nonblock 可以引发 IO::WaitWritable。如果 IO 和 SSL 应该被多态地使用,那么也应该拯救 IO::WaitWritable。有关示例代码,请参阅 OpenSSL::Buffering#read_nonblock 的文档。

请注意,此方法与 readpartial 相同,只是设置了非阻塞标志。

通过将关键字参数 exception 指定为 false,您可以指示 read_nonblock 不应引发 IO::WaitReadable 异常,而是返回符号 :wait_readable。在 EOF 时,它将返回 nil 而不是引发 EOFError

# File ruby_3_3_0/io.rb, line 62
def read_nonblock(len, buf = nil, exception: true)
  Primitive.io_read_nonblock(len, buf, exception)
end
readbyte → integer 点击切换源代码

从流中读取并返回下一个字节(范围为 0..255);如果已到达流末尾,则引发 EOFError。请参阅 字节 IO

f = File.open('t.txt')
f.readbyte # => 70
f.close
f = File.open('t.rus')
f.readbyte # => 209
f.close

相关:IO#getbyte(不会引发 EOFError)。

static VALUE
rb_io_readbyte(VALUE io)
{
    VALUE c = rb_io_getbyte(io);

    if (NIL_P(c)) {
        rb_eof_error();
    }
    return c;
}
readchar → string 点击切换源代码

从流中读取并返回下一个 1 字符串;如果已到达流末尾,则引发 EOFError。参见 字符 IO

f = File.open('t.txt')
f.readchar     # => "F"
f.close
f = File.open('t.rus')
f.readchar.ord # => 1090
f.close

相关:IO#getc(不会引发 EOFError)。

static VALUE
rb_io_readchar(VALUE io)
{
    VALUE c = rb_io_getc(io);

    if (NIL_P(c)) {
        rb_eof_error();
    }
    return c;
}
readline(sep = $/, chomp: false) → string 点击切换源代码
readline(limit, chomp: false) → string
readline(sep, limit, chomp: false) → string

读取一行,与 IO#gets 相同,但如果已到达流末尾,则引发 EOFError

可选关键字参数 chomp 指定是否省略行分隔符。

# File ruby_3_3_0/io.rb, line 133
def readline(sep = $/, limit = nil, chomp: false)
  Primitive.io_readline(sep, limit, chomp)
end
readlines(sep = $/, chomp: false) → array 点击切换源代码
readlines(limit, chomp: false) → array
readlines(sep, limit, chomp: false) → array

读取并返回流中所有剩余的行;不修改 $_。参见 行 IO

如果没有给出参数,则返回由行分隔符 $/ 确定的行,或者如果不存在则返回 nil

f = File.new('t.txt')
f.readlines
# => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"]
f.readlines # => []
f.close

如果只给出字符串参数 sep,则返回由行分隔符 sep 确定的行,或者如果不存在则返回 nil;参见 行分隔符

f = File.new('t.txt')
f.readlines('li')
# => ["First li", "ne\nSecond li", "ne\n\nFourth li", "ne\nFifth li", "ne\n"]
f.close

sep 的两个特殊值将被认可。

f = File.new('t.txt')
# Get all into one string.
f.readlines(nil)
# => ["First line\nSecond line\n\nFourth line\nFifth line\n"]
# Get paragraphs (up to two line separators).
f.rewind
f.readlines('')
# => ["First line\nSecond line\n\n", "Fourth line\nFifth line\n"]
f.close

如果仅提供整数参数 limit,则限制每行的字节数;参见 行限制

f = File.new('t.txt')
f.readlines(8)
# => ["First li", "ne\n", "Second l", "ine\n", "\n", "Fourth l", "ine\n", "Fifth li", "ne\n"]
f.close

给出参数seplimit,结合两种行为

  • 返回由行分隔符 sep 确定的行。

  • 但返回的行中字节数不超过限制允许的字节数。

可选关键字参数 chomp 指定是否省略行分隔符。

f = File.new('t.txt')
f.readlines(chomp: true)
# => ["First line", "Second line", "", "Fourth line", "Fifth line"]
f.close
static VALUE
rb_io_readlines(int argc, VALUE *argv, VALUE io)
{
    struct getline_arg args;

    prepare_getline_args(argc, argv, &args, io);
    return io_readlines(&args, io);
}
readpartial(maxlen) → string 点击切换源代码
readpartial(maxlen, out_string) → out_string

从流中读取最多 maxlen 个字节;返回一个字符串(一个新字符串或给定的 out_string)。它的编码是

  • 如果给出 out_string,则为 out_string 的不变编码。

  • 否则为 ASCII-8BIT。

  • 如果可用,则包含流中的 maxlen 个字节。

  • 否则,如果可用,则包含所有可用字节。

  • 否则为空字符串。

如果只给出单个非负整数参数 maxlen,则返回一个新字符串

f = File.new('t.txt')
f.readpartial(20) # => "First line\nSecond l"
f.readpartial(20) # => "ine\n\nFourth line\n"
f.readpartial(20) # => "Fifth line\n"
f.readpartial(20) # Raises EOFError.
f.close

如果同时给出参数 maxlen 和字符串参数 out_string,则返回修改后的 out_string

f = File.new('t.txt')
s = 'foo'
f.readpartial(20, s) # => "First line\nSecond l"
s = 'bar'
f.readpartial(0, s)  # => ""
f.close

此方法对于管道、套接字或 tty 等流很有用。它只在没有数据立即可用时阻塞。这意味着它只在以下所有条件都为真时阻塞

  • 流中的字节缓冲区为空。

  • 流的内容为空。

  • 流未处于 EOF。

当阻塞时,该方法等待更多数据或流上的 EOF。

  • 如果读取到更多数据,该方法将返回数据。

  • 如果到达 EOF,该方法将引发 EOFError

当不阻塞时,该方法立即响应。

  • 如果存在缓冲区中的数据,则返回该数据。

  • 否则,如果存在流中的数据,则返回该数据。

  • 否则,如果流已到达 EOF,则引发 EOFError

注意,此方法类似于 sysread。区别在于

  • 如果字节缓冲区不为空,则从字节缓冲区读取,而不是“sysread for buffered IO (IOError)”。

  • 它不会导致 Errno::EWOULDBLOCK 和 Errno::EINTR。当 readpartial 通过 read 系统调用遇到 EWOULDBLOCK 和 EINTR 时,readpartial 会重试系统调用。

这意味着 readpartial 对非阻塞标志不敏感。它在 IO#sysread 导致 Errno::EWOULDBLOCK 的情况下阻塞,就好像 fd 是阻塞模式一样。

示例

#                        # Returned      Buffer Content    Pipe Content
r, w = IO.pipe           #
w << 'abc'               #               ""                "abc".
r.readpartial(4096)      # => "abc"      ""                ""
r.readpartial(4096)      # (Blocks because buffer and pipe are empty.)

#                        # Returned      Buffer Content    Pipe Content
r, w = IO.pipe           #
w << 'abc'               #               ""                "abc"
w.close                  #               ""                "abc" EOF
r.readpartial(4096)      # => "abc"      ""                 EOF
r.readpartial(4096)      # raises EOFError

#                        # Returned      Buffer Content    Pipe Content
r, w = IO.pipe           #
w << "abc\ndef\n"        #               ""                "abc\ndef\n"
r.gets                   # => "abc\n"    "def\n"           ""
w << "ghi\n"             #               "def\n"           "ghi\n"
r.readpartial(4096)      # => "def\n"    ""                "ghi\n"
r.readpartial(4096)      # => "ghi\n"    ""                ""
static VALUE
io_readpartial(int argc, VALUE *argv, VALUE io)
{
    VALUE ret;

    ret = io_getpartial(argc, argv, io, Qnil, 0);
    if (NIL_P(ret))
        rb_eof_error();
    return ret;
}
reopen(other_io) → self 点击切换源代码
reopen(path, mode = 'r', **opts) → self

将流与另一个流重新关联,该流可能是不同类的。此方法可用于将现有流重定向到新目标。

给定参数 other_io,与该流重新关联。

# Redirect $stdin from a file.
f = File.open('t.txt')
$stdin.reopen(f)
f.close

# Redirect $stdout to a file.
f = File.open('t.tmp', 'w')
$stdout.reopen(f)
f.close

给定参数 path,与该文件路径的新流重新关联。

$stdin.reopen('t.txt')
$stdout.reopen('t.tmp', 'w')

可选关键字参数opts指定

static VALUE
rb_io_reopen(int argc, VALUE *argv, VALUE file)
{
    VALUE fname, nmode, opt;
    int oflags;
    rb_io_t *fptr;

    if (rb_scan_args(argc, argv, "11:", &fname, &nmode, &opt) == 1) {
        VALUE tmp = rb_io_check_io(fname);
        if (!NIL_P(tmp)) {
            return io_reopen(file, tmp);
        }
    }

    FilePathValue(fname);
    rb_io_taint_check(file);
    fptr = RFILE(file)->fptr;
    if (!fptr) {
        fptr = RFILE(file)->fptr = ZALLOC(rb_io_t);
    }

    if (!NIL_P(nmode) || !NIL_P(opt)) {
        int fmode;
        struct rb_io_encoding convconfig;

        rb_io_extract_modeenc(&nmode, 0, opt, &oflags, &fmode, &convconfig);
        if (RUBY_IO_EXTERNAL_P(fptr) &&
            ((fptr->mode & FMODE_READWRITE) & (fmode & FMODE_READWRITE)) !=
            (fptr->mode & FMODE_READWRITE)) {
            rb_raise(rb_eArgError,
                     "%s can't change access mode from \"%s\" to \"%s\"",
                     PREP_STDIO_NAME(fptr), rb_io_fmode_modestr(fptr->mode),
                     rb_io_fmode_modestr(fmode));
        }
        fptr->mode = fmode;
        fptr->encs = convconfig;
    }
    else {
        oflags = rb_io_fmode_oflags(fptr->mode);
    }

    fptr->pathv = fname;
    if (fptr->fd < 0) {
        fptr->fd = rb_sysopen(fptr->pathv, oflags, 0666);
        fptr->stdio_file = 0;
        return file;
    }

    if (fptr->mode & FMODE_WRITABLE) {
        if (io_fflush(fptr) < 0)
            rb_sys_fail_on_write(fptr);
    }
    fptr->rbuf.off = fptr->rbuf.len = 0;

    if (fptr->stdio_file) {
        int e = rb_freopen(rb_str_encode_ospath(fptr->pathv),
                           rb_io_oflags_modestr(oflags),
                           fptr->stdio_file);
        if (e) rb_syserr_fail_path(e, fptr->pathv);
        fptr->fd = fileno(fptr->stdio_file);
        rb_fd_fix_cloexec(fptr->fd);
#ifdef USE_SETVBUF
        if (setvbuf(fptr->stdio_file, NULL, _IOFBF, 0) != 0)
            rb_warn("setvbuf() can't be honoured for %"PRIsVALUE, fptr->pathv);
#endif
        if (fptr->stdio_file == stderr) {
            if (setvbuf(fptr->stdio_file, NULL, _IONBF, BUFSIZ) != 0)
                rb_warn("setvbuf() can't be honoured for %"PRIsVALUE, fptr->pathv);
        }
        else if (fptr->stdio_file == stdout && isatty(fptr->fd)) {
            if (setvbuf(fptr->stdio_file, NULL, _IOLBF, BUFSIZ) != 0)
                rb_warn("setvbuf() can't be honoured for %"PRIsVALUE, fptr->pathv);
        }
    }
    else {
        int tmpfd = rb_sysopen(fptr->pathv, oflags, 0666);
        int err = 0;
        if (rb_cloexec_dup2(tmpfd, fptr->fd) < 0)
            err = errno;
        (void)close(tmpfd);
        if (err) {
            rb_syserr_fail_path(err, fptr->pathv);
        }
    }

    return file;
}
rewind → 0 点击切换源代码

将流重新定位到其开头,将位置和行号都设置为零;请参阅 位置行号

f = File.open('t.txt')
f.tell     # => 0
f.lineno   # => 0
f.gets     # => "First line\n"
f.tell     # => 12
f.lineno   # => 1
f.rewind   # => 0
f.tell     # => 0
f.lineno   # => 0
f.close

注意,此方法不能用于管道、tty 和套接字等流。

static VALUE
rb_io_rewind(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    if (io_seek(fptr, 0L, 0) < 0 && errno) rb_sys_fail_path(fptr->pathv);
    if (io == ARGF.current_file) {
        ARGF.lineno -= fptr->lineno;
    }
    fptr->lineno = 0;
    if (fptr->readconv) {
        clear_readconv(fptr);
    }

    return INT2FIX(0);
}
seek(offset, whence = IO::SEEK_SET) → 0 点击切换源代码

查找由整数 offset(请参阅 位置)和常量 whence 给定的位置,该常量是以下之一:

  • :CURIO::SEEK_CUR:将流重新定位到其当前位置加上给定的 offset

    f = File.open('t.txt')
    f.tell            # => 0
    f.seek(20, :CUR)  # => 0
    f.tell            # => 20
    f.seek(-10, :CUR) # => 0
    f.tell            # => 10
    f.close
    
  • :ENDIO::SEEK_END:将流重新定位到其末尾加上给定的 offset

    f = File.open('t.txt')
    f.tell            # => 0
    f.seek(0, :END)   # => 0  # Repositions to stream end.
    f.tell            # => 52
    f.seek(-20, :END) # => 0
    f.tell            # => 32
    f.seek(-40, :END) # => 0
    f.tell            # => 12
    f.close
    
  • :SETIO:SEEK_SET:将流重新定位到给定的 offset

    f = File.open('t.txt')
    f.tell            # => 0
    f.seek(20, :SET) # => 0
    f.tell           # => 20
    f.seek(40, :SET) # => 0
    f.tell           # => 40
    f.close
    

相关:IO#pos=IO#tell

static VALUE
rb_io_seek_m(int argc, VALUE *argv, VALUE io)
{
    VALUE offset, ptrname;
    int whence = SEEK_SET;

    if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) {
        whence = interpret_seek_whence(ptrname);
    }

    return rb_io_seek(io, offset, whence);
}
set_encoding(ext_enc) → self 点击切换源代码
set_encoding(ext_enc, int_enc, **enc_opts) → self
set_encoding('ext_enc:int_enc', **enc_opts) → self

请参阅 编码

如果给出参数 ext_enc,它必须是 Encoding 对象或包含编码名称的 String;它被指定为流的编码。

如果给出参数 int_enc,它必须是 Encoding 对象或包含编码名称的 String;它被指定为内部字符串的编码。

如果给出参数 'ext_enc:int_enc',它是一个包含两个以冒号分隔的编码名称的字符串;相应的 Encoding 对象被指定为流的外部和内部编码。

如果字符串的外部编码为二进制/ASCII-8BIT,则字符串的内部编码被设置为 nil,因为不需要进行转码。

可选的关键字参数 enc_opts 指定 编码选项

static VALUE
rb_io_set_encoding(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;
    VALUE v1, v2, opt;

    if (!RB_TYPE_P(io, T_FILE)) {
        return forward(io, id_set_encoding, argc, argv);
    }

    argc = rb_scan_args(argc, argv, "11:", &v1, &v2, &opt);
    GetOpenFile(io, fptr);
    io_encoding_set(fptr, v1, v2, opt);
    return io;
}
set_encoding_by_bom → encoding or nil 点击切换源代码

如果流以 BOM (字节顺序标记) 开头,则会消耗 BOM 并相应地设置外部编码;如果找到,则返回结果编码,否则返回 nil

File.write('t.tmp', "\u{FEFF}abc")
io = File.open('t.tmp', 'rb')
io.set_encoding_by_bom # => #<Encoding:UTF-8>
io.close

File.write('t.tmp', 'abc')
io = File.open('t.tmp', 'rb')
io.set_encoding_by_bom # => nil
io.close

如果流不是 binmode 或其编码已设置,则会引发异常。

static VALUE
rb_io_set_encoding_by_bom(VALUE io)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    if (!(fptr->mode & FMODE_BINMODE)) {
        rb_raise(rb_eArgError, "ASCII incompatible encoding needs binmode");
    }
    if (fptr->encs.enc2) {
        rb_raise(rb_eArgError, "encoding conversion is set");
    }
    else if (fptr->encs.enc && fptr->encs.enc != rb_ascii8bit_encoding()) {
        rb_raise(rb_eArgError, "encoding is set to %s already",
                 rb_enc_name(fptr->encs.enc));
    }
    if (!io_set_encoding_by_bom(io)) return Qnil;
    return rb_enc_from_encoding(fptr->encs.enc);
}
stat → stat 点击切换源代码

返回 ios 的状态信息,作为 File::Stat 类型的对象。

f = File.new("testfile")
s = f.stat
"%o" % s.mode   #=> "100644"
s.blksize       #=> 4096
s.atime         #=> Wed Apr 09 08:53:54 CDT 2003
static VALUE
rb_io_stat(VALUE obj)
{
    rb_io_t *fptr;
    struct stat st;

    GetOpenFile(obj, fptr);
    if (fstat(fptr->fd, &st) == -1) {
        rb_sys_fail_path(fptr->pathv);
    }
    return rb_stat_new(&st);
}
sync → true or false 点击切换源代码

返回流的当前同步模式。当同步模式为 true 时,所有输出都会立即刷新到底层操作系统,并且不会被 Ruby 在内部缓冲。另请参见 fsync

f = File.open('t.tmp', 'w')
f.sync # => false
f.sync = true
f.sync # => true
f.close
static VALUE
rb_io_sync(VALUE io)
{
    rb_io_t *fptr;

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);
    return RBOOL(fptr->mode & FMODE_SYNC);
}
sync = boolean → boolean 点击切换源代码

将流的 sync mode 设置为给定值;返回给定值。

同步模式的值

  • true:所有输出都会立即刷新到底层操作系统,并且不会在内部缓冲。

  • false: 输出可能在内部被缓冲。

示例;

f = File.open('t.tmp', 'w')
f.sync # => false
f.sync = true
f.sync # => true
f.close

相关:IO#fsync.

static VALUE
rb_io_set_sync(VALUE io, VALUE sync)
{
    rb_io_t *fptr;

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);
    if (RTEST(sync)) {
        fptr->mode |= FMODE_SYNC;
    }
    else {
        fptr->mode &= ~FMODE_SYNC;
    }
    return sync;
}
sysread(maxlen) → string 点击切换源代码
sysread(maxlen, out_string) → string

行为类似于 IO#readpartial,但它使用底层系统函数。

此方法不应与其他流读取器方法一起使用。

static VALUE
rb_io_sysread(int argc, VALUE *argv, VALUE io)
{
    VALUE len, str;
    rb_io_t *fptr;
    long n, ilen;
    struct io_internal_read_struct iis;
    int shrinkable;

    rb_scan_args(argc, argv, "11", &len, &str);
    ilen = NUM2LONG(len);

    shrinkable = io_setstrbuf(&str, ilen);
    if (ilen == 0) return str;

    GetOpenFile(io, fptr);
    rb_io_check_byte_readable(fptr);

    if (READ_DATA_BUFFERED(fptr)) {
        rb_raise(rb_eIOError, "sysread for buffered IO");
    }

    rb_io_check_closed(fptr);

    io_setstrbuf(&str, ilen);
    iis.th = rb_thread_current();
    iis.fptr = fptr;
    iis.nonblock = 0;
    iis.fd = fptr->fd;
    iis.buf = RSTRING_PTR(str);
    iis.capa = ilen;
    iis.timeout = NULL;
    n = io_read_memory_locktmp(str, &iis);

    if (n < 0) {
        rb_sys_fail_path(fptr->pathv);
    }

    io_set_read_length(str, n, shrinkable);

    if (n == 0 && ilen > 0) {
        rb_eof_error();
    }

    return str;
}
sysseek(offset, whence = IO::SEEK_SET) → integer 点击切换源代码

行为类似于 IO#seek,但它

  • 使用底层系统函数。

  • 返回新的位置。

static VALUE
rb_io_sysseek(int argc, VALUE *argv, VALUE io)
{
    VALUE offset, ptrname;
    int whence = SEEK_SET;
    rb_io_t *fptr;
    rb_off_t pos;

    if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) {
        whence = interpret_seek_whence(ptrname);
    }
    pos = NUM2OFFT(offset);
    GetOpenFile(io, fptr);
    if ((fptr->mode & FMODE_READABLE) &&
        (READ_DATA_BUFFERED(fptr) || READ_CHAR_PENDING(fptr))) {
        rb_raise(rb_eIOError, "sysseek for buffered IO");
    }
    if ((fptr->mode & FMODE_WRITABLE) && fptr->wbuf.len) {
        rb_warn("sysseek for buffered IO");
    }
    errno = 0;
    pos = lseek(fptr->fd, pos, whence);
    if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv);

    return OFFT2NUM(pos);
}
syswrite(object) → integer 点击切换源代码

将给定的 object 写入 self,它必须以写入模式打开(参见模式);返回写入的字节数。如果 object 不是字符串,则通过方法 to_s 转换。

f = File.new('t.tmp', 'w')
f.syswrite('foo') # => 3
f.syswrite(30)    # => 2
f.syswrite(:foo)  # => 3
f.close

此方法不应与其他流写入器方法一起使用。

static VALUE
rb_io_syswrite(VALUE io, VALUE str)
{
    VALUE tmp;
    rb_io_t *fptr;
    long n, len;
    const char *ptr;

    if (!RB_TYPE_P(str, T_STRING))
        str = rb_obj_as_string(str);

    io = GetWriteIO(io);
    GetOpenFile(io, fptr);
    rb_io_check_writable(fptr);

    if (fptr->wbuf.len) {
        rb_warn("syswrite for buffered IO");
    }

    tmp = rb_str_tmp_frozen_acquire(str);
    RSTRING_GETMEM(tmp, ptr, len);
    n = rb_io_write_memory(fptr, ptr, len);
    if (n < 0) rb_sys_fail_path(fptr->pathv);
    rb_str_tmp_frozen_release(str, tmp);

    return LONG2FIX(n);
}
tell → integer 点击切换源代码

返回self中当前位置(以字节为单位)(参见 位置)。

f = File.open('t.txt')
f.tell # => 0
f.gets # => "First line\n"
f.tell # => 12
f.close

相关:IO#pos=IO#seek

static VALUE
rb_io_tell(VALUE io)
{
    rb_io_t *fptr;
    rb_off_t pos;

    GetOpenFile(io, fptr);
    pos = io_tell(fptr);
    if (pos < 0 && errno) rb_sys_fail_path(fptr->pathv);
    pos -= fptr->rbuf.len;
    return OFFT2NUM(pos);
}
也称为:pos
timeout → duration or nil 点击切换源代码

获取内部超时持续时间,如果未设置则为 nil。

VALUE
rb_io_timeout(VALUE self)
{
    rb_io_t *fptr = rb_io_get_fptr(self);

    return fptr->timeout;
}
timeout = duration → duration 点击切换源代码
timeout = nil → nil

将内部超时设置为指定的持续时间或 nil。超时适用于所有可能的阻塞操作。

当操作执行时间超过设置的超时时间时,将引发 IO::TimeoutError

这会影响以下方法(但不限于):getsputsreadwritewait_readablewait_writable。这也影响阻塞套接字操作,如 Socket#accept 和 Socket#connect。

某些操作,如 File#openIO#close,不受超时影响。写入操作期间的超时可能会使 IO 处于不一致状态,例如数据被部分写入。一般来说,超时是防止应用程序挂起在缓慢 I/O 操作上的最后手段,例如在 slowloris 攻击期间发生的那些操作。

VALUE
rb_io_set_timeout(VALUE self, VALUE timeout)
{
    // Validate it:
    if (RTEST(timeout)) {
        rb_time_interval(timeout);
    }

    rb_io_t *fptr = rb_io_get_fptr(self);

    fptr->timeout = timeout;

    return self;
}
to_i()

返回流的整数文件描述符。

$stdin.fileno             # => 0
$stdout.fileno            # => 1
$stderr.fileno            # => 2
File.open('t.txt').fileno # => 10
f.close
别名:fileno
to_io → self 点击切换源代码

返回 self

static VALUE
rb_io_to_io(VALUE io)
{
    return io;
}
to_path()

返回与 IO 关联的路径,如果与 IO 没有关联的路径,则返回 nil。不能保证该路径在文件系统中存在。

$stdin.path # => "<STDIN>"

File.open("testfile") {|f| f.path} # => "testfile"
别名:path
tty?()

如果流与终端设备 (tty) 相关联,则返回 true,否则返回 false

f = File.new('t.txt').isatty    #=> false
f.close
f = File.new('/dev/tty').isatty #=> true
f.close
别名:isatty
ungetbyte(integer) → nil 点击切换源代码
ungetbyte(string) → nil

将给定的数据推回(“左移”)到流的缓冲区,将数据放置在下一个要读取的位置;返回 nil。参见 字节 IO

注意

  • 对于无缓冲读取(例如 IO#sysread),调用该方法无效。

  • 在流上调用 rewind 会丢弃推回的数据。

当给出参数 integer 时,只使用它的低位字节

File.write('t.tmp', '012')
f = File.open('t.tmp')
f.ungetbyte(0x41)   # => nil
f.read              # => "A012"
f.rewind
f.ungetbyte(0x4243) # => nil
f.read              # => "C012"
f.close

当给出参数 string 时,使用所有字节

File.write('t.tmp', '012')
f = File.open('t.tmp')
f.ungetbyte('A')    # => nil
f.read              # => "A012"
f.rewind
f.ungetbyte('BCDE') # => nil
f.read              # => "BCDE012"
f.close
VALUE
rb_io_ungetbyte(VALUE io, VALUE b)
{
    rb_io_t *fptr;

    GetOpenFile(io, fptr);
    rb_io_check_byte_readable(fptr);
    switch (TYPE(b)) {
      case T_NIL:
        return Qnil;
      case T_FIXNUM:
      case T_BIGNUM: ;
        VALUE v = rb_int_modulo(b, INT2FIX(256));
        unsigned char c = NUM2INT(v) & 0xFF;
        b = rb_str_new((const char *)&c, 1);
        break;
      default:
        SafeStringValue(b);
    }
    io_ungetbyte(b, fptr);
    return Qnil;
}
ungetc(integer) → nil 点击切换源代码
ungetc(string) → nil

将给定的数据推回(“左移”)到流的缓冲区,将数据放置在下一个要读取的位置;返回 nil。参见 字符 IO

注意

  • 对于无缓冲读取(例如 IO#sysread),调用该方法无效。

  • 在流上调用 rewind 会丢弃推回的数据。

当给出参数 integer 时,将整数解释为字符

File.write('t.tmp', '012')
f = File.open('t.tmp')
f.ungetc(0x41)     # => nil
f.read             # => "A012"
f.rewind
f.ungetc(0x0442)   # => nil
f.getc.ord         # => 1090
f.close

当给出参数 string 时,使用所有字符

File.write('t.tmp', '012')
f = File.open('t.tmp')
f.ungetc('A')      # => nil
f.read      # => "A012"
f.rewind
f.ungetc("\u0442\u0435\u0441\u0442") # => nil
f.getc.ord      # => 1090
f.getc.ord      # => 1077
f.getc.ord      # => 1089
f.getc.ord      # => 1090
f.close
VALUE
rb_io_ungetc(VALUE io, VALUE c)
{
    rb_io_t *fptr;
    long len;

    GetOpenFile(io, fptr);
    rb_io_check_char_readable(fptr);
    if (FIXNUM_P(c)) {
        c = rb_enc_uint_chr(FIX2UINT(c), io_read_encoding(fptr));
    }
    else if (RB_BIGNUM_TYPE_P(c)) {
        c = rb_enc_uint_chr(NUM2UINT(c), io_read_encoding(fptr));
    }
    else {
        SafeStringValue(c);
    }
    if (NEED_READCONV(fptr)) {
        SET_BINARY_MODE(fptr);
        len = RSTRING_LEN(c);
#if SIZEOF_LONG > SIZEOF_INT
        if (len > INT_MAX)
            rb_raise(rb_eIOError, "ungetc failed");
#endif
        make_readconv(fptr, (int)len);
        if (fptr->cbuf.capa - fptr->cbuf.len < len)
            rb_raise(rb_eIOError, "ungetc failed");
        if (fptr->cbuf.off < len) {
            MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.capa-fptr->cbuf.len,
                    fptr->cbuf.ptr+fptr->cbuf.off,
                    char, fptr->cbuf.len);
            fptr->cbuf.off = fptr->cbuf.capa-fptr->cbuf.len;
        }
        fptr->cbuf.off -= (int)len;
        fptr->cbuf.len += (int)len;
        MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.off, RSTRING_PTR(c), char, len);
    }
    else {
        NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr);
        io_ungetbyte(c, fptr);
    }
    return Qnil;
}
wait(events, timeout) → 事件掩码,false 或 nil 点击切换源代码
wait(timeout = nil, mode = :read) → self,true 或 false

等待直到 IO 准备好进行指定的事件,并返回已准备好的事件子集,或者在超时时返回一个假值。

事件可以是 IO::READABLEIO::WRITABLEIO::PRIORITY 的位掩码。

当缓冲数据可用时立即返回事件掩码(真值)。

可选参数 mode:read:write:read_write 之一。

static VALUE
io_wait(int argc, VALUE *argv, VALUE io)
{
    VALUE timeout = Qundef;
    enum rb_io_event events = 0;
    int return_io = 0;

    // The documented signature for this method is actually incorrect.
    // A single timeout is allowed in any position, and multiple symbols can be given.
    // Whether this is intentional or not, I don't know, and as such I consider this to
    // be a legacy/slow path.
    if (argc != 2 || (RB_SYMBOL_P(argv[0]) || RB_SYMBOL_P(argv[1]))) {
        // We'd prefer to return the actual mask, but this form would return the io itself:
        return_io = 1;

        // Slow/messy path:
        for (int i = 0; i < argc; i += 1) {
            if (RB_SYMBOL_P(argv[i])) {
                events |= wait_mode_sym(argv[i]);
            }
            else if (UNDEF_P(timeout)) {
                rb_time_interval(timeout = argv[i]);
            }
            else {
                rb_raise(rb_eArgError, "timeout given more than once");
            }
        }

        if (UNDEF_P(timeout)) timeout = Qnil;

        if (events == 0) {
            events = RUBY_IO_READABLE;
        }
    }
    else /* argc == 2 and neither are symbols */ {
        // This is the fast path:
        events = io_event_from_value(argv[0]);
        timeout = argv[1];
    }

    if (events & RUBY_IO_READABLE) {
        rb_io_t *fptr = NULL;
        RB_IO_POINTER(io, fptr);

        if (rb_io_read_pending(fptr)) {
            // This was the original behaviour:
            if (return_io) return Qtrue;
            // New behaviour always returns an event mask:
            else return RB_INT2NUM(RUBY_IO_READABLE);
        }
    }

    return io_wait_event(io, events, timeout, return_io);
}
wait_priority → 真值或假值 点击切换源代码
wait_priority(timeout) → 真值或假值

等待直到 IO 具有优先级,并返回真值或在超时时返回假值。优先级数据使用 Socket::MSG_OOB 标志发送和接收,通常限于流。

static VALUE
io_wait_priority(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr = NULL;

    RB_IO_POINTER(io, fptr);
    rb_io_check_readable(fptr);

    if (rb_io_read_pending(fptr)) return Qtrue;

    rb_check_arity(argc, 0, 1);
    VALUE timeout = argc == 1 ? argv[0] : Qnil;

    return io_wait_event(io, RUBY_IO_PRIORITY, timeout, 1);
}
wait_readable → 真值或假值 点击切换源代码
wait_readable(timeout) → 真值或假值

等待直到 IO 可读,并返回真值,或者在超时时返回假值。当缓冲数据可用时立即返回真值。

static VALUE
io_wait_readable(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;

    RB_IO_POINTER(io, fptr);
    rb_io_check_readable(fptr);

    if (rb_io_read_pending(fptr)) return Qtrue;

    rb_check_arity(argc, 0, 1);
    VALUE timeout = (argc == 1 ? argv[0] : Qnil);

    return io_wait_event(io, RUBY_IO_READABLE, timeout, 1);
}
wait_writable → 真值或假值 点击切换源代码
wait_writable(timeout) → 真值或假值

等待直到 IO 可写并返回真值,或在超时时返回假值。

static VALUE
io_wait_writable(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;

    RB_IO_POINTER(io, fptr);
    rb_io_check_writable(fptr);

    rb_check_arity(argc, 0, 1);
    VALUE timeout = (argc == 1 ? argv[0] : Qnil);

    return io_wait_event(io, RUBY_IO_WRITABLE, timeout, 1);
}
write(*objects) → 整数 点击切换源代码

将给定的每个 objects 写入 self,它必须以写入模式打开(参见 访问模式);返回写入的总字节数;每个不是字符串的 objects 通过方法 to_s 转换。

$stdout.write('Hello', ', ', 'World!', "\n") # => 14
$stdout.write('foo', :bar, 2, "\n")          # => 8

输出

Hello, World!
foobar2

相关:IO#read.

static VALUE
io_write_m(int argc, VALUE *argv, VALUE io)
{
    if (argc != 1) {
        return io_writev(argc, argv, io);
    }
    else {
        VALUE str = argv[0];
        return io_write(io, str, 0);
    }
}
write_nonblock(string) → 整数 点击切换源代码
write_nonblock(string [, options]) → 整数

在为底层文件描述符设置 O_NONBLOCK 后,使用 write(2) 系统调用将给定字符串写入 ios

它返回写入的字节数。

write_nonblock 只是调用 write(2) 系统调用。它会导致 write(2) 系统调用引起的所有错误:Errno::EWOULDBLOCK、Errno::EINTR 等。结果也可能小于 string.length(部分写入)。调用者应该注意这些错误和部分写入。

如果异常是 Errno::EWOULDBLOCK 或 Errno::EAGAIN,它将由 IO::WaitWritable 扩展。因此,IO::WaitWritable 可用于拯救这些异常以重试 write_nonblock。

# Creates a pipe.
r, w = IO.pipe

# write_nonblock writes only 65536 bytes and return 65536.
# (The pipe size is 65536 bytes on this environment.)
s = "a" * 100000
p w.write_nonblock(s)     #=> 65536

# write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN).
p w.write_nonblock("b")   # Resource temporarily unavailable (Errno::EAGAIN)

如果写入缓冲区不为空,它将首先被刷新。

write_nonblock 抛出 IO::WaitWritable 类型的异常时,write_nonblock 应该在 io 可写之前不要被调用,以避免繁忙循环。这可以通过以下方式完成。

begin
  result = io.write_nonblock(string)
rescue IO::WaitWritable, Errno::EINTR
  IO.select(nil, [io])
  retry
end

请注意,这不能保证写入字符串中的所有数据。写入的长度作为结果报告,应稍后检查。

在某些平台(如 Windows)上,根据 IO 对象的类型,write_nonblock 不受支持。在这种情况下,write_nonblock 会抛出 Errno::EBADF

通过将关键字参数 exception 指定为 false,您可以指示 write_nonblock 不应该抛出 IO::WaitWritable 异常,而是返回符号 :wait_writable

# File ruby_3_3_0/io.rb, line 120
def write_nonblock(buf, exception: true)
  Primitive.io_write_nonblock(buf, exception)
end