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 流一样,它具有

  • 一个位置,该位置确定下一次读取或写入将发生在流中的位置;请参阅 位置

  • 行号,这是一个特殊的、面向行的“位置”(与上面提到的位置不同);请参阅 行号

扩展 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:流模式。

  • :flagsInteger 文件打开标志;如果还给定了 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 的 Encoding::Converter 实例。

相关方法

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

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

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

  • 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 类支持面向行的 输入输出

行输入

IO 类支持面向行的输入,适用于 文件IO 流

文件行输入

您可以使用以下方法从文件中读取行

  • IO.foreach:读取每一行并将其传递给给定的块。

  • IO.readlines:读取并返回数组中的所有行。

对于每个这些方法

  • 您可以指定 打开选项

  • 行解析取决于有效的行分隔符;请参阅 行分隔符

  • 返回的每行的长度取决于有效的行限制;请参阅 行限制

流行输入

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

对于每个这些方法

  • 读取可能从行的中间开始,具体取决于流的位置;请参阅 位置

  • 行解析取决于有效的行分隔符;请参阅 行分隔符

  • 返回的每行的长度取决于有效的行限制;请参阅 行限制

行分隔符

每个 行输入方法 都使用一个行分隔符:确定什么是行的字符串;它有时称为输入记录分隔符

默认行分隔符取自全局变量 $/,其初始值为 "\n"

通常,接下来要读取的行是从当前位置到下一个行分隔符之间的所有数据(但请参阅特殊行分隔符值

f = File.new('t.txt')
# Method gets with no sep argument returns the next line, according to $/.
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

您可以通过传递参数 sep 来使用不同的行分隔符

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

或者通过设置全局变量 $/

f = File.new('t.txt')
$/ = 'l'
f.gets # => "First l"
f.gets # => "ine\nSecond l"
f.gets # => "ine\n\nFourth l"
f.close
特殊行分隔符值

每个行输入方法都接受参数 sep 的两个特殊值

  • 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
    
行限制

每个行输入方法都使用一个整数行限制,该限制限制了可能返回的字节数。(不会拆分多字节字符,因此返回的行可能略长于限制)。

默认限制值为 -1;任何负限制值都表示没有限制。

如果没有限制,则仅由 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 确定的下一行。

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

示例

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

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

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

新流的初始行号为零(位置也为零);方法 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 流

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

  • IO#readchar:与 getc 类似,但在流末尾引发异常。

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

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

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

字节 IO

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

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

  • 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:返回为给定的 *n* 从 self 读取的全部剩余或下一个 *n* 字节。

  • read_nonblock:以非阻塞模式为给定的 *n* 从 self 读取的下一个 *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 的 close-on-exec 标志。

  • 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 或 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 单击以切换源

从文件描述符创建并返回新的 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;
    VALUE opt;

    rb_scan_args(argc, argv, "11:", &fnum, &vmode, &opt);
    return io_initialize(io, fnum, vmode, opt);
}
open(fd, mode = 'r', **opts) → io 单击以切换源
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] 单击以切换源
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 是一个数组,其第一个元素是包含 2 个元素的字符串数组,其余元素(如果有)是字符串

  • 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 或 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) → array

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

当从 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) → array 或 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

特别是,非阻塞方法和 IO.select 的组合对于 IO 类对象(如 OpenSSL::SSL::SSLSocket)是首选。它具有 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.select 通知可写性后,IO#write(两个或多个字节) 可能会阻塞。需要 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 单击以切换源

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

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

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 {
        StringValue(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 或 nil 单击以切换源

尝试通过方法 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 单击以切换源

打开流,将给定的 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 单击以切换源

将给定的 object 写入到 self,后者必须为写入而打开(请参阅 访问模式);返回 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 单击以切换源

调用 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 对象不是错误。它只会返回 nil。

示例

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 点击以切换源代码

设置一个执行时关闭标志。

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 默认设置所有文件描述符的执行时关闭标志。因此,您无需自行设置。此外,如果另一个线程使用 fork() 和 exec()(例如,通过 system() 方法),取消设置执行时关闭标志可能会导致文件描述符泄漏。如果确实需要将文件描述符继承给子进程,请使用 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 点击以切换源代码

如果流将在执行时关闭,则返回 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 -> 枚举器 点击以切换源代码

使用从流中读取的每个剩余行调用该块;返回 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,则结合这两种行为(请参阅 行分隔符和行限制)。

可选的关键字参数 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 → 枚举器

使用流中的每个字节 (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 → 枚举器

使用流中的每个字符调用给定的块;返回 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 → 枚举器

使用流中的每个代码点调用给定的块;返回 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,则结合这两种行为(请参阅 行分隔符和行限制)。

可选的关键字参数 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 → 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_io_blocking_region(fptr, nogvl_fdatasync, fptr) == 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_io_blocking_region(fptr, nogvl_fsync, fptr))
        rb_sys_fail_path(fptr->pathv);

    return INT2FIX(0);
}
getbyte → integer 或 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 → character 或 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) → string 或 nil 点击以切换源代码
gets(limit, chomp: false) → string 或 nil
gets(sep, limit, chomp: false) → string 或 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,则结合这两种行为(请参阅 行分隔符和行限制)。

可选的关键字参数 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 → string 点击以切换源代码

返回 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 → 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;
    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.io = 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",而不是输出记录分隔符 ($\)。

每个对象的处理方式

  • 字符串:写入字符串。

  • 既不是字符串也不是数组:写入 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;
    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.io = 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_io_blocking_region_wait(fptr, internal_pwrite_func, &arg, RUBY_IO_WRITABLE);
    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, 或 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

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

如果存在可选的 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_4_1/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_4_1/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,则结合这两种行为(请参阅 行分隔符和行限制)。

可选的关键字参数 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。区别在于

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

  • 它不会导致 Errno::EWOULDBLOCK 和 Errno::EINTR。当 readpartial 通过读取系统调用遇到 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 给定的位置,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 点击以切换源代码

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

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 点击以切换源代码

将流的同步模式设置为给定值;返回给定值。

同步模式的值

  • 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 操作上的最后手段,例如在慢速攻击期间发生的 I/O 操作。

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:
        StringValue(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 {
        StringValue(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) → event mask, 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 → truthy 或 falsy 点击切换源码
wait_priority(timeout) → truthy 或 falsy

等待 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 → truthy 或 falsy 点击切换源码
wait_readable(timeout) → truthy 或 falsy

等待 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 → truthy 或 falsy 点击切换源码
wait_writable(timeout) → truthy 或 falsy

等待 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) → integer 点击切换源码

将给定的每个 objects 写入 selfself 必须为写入而打开(请参阅 访问模式);返回写入的总字节数;每个不是字符串的 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) → integer 点击切换源码
write_nonblock(string [, options]) → integer

在为底层文件描述符设置 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 类型的异常时,在 io 变为可写之前,不应调用 write_nonblock,以避免忙循环。 可以按如下方式完成此操作。

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_4_1/io.rb, line 120
def write_nonblock(buf, exception: true)
  Primitive.io_write_nonblock(buf, exception)
end