类 IO
IO 类的一个实例(通常称为流)表示底层操作系统中的输入/输出流。IO 类是 Ruby 中输入和输出的基础。
类 File
是 Ruby 核心类中唯一一个继承自 IO 的类。Ruby 标准库中的一些类也继承自 IO;这些类包括 TCPSocket 和 UDPSocket。
全局常量 ARGF
(也可以访问为 $<
)提供了一个类似 IO 的流,允许访问 ARGV 中找到的所有文件路径(如果 ARGV 为空,则在 STDIN 中找到)。ARGF
本身不是 IO 的子类。
类 StringIO 提供了一个类似 IO 的流,用于处理 String
。StringIO 本身不是 IO 的子类。
基于 IO 的重要对象包括
-
$stdin。
-
$stdout。
-
$stderr。
-
类
File
的实例。
可以使用以下方法创建 IO 实例
-
IO.new
:为给定的整数文件描述符返回一个新的 IO 对象。 -
IO.open
:将一个新的 IO 对象传递给给定的块。 -
IO.popen
:返回一个新的 IO 对象,该对象连接到新启动的子进程的 $stdin 和 $stdout。 -
Kernel#open
:返回一个新的 IO 对象,该对象连接到给定的源:流、文件或子进程。
与 File
流类似,IO 流具有
与其他 IO 流一样,它具有
-
一个位置,它决定了在流中下一个读或写操作发生的位置;参见 Position。
-
一个行号,它是一个特殊的、面向行的“位置”(不同于上面提到的位置);参见 Line Number。
扩展 io/console
¶ ↑
扩展 io/console
提供了大量与控制台交互的方法;需要它会为 IO 类添加大量方法。
示例文件¶ ↑
这里许多示例使用这些变量
# English text with newlines. text = <<~EOT First line Second line Fourth line Fifth line EOT # Russian text. russian = "\u{442 435 441 442}" # => "тест" # Binary data. data = "\u9990\u9991\u9992\u9993\u9994" # Text file. File.write('t.txt', text) # File with Russian text. File.write('t.rus', russian) # File with binary data. f = File.new('t.dat', 'wb:UTF-16') f.write(data) f.close
打开选项¶ ↑
许多 IO 方法接受可选的关键字参数,这些参数决定了如何打开一个新的流
-
:mode
: 流模式。 -
:flags
:Integer
文件打开标志;如果也给出了mode
,则两者进行按位或运算。 -
:external_encoding
: 流的外部编码。 -
:internal_encoding
: 流的内部编码。'-'
是默认内部编码的同义词。如果值为nil
,则不进行转换。 -
:encoding
: 指定外部和内部编码为'extern:intern'
。 -
:textmode
: 如果为真值,则指定模式为纯文本,否则为二进制。 -
:binmode
: 如果为真值,则指定模式为二进制,否则为纯文本。 -
:autoclose
: 如果为真值,则指定当流关闭时fd
将关闭;否则它保持打开状态。
还提供 String#encode
中提供的选项,这些选项可以控制外部和内部编码之间的转换。
基本 IO¶ ↑
可以使用这些方法执行基本的流 IO,这些方法通常对多字节字符串进行操作
位置¶ ↑
一个 IO 流有一个非负整数位置,它是下一个读或写操作将要发生的字节偏移量。一个新的流的位置为零(行号也为零);方法 rewind
将位置(和行号)重置为零。
相关方法
-
IO#tell
(别名为#pos
): 返回流中当前位置(以字节为单位)。 -
IO#pos=
: 将流的位置设置为给定的整数new_position
(以字节为单位)。 -
IO#seek
: 将流的位置设置为给定的整数offset
(以字节为单位),相对于给定的位置whence
(指示开头、结尾或当前位置)。 -
IO#rewind
: 将流定位到开头(并重置行号)。
打开和关闭流¶ ↑
新的 IO 流可以打开以供读取、打开以供写入,或两者兼而有之。
当被垃圾收集器回收时,流会自动关闭。
尝试在关闭的流上进行读写操作会导致异常。
相关方法
-
IO#close
: 关闭流以供读取和写入。 -
IO#close_read
: 关闭流以供读取。 -
IO#close_write
: 关闭流以供写入。 -
IO#closed?
: 返回流是否已关闭。
流结束¶ ↑
您可以查询流是否位于其末尾
-
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#each_line
: 读取每个剩余行,并将它们传递给给定的块。 -
IO#gets
: 返回下一行。 -
IO#readline
: 与gets
相似,但在流结束时引发异常。 -
IO#readlines
: 在数组中返回所有剩余行。
每个读取器方法都接受
对于每个读取器方法,读取可能从行中间开始,具体取决于流的位置;请参阅 位置
f = File.new('t.txt') f.pos = 27 f.each_line {|line| p line } f.close
输出
"rth line\n" "Fifth line\n"
您可以使用以下方法逐行写入 IO 流
-
IO#puts
: 将对象写入流。
行分隔符¶ ↑
每个方法都使用行分隔符,它是用于分隔行的字符串
默认的行分隔符由全局变量$/
给出,其默认值为"\n"
。要读取的下一行是当前位置到下一个行分隔符的所有数据
f = File.new('t.txt') f.gets # => "First line\n" f.gets # => "Second line\n" f.gets # => "\n" f.gets # => "Fourth line\n" f.gets # => "Fifth line\n" f.close
您可以指定不同的行分隔符
f = File.new('t.txt') f.gets('l') # => "First l" f.gets('li') # => "ine\nSecond li" f.gets('lin') # => "ne\n\nFourth lin" f.gets # => "e\n" f.close
有两个特殊的行分隔符
-
nil
:整个流被读入一个字符串f = File.new('t.txt') f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n" f.close
-
''
(空字符串):读取下一段(段落由两个连续的行分隔符分隔)f = File.new('t.txt') f.gets('') # => "First line\nSecond line\n\n" f.gets('') # => "Fourth line\nFifth line\n" f.close
行限制¶ ↑
这些方法中的每一个都使用一个行限制,它指定返回的字节数可能不会(比给定的limit
)长很多;
多字节字符不会被拆分,因此一行可能略长于给定的限制。
如果没有给出limit
,则行仅由sep
确定。
# Text with 1-byte characters. File.open('t.txt') {|f| f.gets(1) } # => "F" File.open('t.txt') {|f| f.gets(2) } # => "Fi" File.open('t.txt') {|f| f.gets(3) } # => "Fir" File.open('t.txt') {|f| f.gets(4) } # => "Firs" # No more than one line. File.open('t.txt') {|f| f.gets(10) } # => "First line" File.open('t.txt') {|f| f.gets(11) } # => "First line\n" File.open('t.txt') {|f| f.gets(12) } # => "First line\n" # Text with 2-byte characters, which will not be split. File.open('t.rus') {|f| f.gets(1).size } # => 1 File.open('t.rus') {|f| f.gets(2).size } # => 1 File.open('t.rus') {|f| f.gets(3).size } # => 2 File.open('t.rus') {|f| f.gets(4).size } # => 2
行分隔符和行限制¶ ↑
给出参数sep
和limit
,结合两种行为
-
返回由行分隔符
sep
确定的下一行。 -
但返回的字节数不超过限制允许的字节数。
示例
File.open('t.txt') {|f| f.gets('li', 20) } # => "First li" File.open('t.txt') {|f| f.gets('li', 2) } # => "Fi"
行号¶ ↑
可读的 IO 流有一个非负整数行号。
相关方法
-
IO#lineno
: 返回行号。 -
IO#lineno=
: 重置并返回行号。
除非通过调用方法IO#lineno=
修改,否则行号是根据给定的行分隔符sep
,由某些面向行的读取方法读取的行数
-
IO.foreach
: 在每次调用块时递增行号。 -
IO#each_line
: 在每次调用块时递增行号。 -
IO#gets
: 递增行号。 -
IO#readline
: 递增行号。 -
IO#readlines
: 为读取的每一行递增行号。
新的流最初具有行号零(和位置零);方法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#rewind
和IO#seek
不会影响$.
f = File.new('t.txt') f.readlines # => ["First line\n", "Second line\n", "\n", "Fourth line\n", "Fifth line\n"] $. # => 5 f.rewind f.seek(0, :SET) $. # => 5 f.close
字符 IO¶ ↑
您可以使用以下方法逐个字符处理 IO 流
-
IO#getc
: 从流中读取并返回下一个字符。 -
IO#readchar
: 与getc
相似,但在流结束时会引发异常。 -
IO#ungetc
: 将字符或整数推回(“取消移位”)到流中。 -
IO#putc
: 将字符写入流。 -
IO#each_char
: 读取流中剩余的每个字符,并将字符传递给给定的块。
字节 IO¶ ↑
您可以使用以下方法逐字节处理 IO 流
-
IO#getbyte
: 返回下一个 8 位字节,作为 0..255 范围内的整数。 -
IO#readbyte
: 与getbyte
相似,但在流结束时会引发异常。 -
IO#ungetbyte
: 将字节推回(“取消移位”)到流中。 -
IO#each_byte
: 读取流中剩余的每个字节,并将字节传递给给定的块。
码点 IO¶ ↑
您可以逐个码点处理 IO 流
-
IO#each_codepoint
: 读取剩余的每个码点,并将它传递给给定的块。
这里的内容¶ ↑
首先,看看其他地方。IO 类
-
继承自 Object 类。
-
包含 Enumerable 模块,它提供了数十种额外的 方法。
这里,IO 类提供了对以下操作有用的方法:
创建¶ ↑
-
::open
: 创建一个新的 IO 对象。 -
::pipe
: 创建一对连接的读写 IO 对象。 -
::popen
: 创建一个 IO 对象来与子进程交互。 -
::select
: 选择哪些给定的 IO 实例已准备好进行读取、写入或有待处理的异常。
读取¶ ↑
-
::binread
: 返回一个二进制字符串,其中包含给定文件中的所有字节或部分字节。 -
::read
: 返回一个字符串,其中包含给定文件中的所有字节或部分字节。 -
::readlines
: 返回一个字符串数组,这些字符串是给定文件中的行。 -
getbyte
: 返回从self
读取的下一个 8 位字节,作为一个整数。 -
getc
: 返回从self
读取的下一个字符,作为一个字符串。 -
gets
: 返回从self
读取的行。 -
pread
: 返回从self
读取的所有字节或接下来的 n 个字节,不更新接收器的偏移量。 -
read
: 返回从self
读取的所有剩余字节或接下来的 n 个字节,对于给定的 n。 -
read_nonblock
: 从self
读取接下来的 n 个字节,对于给定的 n,在非阻塞模式下。 -
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=
: 设置self
中的字节偏移量。 -
reopen
: 将self
与新的或现有的 IO 流重新关联。 -
rewind
: 将self
定位到输入的开头。 -
seek
: 设置self
相对于给定位置的偏移量。
迭代¶ ↑
-
::foreach
: 将给定文件的每一行都生成到块中。 -
each_byte
: 使用self
中的每个连续字节作为整数调用给定的块。 -
each_char
: 以字符串形式将self
中的每个连续字符传递给给定的块。 -
each_codepoint
: 以整数形式将self
中的每个连续码点传递给给定的块。
设置¶ ↑
-
autoclose=
: 设置self
是否自动关闭。 -
binmode
: 将self
设置为二进制模式。 -
close
: 关闭self
。 -
close_on_exec=
: 设置关闭执行标志。 -
close_read
: 关闭self
的读取操作。 -
close_write
: 关闭self
的写入操作。 -
set_encoding
: 设置self
的编码。 -
set_encoding_by_bom
: 根据其 Unicode 字节顺序标记设置self
的编码。 -
sync=
: 将同步模式设置为给定值。
查询¶ ↑
-
autoclose?
: 返回self
是否自动关闭。 -
binmode?
: 返回self
是否处于二进制模式。 -
close_on_exec?
: 返回self
的关闭执行标志。 -
closed?
: 返回self
是否已关闭。 -
external_encoding
: 返回self
的外部编码对象。 -
internal_encoding
: 返回self
的内部编码对象。 -
stat
: 返回包含self
状态信息的File::Stat
对象。 -
sync
: 返回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
的字符串表示形式。
常量
公共类方法
行为类似于 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); }
从给定的 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
如果只给出参数 src
和 dst
,则会复制整个源流
# 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); }
是 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; }
使用从流中读取的每一行调用块。
当从 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"
给出参数sep
和limit
时,会解析由给定行分隔符和给定行长度限制决定的行(参见 行分隔符和行限制)。
可选关键字参数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); }
从文件描述符创建并返回一个新的 IO 对象(文件流)。
IO.new 可能对与低级库的交互有用。对于更高级别的交互,使用 File.open
创建文件流可能更简单。
参数fd
必须是一个有效的文件描述符(整数)。
path = 't.tmp' fd = IO.sysopen(path) # => 3 IO.new(fd) # => #<IO:fd 3>
新的 IO 对象不继承编码(因为整数文件描述符没有编码)。
fd = IO.sysopen('t.rus', 'rb') io = IO.new(fd) io.external_encoding # => #<Encoding:UTF-8> # Not ASCII-8BIT.
可选参数mode
(默认为 'r')必须指定一个有效的模式;参见 访问模式。
IO.new(fd, 'w') # => #<IO:fd 3> IO.new(fd, File::WRONLY) # => #<IO:fd 3>
可选关键字参数opts
指定
示例
IO.new(fd, internal_encoding: nil) # => #<IO:fd 3> IO.new(fd, autoclose: true) # => #<IO:fd 3>
static VALUE rb_io_initialize(int argc, VALUE *argv, VALUE io) { VALUE fnum, vmode; rb_io_t *fp; int fd, fmode, oflags = O_RDONLY; struct rb_io_encoding convconfig; VALUE opt; #if defined(HAVE_FCNTL) && defined(F_GETFL) int ofmode; #else struct stat st; #endif argc = rb_scan_args(argc, argv, "11:", &fnum, &vmode, &opt); rb_io_extract_modeenc(&vmode, 0, opt, &oflags, &fmode, &convconfig); fd = NUM2INT(fnum); if (rb_reserved_fd_p(fd)) { rb_raise(rb_eArgError, "The given fd is not accessible because RubyVM reserves it"); } #if defined(HAVE_FCNTL) && defined(F_GETFL) oflags = fcntl(fd, F_GETFL); if (oflags == -1) rb_sys_fail(0); #else if (fstat(fd, &st) < 0) rb_sys_fail(0); #endif rb_update_max_fd(fd); #if defined(HAVE_FCNTL) && defined(F_GETFL) ofmode = rb_io_oflags_fmode(oflags); if (NIL_P(vmode)) { fmode = ofmode; } else if ((~ofmode & fmode) & FMODE_READWRITE) { VALUE error = INT2FIX(EINVAL); rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError)); } #endif VALUE path = Qnil; if (!NIL_P(opt)) { if (rb_hash_aref(opt, sym_autoclose) == Qfalse) { fmode |= FMODE_EXTERNAL; } path = rb_hash_aref(opt, RB_ID2SYM(idPath)); if (!NIL_P(path)) { StringValue(path); path = rb_str_new_frozen(path); } } MakeOpenFile(io, fp); fp->self = io; fp->fd = fd; fp->mode = fmode; fp->encs = convconfig; fp->pathv = path; fp->timeout = Qnil; clear_codeconv(fp); io_check_tty(fp); if (fileno(stdin) == fd) fp->stdio_file = stdin; else if (fileno(stdout) == fd) fp->stdio_file = stdout; else if (fileno(stderr) == fd) fp->stdio_file = stderr; if (fmode & FMODE_SETENC_BY_BOM) io_set_encoding_by_bom(io); return io; }
使用给定参数通过 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; }
创建一对相互连接的管道端点,read_io
和 write_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; }
将给定的命令 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
指定
-
打开选项.
-
编码选项.
-
有关
Kernel#spawn
的选项。
分叉进程
当参数 cmd
是 1 个字符的字符串 '-'
时,会导致进程分叉。
IO.popen('-') do |pipe| if pipe $stderr.puts "In parent, child pid is #{pipe.pid}\n" else $stderr.puts "In child, pid is #{$$}\n" end end
输出
In parent, child pid is 26253 In child, pid is 26253
请注意,这并非所有平台都支持。
Shell 子进程
当参数 cmd
为单个字符串(但不是 '-'
)时,名为 cmd
的程序将作为 shell 命令运行。
IO.popen('uname') do |pipe| pipe.readlines end
输出
["Linux\n"]
另一个示例
IO.popen('/bin/sh', 'r+') do |pipe| pipe.puts('ls') pipe.close_write $stderr.puts pipe.readlines.size end
输出
213
程序子进程
当参数 cmd
为字符串数组时,名为 cmd[0]
的程序将使用 cmd
中的所有元素作为其参数运行。
IO.popen(['du', '..', '.']) do |pipe| $stderr.puts pipe.readlines.size end
输出
1111
带有 argv0
的程序子进程
当参数 cmd
为一个数组,其第一个元素是一个包含两个元素的字符串数组,而其余元素(如果有)为字符串时
-
cmd[0][0]
(嵌套数组中的第一个字符串)是运行的程序的名称。 -
cmd[0][1]
(嵌套数组中的第二个字符串)被设置为程序的argv[0]
。 -
cmd[1..-1]
(外部数组中的字符串)是程序的参数。
示例(将 $0
设置为 'foo')
IO.popen([['/bin/sh', 'foo'], '-c', 'echo $0']).read # => "foo\n"
一些特殊示例
# Set IO encoding. IO.popen("nkf -e filename", :external_encoding=>"EUC-JP") {|nkf_io| euc_jp_string = nkf_io.read } # Merge standard output and standard error using Kernel#spawn option. See Kernel#spawn. IO.popen(["ls", "/", :err=>[:child, :out]]) do |io| ls_result_with_error = io.read end # Use mixture of spawn options and IO options. IO.popen(["ls", "/"], :err=>[:child, :out]) do |io| ls_result_with_error = io.read end f = IO.popen("uname") p f.readlines f.close puts "Parent is #{Process.pid}" IO.popen("date") {|f| puts f.gets } IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f.inspect}"} p $? IO.popen(%w"sed -e s|^|<foo>| -e s&$&;zot;&", "r+") {|f| f.puts "bar"; f.close_write; puts f.gets }
输出(来自上一节)
["Linux\n"] Parent is 21346 Thu Jan 15 22:41:19 JST 2009 21346 is here, f is #<IO:fd 3> 21352 is here, f is nil #<Process::Status: pid 21352 exit 0> <foo>bar;zot;
引发 IO.pipe
和 Kernel.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); }
打开流,读取并返回部分或全部内容,然后关闭流;如果未读取任何字节,则返回 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"
给出参数 length
和 offset
时,如果可用,则从给定 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); }
返回从流中读取的所有行的数组。
当从 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"]
给出参数sep
和limit
时,会解析由给定行分隔符和给定行长度限制决定的行(参见 行分隔符和行限制)。
可选关键字参数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(2),它监控多个文件描述符,等待一个或多个文件描述符准备好进行某种类型的 I/O 操作。
并非所有平台都已实现。
每个参数 read_ios
、write_ios
和 error_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_nonblock
、write_nonblock
等)之后调用它。这些方法会引发一个异常,该异常由 IO::WaitReadable
或 IO::WaitWritable
扩展。这些模块通知调用者应如何使用 IO.select 等待。如果引发了 IO::WaitReadable
,则调用者应等待读取。如果引发了 IO::WaitWritable
,则调用者应等待写入。
因此,可以使用 read_nonblock
和 IO.select 模拟阻塞读取 (readpartial
),如下所示
begin result = io_like.read_nonblock(maxlen) rescue IO::WaitReadable IO.select([io_like]) retry rescue IO::WaitWritable IO.select(nil, [io_like]) retry end
特别是,对于像 OpenSSL::SSL::SSLSocket 这样的 IO
类对象,非阻塞方法和 IO.select 的组合是首选。它具有 to_io
方法来返回底层的 IO
对象。 IO.select
调用 to_io
来获取要等待的文件描述符。
这意味着 IO.select 通知的可读性并不意味着从 OpenSSL::SSL::SSLSocket 对象中可读。
最可能的情况是 OpenSSL::SSL::SSLSocket 缓冲了一些数据。IO.select 无法看到缓冲区。因此,当 OpenSSL::SSL::SSLSocket#readpartial 不阻塞时,IO.select 可能会阻塞。
但是,还存在一些更复杂的情况。
SSL 是一种协议,它是一系列记录。记录由多个字节组成。因此,SSL 的远程端发送部分记录,IO.select
通知可读性,但 OpenSSL::SSL::SSLSocket 无法解密一个字节,并且 OpenSSL::SSL::SSLSocket#readpartial 将阻塞。
此外,远程端可以请求 SSL 重新协商,这将强制本地 SSL 引擎写入一些数据。这意味着 OpenSSL::SSL::SSLSocket#readpartial 可能会调用 write
系统调用,并且它可能会阻塞。在这种情况下,OpenSSL::SSL::SSLSocket#read_nonblock 会引发 IO::WaitWritable
而不是阻塞。因此,调用者应该像上面的示例一样等待可写性就绪。
非阻塞方法和 IO.select 的组合对于 tty、管道套接字和套接字等流也很有用,当多个进程从一个流中读取时。
最后,Linux 内核开发人员不保证 select(2) 的可读性意味着即使对于单个进程,后续 read(2) 的可读性;请参阅 select(2)
在 IO#readpartial
之前调用 IO.select 通常效果很好。但是,这不是使用 IO.select 的最佳方式。
select(2) 通知的可写性不显示有多少字节可写。 IO#write
方法会阻塞,直到写入给定的整个字符串。因此,IO#write(两个或多个字节)
可能会在 IO.select 通知可写性后阻塞。需要 IO#write_nonblock
来避免阻塞。
可以使用 write_nonblock
和 IO.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); }
以给定的模式和权限打开给定路径的文件;返回整数文件描述符。
如果文件可读,则文件必须存在;如果文件可写且不存在,则会以给定的权限创建文件。
File.write('t.tmp', '') # => 0 IO.sysopen('t.tmp') # => 8 IO.sysopen('t.tmp', 'w') # => 9
static VALUE rb_io_s_sysopen(int argc, VALUE *argv, VALUE _) { VALUE fname, vmode, vperm; VALUE intmode; int oflags, fd; mode_t perm; rb_scan_args(argc, argv, "12", &fname, &vmode, &vperm); FilePathValue(fname); if (NIL_P(vmode)) oflags = O_RDONLY; else if (!NIL_P(intmode = rb_check_to_integer(vmode, "to_int"))) oflags = NUM2INT(intmode); else { SafeStringValue(vmode); oflags = rb_io_modestr_oflags(StringValueCStr(vmode)); } if (NIL_P(vperm)) perm = 0666; else perm = NUM2MODET(vperm); RB_GC_GUARD(fname) = rb_str_new4(fname); fd = rb_sysopen(fname, oflags, perm); return INT2NUM(fd); }
尝试通过方法 to_io
将 object
转换为 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); }
打开流,将给定的 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); }
公共实例方法
将给定的 object
写入 self
,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; }
调用 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 }
设置自动关闭标志。
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; }
如果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)); }
将流的数据模式设置为二进制(参见数据模式)。
流的数据模式不能从二进制更改为文本。
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; }
如果流处于二进制模式,则返回true
,否则返回false
。 参见数据模式。
static VALUE rb_io_binmode_p(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); return RBOOL(fptr->mode & FMODE_BINMODE); }
如果流同时打开用于读写,则关闭用于读写;返回nil
。 参见打开和关闭流。
如果流是打开用于写入的,则在关闭之前将任何缓冲的写入刷新到操作系统。
如果流是由IO.popen
打开的,则设置全局变量$?
(子进程退出状态)。
示例
IO.popen('ruby', 'r+') do |pipe| puts pipe.closed? pipe.close puts $? puts pipe.closed? end
输出
false pid 13760 exit 0 true
相关:IO#close_read
,IO#close_write
,IO#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 标志。
f = File.open(File::NULL) f.close_on_exec = true system("cat", "/proc/self/fd/#{f.fileno}") # cat: /proc/self/fd/3: No such file or directory f.closed? #=> false
从 Ruby 2.0.0 开始,Ruby 默认情况下会为所有文件描述符设置 close-on-exec 标志。因此,您无需自行设置。此外,如果另一个线程使用 fork() 和 exec()(例如通过 system() 方法),取消设置 close-on-exec 标志会导致文件描述符泄漏。如果您确实需要将文件描述符继承到子进程,请使用 spawn() 的参数,例如 fd=>fd。
static VALUE rb_io_set_close_on_exec(VALUE io, VALUE arg) { int flag = RTEST(arg) ? FD_CLOEXEC : 0; rb_io_t *fptr; VALUE write_io; int fd, ret; write_io = GetWriteIO(io); if (io != write_io) { GetOpenFile(write_io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fptr->fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if ((ret & FD_CLOEXEC) != flag) { ret = (ret & ~FD_CLOEXEC) | flag; ret = fcntl(fd, F_SETFD, ret); if (ret != 0) rb_sys_fail_path(fptr->pathv); } } } GetOpenFile(io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if ((ret & FD_CLOEXEC) != flag) { ret = (ret & ~FD_CLOEXEC) | flag; ret = fcntl(fd, F_SETFD, ret); if (ret != 0) rb_sys_fail_path(fptr->pathv); } } return Qnil; }
如果流将在 exec 时关闭,则返回 true
,否则返回 false
。
f = File.open('t.txt') f.close_on_exec? # => true f.close_on_exec = false f.close_on_exec? # => false f.close
static VALUE rb_io_close_on_exec_p(VALUE io) { rb_io_t *fptr; VALUE write_io; int fd, ret; write_io = GetWriteIO(io); if (io != write_io) { GetOpenFile(write_io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if (!(ret & FD_CLOEXEC)) return Qfalse; } } GetOpenFile(io, fptr); if (fptr && 0 <= (fd = fptr->fd)) { if ((ret = fcntl(fd, F_GETFD)) == -1) rb_sys_fail_path(fptr->pathv); if (!(ret & FD_CLOEXEC)) return Qfalse; } return Qtrue; }
如果打开用于读取,则关闭用于读取的流;返回 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#close
,IO#close_write
,IO#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); }
如果打开用于写入,则关闭用于写入的流;返回 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#close
,IO#close_read
,IO#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; }
如果流已关闭读取和写入,则返回 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_read
,IO#close_write
,IO#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); }
对从流中读取的每行剩余内容调用块;返回 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"
给出参数sep
和limit
,结合两种行为
-
根据行分隔符
sep
调用下一行。 -
但返回的字节数不超过限制允许的字节数。
可选关键字参数 chomp
指定是否省略行分隔符。
f = File.new('t.txt') f.each_line(chomp: true) {|line| p line } f.close
输出
"First line" "Second line" "" "Fourth line" "Fifth line"
如果没有给出块,则返回一个 Enumerator
。
static VALUE rb_io_each_line(int argc, VALUE *argv, VALUE io) { VALUE str; struct getline_arg args; RETURN_ENUMERATOR(io, argc, argv); prepare_getline_args(argc, argv, &args, io); if (args.limit == 0) rb_raise(rb_eArgError, "invalid limit: 0 for each_line"); while (!NIL_P(str = rb_io_getline_1(args.rs, args.limit, args.chomp, io))) { rb_yield(str); } return io; }
对流中的每个字节 (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_char
,IO#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; }
对流中的每个字符调用给定的块;返回 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_byte
,IO#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; }
对流中的每个码点调用给定的块;返回 self
。
f = File.new('t.rus') a = [] f.each_codepoint {|c| a << c } a # => [1090, 1077, 1089, 1090] f.close
如果没有给出块,则返回一个 Enumerator
。
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); }
对从流中读取的每行剩余内容调用块;返回 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"
给出参数sep
和limit
,结合两种行为
-
根据行分隔符
sep
调用下一行。 -
但返回的字节数不超过限制允许的字节数。
可选关键字参数 chomp
指定是否省略行分隔符。
f = File.new('t.txt') f.each_line(chomp: true) {|line| p line } f.close
输出
"First line" "Second line" "" "Fourth line" "Fifth line"
如果没有给出块,则返回一个 Enumerator
。
如果流处于其末尾,则返回 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); }
如果流处于其末尾,则返回 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
(对于某些流不可用)。
返回表示流编码的 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)); }
调用 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(2)
(如果支持)立即将流中缓冲的所有数据写入磁盘,否则通过 fsync(2)
(如果支持)写入磁盘;否则引发异常。
static VALUE rb_io_fdatasync(VALUE io) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); if (io_fflush(fptr) < 0) rb_sys_fail_on_write(fptr); if ((int)rb_thread_io_blocking_region(nogvl_fdatasync, fptr, fptr->fd) == 0) return INT2FIX(0); /* fall back */ return rb_io_fsync(io); }
返回流的整数文件描述符。
$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); }
将 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(2)
。
注意以下区别
如果操作系统不支持 fsync(2)
,则会引发异常。
static VALUE rb_io_fsync(VALUE io) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); if (io_fflush(fptr) < 0) rb_sys_fail_on_write(fptr); if ((int)rb_thread_io_blocking_region(nogvl_fsync, fptr, fptr->fd) < 0) rb_sys_fail_path(fptr->pathv); return INT2FIX(0); }
从流中读取并返回下一个字节(范围为 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); }
从流中读取并返回下一个 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); }
从流中读取并返回一行;将返回值分配给 $_
。参见 行 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"
给出参数sep
和limit
,结合两种行为
-
返回由行分隔符
sep
确定的下一行,或者如果不存在则返回nil
。 -
但返回的字节数不超过限制允许的字节数。
可选关键字参数 chomp
指定是否省略行分隔符。
f = File.open('t.txt') # Chomp the lines. f.gets(chomp: true) # => "First line" f.gets(chomp: true) # => "Second line" f.gets(chomp: true) # => "" f.gets(chomp: true) # => "Fourth line" f.gets(chomp: true) # => "Fifth line" f.gets(chomp: true) # => nil f.close
static VALUE rb_io_gets_m(int argc, VALUE *argv, VALUE io) { VALUE str; str = rb_io_getline(argc, argv, io); rb_lastline_set(str); return str; }
返回 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, ">"); }
调用 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); }
如果流与终端设备 (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); }
返回流的当前行号;请参见 行号。
static VALUE rb_io_lineno(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); return INT2NUM(fptr->lineno); }
设置并返回流的行号;请参见 行号。
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; }
返回与 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); }
返回与流关联的子进程的进程 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); }
返回self
中当前位置(以字节为单位)(参见 位置)。
f = File.open('t.txt') f.tell # => 0 f.gets # => "First line\n" f.tell # => 12 f.close
跳转到给定的new_position
(以字节为单位);参见 位置
f = File.open('t.txt') f.tell # => 0 f.pos = 20 # => 20 f.tell # => 20 f.close
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); }
行为类似于 IO#readpartial
,但它
-
从给定的
offset
(以字节为单位)读取。 -
忽略并不会修改流的位置(参见 位置)。
-
绕过流中的任何用户空间缓冲。
由于此方法不会干扰流的状态(特别是其位置),因此pread
允许多个线程和进程使用相同的 IO 对象在不同的偏移量处进行读取。
f = File.open('t.txt') f.read # => "First line\nSecond line\n\nFourth line\nFifth line\n" f.pos # => 52 # Read 12 bytes at offset 0. f.pread(12, 0) # => "First line\n" # Read 9 bytes at offset 8. f.pread(9, 8) # => "ne\nSecon" f.close
在某些平台上不可用。
static VALUE rb_io_pread(int argc, VALUE *argv, VALUE io) { VALUE len, offset, str; rb_io_t *fptr; ssize_t n; struct prdwr_internal_arg arg = {.io = io}; int shrinkable; rb_scan_args(argc, argv, "21", &len, &offset, &str); arg.count = NUM2SIZET(len); arg.offset = NUM2OFFT(offset); shrinkable = io_setstrbuf(&str, (long)arg.count); if (arg.count == 0) return str; arg.buf = RSTRING_PTR(str); GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); arg.fd = fptr->fd; rb_io_check_closed(fptr); rb_str_locktmp(str); n = (ssize_t)rb_ensure(pread_internal_call, (VALUE)&arg, rb_str_unlocktmp, str); if (n < 0) { rb_sys_fail_path(fptr->pathv); } io_set_read_length(str, n, shrinkable); if (n == 0 && arg.count > 0) { rb_eof_error(); } return str; }
将给定的对象写入流;返回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
VALUE rb_io_print(int argc, const VALUE *argv, VALUE out) { int i; VALUE line; /* if no argument given, print `$_' */ if (argc == 0) { argc = 1; line = rb_lastline_get(); argv = &line; } if (argc > 1 && !NIL_P(rb_output_fs)) { rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "$, is set to non-nil value"); } for (i=0; i<argc; i++) { if (!NIL_P(rb_output_fs) && i>0) { rb_io_write(out, rb_output_fs); } rb_io_write(out, argv[i]); } if (argc > 0 && !NIL_P(rb_output_rs)) { rb_io_write(out, rb_output_rs); } return Qnil; }
格式化并将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; }
将一个字符写入流。参见 字符 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; }
将给定的objects
写入流,该流必须处于写入打开状态;返回nil
。\ 在每个不以换行符序列结尾的对象之后写入一个换行符。如果在没有参数的情况下调用,则写入一个换行符。参见 行 IO。
请注意,每个添加的换行符都是字符"\n"<//tt>,而不是输出记录分隔符 (<tt>$\
)。
对每个对象的处理
-
字符串:写入字符串。
-
既不是字符串也不是数组:写入
object.to_s
。 -
数组:写入数组的每个元素;数组可以嵌套。
为了使这些示例简短,我们定义了这个辅助方法
def show(*objects) # Puts objects to file. f = File.new('t.tmp', 'w+') f.puts(objects) # Return file content. f.rewind p f.read f.close end # Strings without newlines. show('foo', 'bar', 'baz') # => "foo\nbar\nbaz\n" # Strings, some with newlines. show("foo\n", 'bar', "baz\n") # => "foo\nbar\nbaz\n" # Neither strings nor arrays: show(0, 0.0, Rational(0, 1), Complex(9, 0), :zero) # => "0\n0.0\n0/1\n9+0i\nzero\n" # Array of strings. show(['foo', "bar\n", 'baz']) # => "foo\nbar\nbaz\n" # Nested arrays. show([[[0, 1], 2, 3], 4, 5]) # => "0\n1\n2\n3\n4\n5\n"
VALUE rb_io_puts(int argc, const VALUE *argv, VALUE out) { VALUE line, args[2]; /* if no argument given, print newline. */ if (argc == 0) { rb_io_write(out, rb_default_rs); return Qnil; } for (int i = 0; i < argc; i++) { // Convert the argument to a string: if (RB_TYPE_P(argv[i], T_STRING)) { line = argv[i]; } else if (rb_exec_recursive(io_puts_ary, argv[i], out)) { continue; } else { line = rb_obj_as_string(argv[i]); } // Write the line: int n = 0; if (RSTRING_LEN(line) == 0) { args[n++] = rb_default_rs; } else { args[n++] = line; if (!rb_str_end_with_asciichar(line, '\n')) { args[n++] = rb_default_rs; } } rb_io_writev(out, n, args); } return Qnil; }
行为类似于 IO#write
,除了它
-
在给定的
offset
(以字节为单位)处写入。 -
忽略并不会修改流的位置(参见 位置)。
-
绕过流中的任何用户空间缓冲。
由于此方法不会干扰流的状态(特别是其位置),因此pwrite
允许多个线程和进程使用相同的 IO 对象在不同的偏移量处写入。
f = File.open('t.tmp', 'w+') # Write 6 bytes at offset 3. f.pwrite('ABCDEF', 3) # => 6 f.rewind f.read # => "\u0000\u0000\u0000ABCDEF" f.close
在某些平台上不可用。
static VALUE rb_io_pwrite(VALUE io, VALUE str, VALUE offset) { rb_io_t *fptr; ssize_t n; struct prdwr_internal_arg arg = {.io = io}; VALUE tmp; if (!RB_TYPE_P(str, T_STRING)) str = rb_obj_as_string(str); arg.offset = NUM2OFFT(offset); io = GetWriteIO(io); GetOpenFile(io, fptr); rb_io_check_writable(fptr); arg.fd = fptr->fd; tmp = rb_str_tmp_frozen_acquire(str); arg.buf = RSTRING_PTR(tmp); arg.count = (size_t)RSTRING_LEN(tmp); n = (ssize_t)rb_thread_io_blocking_call(internal_pwrite_func, &arg, fptr->fd, RB_WAITFD_OUT); if (n < 0) rb_sys_fail_path(fptr->pathv); rb_str_tmp_frozen_release(str, tmp); return SSIZET2NUM(n); }
从流中读取字节;流必须处于读取打开状态(参见 访问模式)。
-
如果
maxlen
为nil
,则使用流的数据模式读取所有字节。 -
否则,以二进制模式读取最多
maxlen
个字节。
返回一个字符串(新的字符串或给定的out_string
),其中包含读取的字节。字符串的编码取决于maxLen
和out_string
。
-
maxlen
为nil
:使用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) 系统调用的行为,请考虑使用 readpartial
、read_nonblock
和 sysread
。
相关: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; }
从 ios 中读取最多 maxlen 字节,使用 read(2) 系统调用,在底层文件描述符上设置 O_NONBLOCK 之后。
如果存在可选的 outbuf 参数,它必须引用一个 String
,该字符串将接收数据。即使在方法调用之前 outbuf 不为空,它也只包含接收到的数据。
read_nonblock
只是调用 read(2) 系统调用。它会导致 read(2) 系统调用引起的所有错误:Errno::EWOULDBLOCK、Errno::EINTR 等。调用者应该注意这些错误。
如果异常是 Errno::EWOULDBLOCK 或 Errno::EAGAIN,它将由 IO::WaitReadable
扩展。因此,可以使用 IO::WaitReadable
来拯救异常以重试 read_nonblock。
read_nonblock
在 EOF 时会导致 EOFError
。
在某些平台上,例如 Windows,非阻塞模式在除套接字之外的 IO
对象上不受支持。在这种情况下,将引发 Errno::EBADF。
如果读取字节缓冲区不为空,read_nonblock
将像 readpartial 一样从缓冲区读取。在这种情况下,不会调用 read(2) 系统调用。
当 read_nonblock
引发 IO::WaitReadable
类型的异常时,在 io 可读之前不应调用 read_nonblock
以避免繁忙循环。这可以通过以下方式完成。
# emulates blocking read (readpartial). begin result = io.read_nonblock(maxlen) rescue IO::WaitReadable IO.select([io]) retry end
虽然 IO#read_nonblock
不会引发 IO::WaitWritable
。OpenSSL::Buffering#read_nonblock 可以引发 IO::WaitWritable
。如果 IO
和 SSL 应该被多态地使用,那么也应该拯救 IO::WaitWritable
。有关示例代码,请参阅 OpenSSL::Buffering#read_nonblock 的文档。
请注意,此方法与 readpartial 相同,只是设置了非阻塞标志。
通过将关键字参数 exception 指定为 false
,您可以指示 read_nonblock
不应引发 IO::WaitReadable
异常,而是返回符号 :wait_readable
。在 EOF 时,它将返回 nil 而不是引发 EOFError
。
# File ruby_3_3_0/io.rb, line 62 def read_nonblock(len, buf = nil, exception: true) Primitive.io_read_nonblock(len, buf, exception) end
从流中读取并返回下一个字节(范围为 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; }
读取并返回流中所有剩余的行;不修改 $_
。参见 行 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
给出参数sep
和limit
,结合两种行为
-
返回由行分隔符
sep
确定的行。 -
但返回的行中字节数不超过限制允许的字节数。
可选关键字参数 chomp
指定是否省略行分隔符。
f = File.new('t.txt') f.readlines(chomp: true) # => ["First line", "Second line", "", "Fourth line", "Fifth line"] f.close
static VALUE rb_io_readlines(int argc, VALUE *argv, VALUE io) { struct getline_arg args; prepare_getline_args(argc, argv, &args, io); return io_readlines(&args, io); }
从流中读取最多 maxlen
个字节;返回一个字符串(一个新字符串或给定的 out_string
)。它的编码是
-
如果给出
out_string
,则为out_string
的不变编码。 -
否则为 ASCII-8BIT。
-
如果可用,则包含流中的
maxlen
个字节。 -
否则,如果可用,则包含所有可用字节。
-
否则为空字符串。
如果只给出单个非负整数参数 maxlen
,则返回一个新字符串
f = File.new('t.txt') f.readpartial(20) # => "First line\nSecond l" f.readpartial(20) # => "ine\n\nFourth line\n" f.readpartial(20) # => "Fifth line\n" f.readpartial(20) # Raises EOFError. f.close
如果同时给出参数 maxlen
和字符串参数 out_string
,则返回修改后的 out_string
f = File.new('t.txt') s = 'foo' f.readpartial(20, s) # => "First line\nSecond l" s = 'bar' f.readpartial(0, s) # => "" f.close
此方法对于管道、套接字或 tty 等流很有用。它只在没有数据立即可用时阻塞。这意味着它只在以下所有条件都为真时阻塞
-
流中的字节缓冲区为空。
-
流的内容为空。
-
流未处于 EOF。
当阻塞时,该方法等待更多数据或流上的 EOF。
-
如果读取到更多数据,该方法将返回数据。
-
如果到达 EOF,该方法将引发
EOFError
。
当不阻塞时,该方法立即响应。
-
如果存在缓冲区中的数据,则返回该数据。
-
否则,如果存在流中的数据,则返回该数据。
-
否则,如果流已到达 EOF,则引发
EOFError
。
注意,此方法类似于 sysread。区别在于
-
如果字节缓冲区不为空,则从字节缓冲区读取,而不是“sysread for buffered
IO
(IOError
)”。 -
它不会导致 Errno::EWOULDBLOCK 和 Errno::EINTR。当 readpartial 通过 read 系统调用遇到 EWOULDBLOCK 和 EINTR 时,readpartial 会重试系统调用。
这意味着 readpartial 对非阻塞标志不敏感。它在 IO#sysread
导致 Errno::EWOULDBLOCK 的情况下阻塞,就好像 fd 是阻塞模式一样。
示例
# # Returned Buffer Content Pipe Content r, w = IO.pipe # w << 'abc' # "" "abc". r.readpartial(4096) # => "abc" "" "" r.readpartial(4096) # (Blocks because buffer and pipe are empty.) # # Returned Buffer Content Pipe Content r, w = IO.pipe # w << 'abc' # "" "abc" w.close # "" "abc" EOF r.readpartial(4096) # => "abc" "" EOF r.readpartial(4096) # raises EOFError # # Returned Buffer Content Pipe Content r, w = IO.pipe # w << "abc\ndef\n" # "" "abc\ndef\n" r.gets # => "abc\n" "def\n" "" w << "ghi\n" # "def\n" "ghi\n" r.readpartial(4096) # => "def\n" "" "ghi\n" r.readpartial(4096) # => "ghi\n" "" ""
static VALUE io_readpartial(int argc, VALUE *argv, VALUE io) { VALUE ret; ret = io_getpartial(argc, argv, io, Qnil, 0); if (NIL_P(ret)) rb_eof_error(); return ret; }
将流与另一个流重新关联,该流可能是不同类的。此方法可用于将现有流重定向到新目标。
给定参数 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; }
将流重新定位到其开头,将位置和行号都设置为零;请参阅 位置 和 行号
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); }
查找由整数 offset
(请参阅 位置)和常量 whence
给定的位置,该常量是以下之一:
-
:CUR
或IO::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
-
:END
或IO::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
-
:SET
或IO: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
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); }
请参阅 编码。
如果给出参数 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; }
如果流以 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); }
返回 ios 的状态信息,作为 File::Stat
类型的对象。
f = File.new("testfile") s = f.stat "%o" % s.mode #=> "100644" s.blksize #=> 4096 s.atime #=> Wed Apr 09 08:53:54 CDT 2003
static VALUE rb_io_stat(VALUE obj) { rb_io_t *fptr; struct stat st; GetOpenFile(obj, fptr); if (fstat(fptr->fd, &st) == -1) { rb_sys_fail_path(fptr->pathv); } return rb_stat_new(&st); }
返回流的当前同步模式。当同步模式为 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 mode 设置为给定值;返回给定值。
同步模式的值
-
true
:所有输出都会立即刷新到底层操作系统,并且不会在内部缓冲。 -
false
: 输出可能在内部被缓冲。
示例;
f = File.open('t.tmp', 'w') f.sync # => false f.sync = true f.sync # => true f.close
相关:IO#fsync
.
static VALUE rb_io_set_sync(VALUE io, VALUE sync) { rb_io_t *fptr; io = GetWriteIO(io); GetOpenFile(io, fptr); if (RTEST(sync)) { fptr->mode |= FMODE_SYNC; } else { fptr->mode &= ~FMODE_SYNC; } return sync; }
行为类似于 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; }
行为类似于 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); }
将给定的 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); }
返回self
中当前位置(以字节为单位)(参见 位置)。
f = File.open('t.txt') f.tell # => 0 f.gets # => "First line\n" f.tell # => 12 f.close
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); }
获取内部超时持续时间,如果未设置则为 nil。
VALUE rb_io_timeout(VALUE self) { rb_io_t *fptr = rb_io_get_fptr(self); return fptr->timeout; }
将内部超时设置为指定的持续时间或 nil。超时适用于所有可能的阻塞操作。
当操作执行时间超过设置的超时时间时,将引发 IO::TimeoutError
。
这会影响以下方法(但不限于):gets
,puts
,read
,write
,wait_readable
和 wait_writable
。这也影响阻塞套接字操作,如 Socket#accept 和 Socket#connect。
某些操作,如 File#open
和 IO#close
,不受超时影响。写入操作期间的超时可能会使 IO
处于不一致状态,例如数据被部分写入。一般来说,超时是防止应用程序挂起在缓慢 I/O 操作上的最后手段,例如在 slowloris 攻击期间发生的那些操作。
VALUE rb_io_set_timeout(VALUE self, VALUE timeout) { // Validate it: if (RTEST(timeout)) { rb_time_interval(timeout); } rb_io_t *fptr = rb_io_get_fptr(self); fptr->timeout = timeout; return self; }
返回流的整数文件描述符。
$stdin.fileno # => 0 $stdout.fileno # => 1 $stderr.fileno # => 2 File.open('t.txt').fileno # => 10 f.close
返回 self
。
static VALUE rb_io_to_io(VALUE io) { return io; }
返回与 IO
关联的路径,如果与 IO
没有关联的路径,则返回 nil
。不能保证该路径在文件系统中存在。
$stdin.path # => "<STDIN>" File.open("testfile") {|f| f.path} # => "testfile"
如果流与终端设备 (tty) 相关联,则返回 true
,否则返回 false
。
f = File.new('t.txt').isatty #=> false f.close f = File.new('/dev/tty').isatty #=> true f.close
将给定的数据推回(“左移”)到流的缓冲区,将数据放置在下一个要读取的位置;返回 nil
。参见 字节 IO。
注意
-
对于无缓冲读取(例如
IO#sysread
),调用该方法无效。 -
在流上调用
rewind
会丢弃推回的数据。
当给出参数 integer
时,只使用它的低位字节
File.write('t.tmp', '012') f = File.open('t.tmp') f.ungetbyte(0x41) # => nil f.read # => "A012" f.rewind f.ungetbyte(0x4243) # => nil f.read # => "C012" f.close
当给出参数 string
时,使用所有字节
File.write('t.tmp', '012') f = File.open('t.tmp') f.ungetbyte('A') # => nil f.read # => "A012" f.rewind f.ungetbyte('BCDE') # => nil f.read # => "BCDE012" f.close
VALUE rb_io_ungetbyte(VALUE io, VALUE b) { rb_io_t *fptr; GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); switch (TYPE(b)) { case T_NIL: return Qnil; case T_FIXNUM: case T_BIGNUM: ; VALUE v = rb_int_modulo(b, INT2FIX(256)); unsigned char c = NUM2INT(v) & 0xFF; b = rb_str_new((const char *)&c, 1); break; default: SafeStringValue(b); } io_ungetbyte(b, fptr); return Qnil; }
将给定的数据推回(“左移”)到流的缓冲区,将数据放置在下一个要读取的位置;返回 nil
。参见 字符 IO。
注意
-
对于无缓冲读取(例如
IO#sysread
),调用该方法无效。 -
在流上调用
rewind
会丢弃推回的数据。
当给出参数 integer
时,将整数解释为字符
File.write('t.tmp', '012') f = File.open('t.tmp') f.ungetc(0x41) # => nil f.read # => "A012" f.rewind f.ungetc(0x0442) # => nil f.getc.ord # => 1090 f.close
当给出参数 string
时,使用所有字符
File.write('t.tmp', '012') f = File.open('t.tmp') f.ungetc('A') # => nil f.read # => "A012" f.rewind f.ungetc("\u0442\u0435\u0441\u0442") # => nil f.getc.ord # => 1090 f.getc.ord # => 1077 f.getc.ord # => 1089 f.getc.ord # => 1090 f.close
VALUE rb_io_ungetc(VALUE io, VALUE c) { rb_io_t *fptr; long len; GetOpenFile(io, fptr); rb_io_check_char_readable(fptr); if (FIXNUM_P(c)) { c = rb_enc_uint_chr(FIX2UINT(c), io_read_encoding(fptr)); } else if (RB_BIGNUM_TYPE_P(c)) { c = rb_enc_uint_chr(NUM2UINT(c), io_read_encoding(fptr)); } else { SafeStringValue(c); } if (NEED_READCONV(fptr)) { SET_BINARY_MODE(fptr); len = RSTRING_LEN(c); #if SIZEOF_LONG > SIZEOF_INT if (len > INT_MAX) rb_raise(rb_eIOError, "ungetc failed"); #endif make_readconv(fptr, (int)len); if (fptr->cbuf.capa - fptr->cbuf.len < len) rb_raise(rb_eIOError, "ungetc failed"); if (fptr->cbuf.off < len) { MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.capa-fptr->cbuf.len, fptr->cbuf.ptr+fptr->cbuf.off, char, fptr->cbuf.len); fptr->cbuf.off = fptr->cbuf.capa-fptr->cbuf.len; } fptr->cbuf.off -= (int)len; fptr->cbuf.len += (int)len; MEMMOVE(fptr->cbuf.ptr+fptr->cbuf.off, RSTRING_PTR(c), char, len); } else { NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); io_ungetbyte(c, fptr); } return Qnil; }
等待直到 IO
准备好进行指定的事件,并返回已准备好的事件子集,或者在超时时返回一个假值。
事件可以是 IO::READABLE
、IO::WRITABLE
或 IO::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); }
等待直到 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); }
等待直到 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); }
等待直到 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); }
将给定的每个 objects
写入 self
,它必须以写入模式打开(参见 访问模式);返回写入的总字节数;每个不是字符串的 objects
通过方法 to_s
转换。
$stdout.write('Hello', ', ', 'World!', "\n") # => 14 $stdout.write('foo', :bar, 2, "\n") # => 8
输出
Hello, World! foobar2
相关:IO#read
.
static VALUE io_write_m(int argc, VALUE *argv, VALUE io) { if (argc != 1) { return io_writev(argc, argv, io); } else { VALUE str = argv[0]; return io_write(io, str, 0); } }
在为底层文件描述符设置 O_NONBLOCK 后,使用 write(2) 系统调用将给定字符串写入 ios。
它返回写入的字节数。
write_nonblock
只是调用 write(2) 系统调用。它会导致 write(2) 系统调用引起的所有错误:Errno::EWOULDBLOCK、Errno::EINTR 等。结果也可能小于 string.length(部分写入)。调用者应该注意这些错误和部分写入。
如果异常是 Errno::EWOULDBLOCK 或 Errno::EAGAIN,它将由 IO::WaitWritable
扩展。因此,IO::WaitWritable
可用于拯救这些异常以重试 write_nonblock。
# Creates a pipe. r, w = IO.pipe # write_nonblock writes only 65536 bytes and return 65536. # (The pipe size is 65536 bytes on this environment.) s = "a" * 100000 p w.write_nonblock(s) #=> 65536 # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN). p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN)
如果写入缓冲区不为空,它将首先被刷新。
当 write_nonblock
抛出 IO::WaitWritable
类型的异常时,write_nonblock
应该在 io 可写之前不要被调用,以避免繁忙循环。这可以通过以下方式完成。
begin result = io.write_nonblock(string) rescue IO::WaitWritable, Errno::EINTR IO.select(nil, [io]) retry end
请注意,这不能保证写入字符串中的所有数据。写入的长度作为结果报告,应稍后检查。
在某些平台(如 Windows)上,根据 IO
对象的类型,write_nonblock
不受支持。在这种情况下,write_nonblock
会抛出 Errno::EBADF
。
通过将关键字参数 exception 指定为 false
,您可以指示 write_nonblock
不应该抛出 IO::WaitWritable
异常,而是返回符号 :wait_writable
。
# File ruby_3_3_0/io.rb, line 120 def write_nonblock(buf, exception: true) Primitive.io_write_nonblock(buf, exception) end