类 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
:流模式。 -
:flags
:Integer
文件打开标志;如果还给定了mode
,则对两者进行按位 OR 操作。 -
: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); }
返回打开控制台的 File
实例。
如果给定了 sym
,则会使用 args
将其发送到打开的控制台,并将返回结果,而不是控制台 IO
本身。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_dev(int argc, VALUE *argv, VALUE klass) { VALUE con = 0; VALUE sym = 0; rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); if (argc) { Check_Type(sym = argv[0], T_SYMBOL); } // Force the class to be File. if (klass == rb_cIO) klass = rb_cFile; if (rb_const_defined(klass, id_console)) { con = rb_const_get(klass, id_console); if (!RB_TYPE_P(con, T_FILE) || RTEST(rb_io_closed_p(con))) { rb_const_remove(klass, id_console); con = 0; } } if (sym) { if (sym == ID2SYM(id_close) && argc == 1) { if (con) { rb_io_close(con); rb_const_remove(klass, id_console); con = 0; } return Qnil; } } if (!con) { #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H || defined HAVE_SGTTY_H # define CONSOLE_DEVICE "/dev/tty" #elif defined _WIN32 # define CONSOLE_DEVICE "con$" # define CONSOLE_DEVICE_FOR_READING "conin$" # define CONSOLE_DEVICE_FOR_WRITING "conout$" #endif #ifndef CONSOLE_DEVICE_FOR_READING # define CONSOLE_DEVICE_FOR_READING CONSOLE_DEVICE #endif #ifdef CONSOLE_DEVICE_FOR_WRITING VALUE out; rb_io_t *ofptr; #endif int fd; VALUE path = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE)); #ifdef CONSOLE_DEVICE_FOR_WRITING fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0); if (fd < 0) return Qnil; out = rb_io_open_descriptor(klass, fd, FMODE_WRITABLE | FMODE_SYNC, path, Qnil, NULL); #endif fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_READING, O_RDWR, 0); if (fd < 0) { #ifdef CONSOLE_DEVICE_FOR_WRITING rb_io_close(out); #endif return Qnil; } con = rb_io_open_descriptor(klass, fd, FMODE_READWRITE | FMODE_SYNC, path, Qnil, NULL); #ifdef CONSOLE_DEVICE_FOR_WRITING rb_io_set_write_io(con, out); #endif rb_const_set(klass, id_console, con); } if (sym) { return rb_f_send(argc, argv, con); } return con; }
从给定的 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
为数组,其第一个元素为 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.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
尤其是,对于类似 IO
的对象(例如 OpenSSL::SSL::SSLSocket
),首选非阻塞方法和 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.select 通知可写性之后,IO#write(两个或更多字节)
可能会被阻塞。需要 IO#write_nonblock
来避免阻塞。
可以使用 write_nonblock
和 IO.select
模拟阻塞写入 (write
),如下所示: IO::WaitReadable
也应该在 OpenSSL::SSL::SSLSocket
中为 SSL 重新协商进行救援。
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
;如果 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; }
如果在最终确定或调用 close
时关闭 ios 的底层文件描述符,则返回 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 console_beep(VALUE io) { #ifdef _WIN32 MessageBeep(0); #else int fd = GetWriteFD(io); if (write(fd, "\a", 1) < 0) sys_fail(io); #endif return io; }
将流的数据模式设置为二进制(请参阅 数据模式)。
流的数据模式不能从二进制更改为文本。
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); }
_WIN32 static VALUE console_check_winsize_changed(VALUE io) { HANDLE h; DWORD num; h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io)); while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) { INPUT_RECORD rec; if (ReadConsoleInput(h, &rec, 1, &num)) { if (rec.EventType == WINDOW_BUFFER_SIZE_EVENT) { rb_yield(Qnil); } } } return io; }
static VALUE console_clear_screen(VALUE io) { console_erase_screen(io, INT2FIX(2)); console_goto(io, INT2FIX(0), INT2FIX(0)); return io; }
如果流已打开进行读写,则关闭流;返回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; }
设置关闭时执行标志。
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; }
如果流将在执行时关闭,则返回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); }
返回表示当前控制台模式的数据。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_conmode_get(VALUE io) { conmode t; int fd = GetReadFD(io); if (!getattr(fd, &t)) sys_fail(io); return conmode_new(cConmode, &t); }
将控制台模式设置为 mode
。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_conmode_set(VALUE io, VALUE mode) { conmode *t, r; int fd = GetReadFD(io); TypedData_Get_Struct(mode, conmode, &conmode_type, t); r = *t; if (!setattr(fd, &r)) sys_fail(io); return mode; }
在 cooked 模式下产生 self
。
STDIN.cooked(&:gets)
将读取并返回一行,并回显和行编辑。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_cooked(VALUE io) { return ttymode(io, rb_yield, io, set_cookedmode, NULL); }
启用 cooked 模式。
如果需要返回终端模式,请使用 io.cooked { … }。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_set_cooked(VALUE io) { conmode t; int fd = GetReadFD(io); if (!getattr(fd, &t)) sys_fail(io); set_cookedmode(&t, NULL); if (!setattr(fd, &t)) sys_fail(io); return io; }
将当前光标位置作为整数的两个元素数组 (行,列) 返回
io.cursor # => [3, 5]
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_cursor_pos(VALUE io) { #ifdef _WIN32 rb_console_size_t ws; int fd = GetWriteFD(io); if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { rb_syserr_fail(LAST_ERROR, 0); } return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); #else static const struct query_args query = {"\033[6n", 0}; VALUE resp = console_vt_response(0, 0, io, &query); VALUE row, column, term; unsigned int r, c; if (!RB_TYPE_P(resp, T_ARRAY) || RARRAY_LEN(resp) != 3) return Qnil; term = RARRAY_AREF(resp, 2); if (!RB_TYPE_P(term, T_STRING) || RSTRING_LEN(term) != 1) return Qnil; if (RSTRING_PTR(term)[0] != 'R') return Qnil; row = RARRAY_AREF(resp, 0); column = RARRAY_AREF(resp, 1); rb_ary_resize(resp, 2); r = NUM2UINT(row) - 1; c = NUM2UINT(column) - 1; RARRAY_ASET(resp, 0, INT2NUM(r)); RARRAY_ASET(resp, 1, INT2NUM(c)); return resp; #endif }
static VALUE console_cursor_set(VALUE io, VALUE cpos) { cpos = rb_convert_type(cpos, T_ARRAY, "Array", "to_ary"); if (RARRAY_LEN(cpos) != 2) rb_raise(rb_eArgError, "expected 2D coordinate"); return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1)); }
static VALUE console_cursor_down(VALUE io, VALUE val) { return console_move(io, +NUM2INT(val), 0); }
static VALUE console_cursor_left(VALUE io, VALUE val) { return console_move(io, 0, -NUM2INT(val)); }
static VALUE console_cursor_right(VALUE io, VALUE val) { return console_move(io, 0, +NUM2INT(val)); }
static VALUE console_cursor_up(VALUE io, VALUE val) { return console_move(io, -NUM2INT(val), 0); }
使用流中的每个字节 (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
。
启用/禁用回显。在某些平台上,此标志和原始/已煮模式的所有组合可能无效。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_set_echo(VALUE io, VALUE f) { conmode t; int fd = GetReadFD(io); if (!getattr(fd, &t)) sys_fail(io); if (RTEST(f)) set_echo(&t, NULL); else set_noecho(&t, NULL); if (!setattr(fd, &t)) sys_fail(io); return io; }
如果回显已启用,则返回 true
。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_echo_p(VALUE io) { conmode t; int fd = GetReadFD(io); if (!getattr(fd, &t)) sys_fail(io); return echo_p(&t) ? Qtrue : Qfalse; }
如果流位于其末尾,则返回 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); }
static VALUE console_erase_line(VALUE io, VALUE val) { int mode = mode_in_range(val, 2, "line erase"); #ifdef _WIN32 HANDLE h; rb_console_size_t ws; COORD *pos = &ws.dwCursorPosition; DWORD w; h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); if (!GetConsoleScreenBufferInfo(h, &ws)) { rb_syserr_fail(LAST_ERROR, 0); } w = winsize_col(&ws); switch (mode) { case 0: /* after cursor */ w -= pos->X; break; case 1: /* before *and* cursor */ w = pos->X + 1; pos->X = 0; break; case 2: /* entire line */ pos->X = 0; break; } constat_clear(h, ws.wAttributes, w, *pos); return io; #else rb_io_write(io, rb_sprintf("\x1b[%dK", mode)); #endif return io; }
static VALUE console_erase_screen(VALUE io, VALUE val) { int mode = mode_in_range(val, 3, "screen erase"); #ifdef _WIN32 HANDLE h; rb_console_size_t ws; COORD *pos = &ws.dwCursorPosition; DWORD w; h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); if (!GetConsoleScreenBufferInfo(h, &ws)) { rb_syserr_fail(LAST_ERROR, 0); } w = winsize_col(&ws); switch (mode) { case 0: /* erase after cursor */ w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); break; case 1: /* erase before *and* cursor */ w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); pos->X = 0; pos->Y = ws.srWindow.Top; break; case 2: /* erase entire screen */ w = (w * winsize_row(&ws)); pos->X = 0; pos->Y = ws.srWindow.Top; break; case 3: /* erase entire screen */ w = (w * ws.dwSize.Y); pos->X = 0; pos->Y = 0; break; } constat_clear(h, ws.wAttributes, w, *pos); #else rb_io_write(io, rb_sprintf("\x1b[%dJ", mode)); #endif return io; }
expect
库添加了实例方法 IO#expect
,它类似于 TCL expect 扩展。
要使用此方法,您必须需要 expect
require 'expect'
从 IO
中读取,直到给定的 pattern
匹配或 timeout
结束。
它返回一个数组,其中包含读取缓冲区,后跟匹配项。如果给出了一个块,则结果将生成到块中并返回 nil。
如果未调用块,它将等待从 IO
获得与给定 pattern
匹配的输入,或超时时间已过。从 IO
获得模式时,将返回一个数组。IO
中获得的整个字符串是数组的第一个元素,直到模式匹配,然后是指示模式与正则表达式中锚匹配的元素。
可选的超时参数(以秒为单位)定义等待模式的总时间。如果超时或找到 eof,则返回或产生 nil。但是,超时会话中的缓冲区将保留用于下一次 expect 调用。默认超时为 9999999 秒。
# File ext/pty/lib/expect.rb, line 33 def expect(pat,timeout=9999999) buf = ''.dup case pat when String e_pat = Regexp.new(Regexp.quote(pat)) when Regexp e_pat = pat else raise TypeError, "unsupported pattern class: #{pat.class}" end @unusedBuf ||= '' while true if not @unusedBuf.empty? c = @unusedBuf.slice!(0) elsif !IO.select([self],nil,nil,timeout) or eof? then result = nil @unusedBuf = buf break else c = getc end buf << c if $expect_verbose STDOUT.print c STDOUT.flush end if mat=e_pat.match(buf) then result = [buf,*mat.captures] break end end if block_given? then yield result else return result end nil end
返回表示流编码的 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#raw
。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_getch(int argc, VALUE *argv, VALUE io) { rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); #ifndef _WIN32 return ttymode(io, getc_call, io, set_rawmode, optp); #else rb_io_t *fptr; VALUE str; wint_t c; int len; char buf[8]; wint_t wbuf[2]; # ifndef HAVE_RB_IO_WAIT struct timeval *to = NULL, tv; # else VALUE timeout = Qnil; # endif GetOpenFile(io, fptr); if (optp) { if (optp->vtime) { # ifndef HAVE_RB_IO_WAIT to = &tv; # else struct timeval tv; # endif tv.tv_sec = optp->vtime / 10; tv.tv_usec = (optp->vtime % 10) * 100000; # ifdef HAVE_RB_IO_WAIT timeout = rb_fiber_scheduler_make_timeout(&tv); # endif } switch (optp->vmin) { case 1: /* default */ break; case 0: /* return nil when timed out */ if (optp->vtime) break; /* fallthru */ default: rb_warning("min option larger than 1 ignored"); } if (optp->intr) { # ifndef HAVE_RB_IO_WAIT int w = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, to); if (w < 0) rb_eof_error(); if (!(w & RB_WAITFD_IN)) return Qnil; # else VALUE result = rb_io_wait(io, RB_INT2NUM(RUBY_IO_READABLE), timeout); if (!RTEST(result)) return Qnil; # endif } else if (optp->vtime) { rb_warning("Non-zero vtime option ignored if intr flag is unset"); } } len = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getch, wbuf, RUBY_UBF_IO, 0); switch (len) { case 0: return Qnil; case 2: buf[0] = (char)wbuf[0]; c = wbuf[1]; len = 1; do { buf[len++] = (unsigned char)c; } while ((c >>= CHAR_BIT) && len < (int)sizeof(buf)); return rb_str_new(buf, len); default: c = wbuf[0]; len = rb_uv_to_utf8(buf, c); str = rb_utf8_str_new(buf, len); return rb_str_conv_enc(str, NULL, rb_default_external_encoding()); } #endif }
读取并返回一行,不回显。打印 prompt
,除非它是 nil
。
终止读取行的换行符从返回的字符串中删除,请参阅 String#chomp!
。
必须要求 ‘io/console’ 才能使用此方法。
require 'io/console' IO::console.getpass("Enter password:") Enter password: # => "mypassword"
static VALUE console_getpass(int argc, VALUE *argv, VALUE io) { VALUE str, wio; rb_check_arity(argc, 0, 1); wio = rb_io_get_write_io(io); if (wio == io && io == rb_stdin) wio = rb_stderr; prompt(argc, argv, wio); rb_io_flush(wio); str = rb_ensure(getpass_call, io, puts_call, wio); return str_chomp(str); }
从流中读取并返回一行;将返回值分配给 $_
。请参阅 行 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; }
static VALUE console_goto(VALUE io, VALUE y, VALUE x) { #ifdef _WIN32 COORD pos; int fd = GetWriteFD(io); pos.X = NUM2UINT(x); pos.Y = NUM2UINT(y); if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { rb_syserr_fail(LAST_ERROR, 0); } #else rb_io_write(io, rb_sprintf("\x1b[%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); #endif return io; }
static VALUE console_goto_column(VALUE io, VALUE val) { #ifdef _WIN32 HANDLE h; rb_console_size_t ws; COORD *pos = &ws.dwCursorPosition; h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); if (!GetConsoleScreenBufferInfo(h, &ws)) { rb_syserr_fail(LAST_ERROR, 0); } pos->X = NUM2INT(val); if (!SetConsoleCursorPosition(h, *pos)) { rb_syserr_fail(LAST_ERROR, 0); } #else rb_io_write(io, rb_sprintf("\x1b[%dG", NUM2UINT(val)+1)); #endif return io; }
刷新内核中的输入缓冲区。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_iflush(VALUE io) { #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H int fd = GetReadFD(io); if (tcflush(fd, TCIFLUSH)) sys_fail(io); #endif return io; }
返回 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); }
刷新内核中的输入和输出缓冲区。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_ioflush(VALUE io) { #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H int fd1 = GetReadFD(io); int fd2 = GetWriteFD(io); if (fd2 != -1 && fd1 != fd2) { if (tcflush(fd1, TCIFLUSH)) sys_fail(io); if (tcflush(fd2, TCOFLUSH)) sys_fail(io); } else { if (tcflush(fd1, TCIOFLUSH)) sys_fail(io); } #endif return io; }
如果流与终端设备 (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; }
在禁用回显的情况下生成 self
。
STDIN.noecho(&:gets)
将读取并返回一行,而不会回显。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_noecho(VALUE io) { return ttymode(io, rb_yield, io, set_noecho, NULL); }
在非阻塞模式下生成 self
。
当提供 false
作为参数时,将在阻塞模式下生成 self
。在执行块后,将恢复原始模式。
static VALUE rb_io_nonblock_block(int argc, VALUE *argv, VALUE self) { int nb = 1; int descriptor = rb_io_descriptor(self); if (argc > 0) { VALUE v; rb_scan_args(argc, argv, "01", &v); nb = RTEST(v); } int current_flags = get_fcntl_flags(descriptor); int restore[2] = {descriptor, current_flags}; if (!io_nonblock_set(descriptor, current_flags, nb)) return rb_yield(self); return rb_ensure(rb_yield, self, io_nonblock_restore, (VALUE)restore); }
将流的非阻塞模式设置为 true
时启用,将阻塞模式设置为 false
时禁用。
此方法为 ios 中的文件描述符设置或清除 O_NONBLOCK 标志。
大多数 IO
方法的行为不受此标志影响,因为它们在 EAGAIN 和部分读/写后重试系统调用以完成其任务。(例外是 IO#syswrite
,它不会重试。)
此方法可用于清除标准 I/O 的非阻塞模式。由于非阻塞方法(read_nonblock
等)设置非阻塞模式,但不会清除它,因此此方法可用如下所示。
END { STDOUT.nonblock = false } STDOUT.write_nonblock("foo")
由于该标志在进程之间共享,并且许多非 Ruby 命令不期望标准 I/O 处于非阻塞模式,因此在 Ruby 程序退出之前清除该标志是安全的。
例如,以下 Ruby 程序使 STDIN/STDOUT/STDER 退出非阻塞模式。(STDIN、STDOUT 和 STDERR 连接到终端。因此,使其中一个处于非阻塞模式会影响其他两个。)因此,cat 命令尝试从标准输入读取,并导致“资源暂时不可用”错误 (EAGAIN)。
% ruby -e ' STDOUT.write_nonblock("foo\n")'; cat foo cat: -: Resource temporarily unavailable
清除标志使 cat 命令的行为正常。(cat 命令等待标准输入的输入。)
% ruby -rio/nonblock -e ' END { STDOUT.nonblock = false } STDOUT.write_nonblock("foo") '; cat foo
static VALUE rb_io_nonblock_set(VALUE self, VALUE value) { if (RTEST(value)) { rb_io_t *fptr; GetOpenFile(self, fptr); rb_io_set_nonblock(fptr); } else { int descriptor = rb_io_descriptor(self); io_nonblock_set(descriptor, get_fcntl_flags(descriptor), RTEST(value)); } return self; }
如果 IO
对象处于非阻塞模式,则返回 true
。
static VALUE rb_io_nonblock_p(VALUE io) { if (get_fcntl_flags(rb_io_descriptor(io)) & O_NONBLOCK) return Qtrue; return Qfalse; }
返回可以在不阻塞的情况下读取的字节数。如果没有可用信息,则返回零。
您必须需要“io/wait”才能使用此方法。
static VALUE io_nread(VALUE io) { rb_io_t *fptr; int len; ioctl_arg n; GetOpenFile(io, fptr); rb_io_check_readable(fptr); len = rb_io_read_pending(fptr); if (len > 0) return INT2FIX(len); #ifdef HAVE_RB_IO_DESCRIPTOR int fd = rb_io_descriptor(io); #else int fd = fptr->fd; #endif if (!FIONREAD_POSSIBLE_P(fd)) return INT2FIX(0); if (ioctl(fd, FIONREAD, &n)) return INT2FIX(0); if (n > 0) return ioctl_arg2num(n); return INT2FIX(0); }
刷新内核中的输出缓冲区。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_oflush(VALUE io) { int fd = GetWriteFD(io); #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H if (tcflush(fd, TCOFLUSH)) sys_fail(io); #endif (void)fd; return io; }
返回与 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); }
使用 fpathconf() 返回路径名配置变量。
name 应为以 PC_
开头的 Etc
下的常量。
返回值为整数或 nil。nil 表示不确定限制。(fpathconf() 返回 -1 但 errno 未设置。)
require 'etc' IO.pipe {|r, w| p w.pathconf(Etc::PC_PIPE_BUF) #=> 4096 }
static VALUE io_pathconf(VALUE io, VALUE arg) { int name; long ret; name = NUM2INT(arg); errno = 0; ret = fpathconf(rb_io_descriptor(io), name); if (ret == -1) { if (errno == 0) /* no limit */ return Qnil; rb_sys_fail("fpathconf"); } return LONG2NUM(ret); }
返回与流关联的子进程的进程 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); }
查找给定的 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; }
static VALUE console_key_pressed_p(VALUE io, VALUE k) { int vk = -1; if (FIXNUM_P(k)) { vk = NUM2UINT(k); } else { const struct vktable *t; const char *kn; if (SYMBOL_P(k)) { k = rb_sym2str(k); kn = RSTRING_PTR(k); } else { kn = StringValuePtr(k); } t = console_win32_vk(kn, RSTRING_LEN(k)); if (!t || (vk = (short)t->vk) == -1) { rb_raise(rb_eArgError, "unknown virtual key code: % "PRIsVALUE, k); } } return GetKeyState(vk) & 0x80 ? Qtrue : Qfalse; }
将给定的对象写入流;返回 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); }
在原始模式下生成 self
,并返回块的结果。
STDIN.raw(&:gets)
将读取并返回一行,而无需回显和行编辑。
参数 min
指定在执行读取操作时应接收的最小字节数。(默认值:1)
参数 time
以 1/10 秒的精度指定超时时间(以秒为单位)。(默认值:0)
如果参数 intr
为 true
,则启用中断、中断、退出和挂起特殊字符。
有关更多详细信息,请参阅 termios 手册页。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_raw(int argc, VALUE *argv, VALUE io) { rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); return ttymode(io, rb_yield, io, set_rawmode, optp); }
启用原始模式,并返回 io
。
如果需要返回终端模式,请使用 io.raw { ... }
。
有关参数的详细信息,请参阅 IO#raw
。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_set_raw(int argc, VALUE *argv, VALUE io) { conmode t; rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); int fd = GetReadFD(io); if (!getattr(fd, &t)) sys_fail(io); set_rawmode(&t, optp); if (!setattr(fd, &t)) sys_fail(io); return io; }
从流中读取字节;该流必须打开以供读取(参见访问模式)
-
如果
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; }
在为底层文件描述符设置 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
类型的异常时,read_nonblock
不应被调用,直到 io 可读以避免繁忙循环。这可按如下方式完成。
# 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 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。不同之处在于
-
它不会导致 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; }
如果可以在不阻塞的情况下获得输入,则返回真值,否则返回假值。
您必须需要“io/wait”才能使用此方法。
static VALUE io_ready_p(VALUE io) { rb_io_t *fptr; #ifndef HAVE_RB_IO_WAIT struct timeval tv = {0, 0}; #endif GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (rb_io_read_pending(fptr)) return Qtrue; #ifndef HAVE_RB_IO_WAIT return wait_for_single_fd(fptr, RB_WAITFD_IN, &tv) ? Qtrue : Qfalse; #else return io_wait_event(io, RUBY_IO_READABLE, RB_INT2NUM(0), 1); #endif }
将流重新关联到另一个流,该流可能属于不同的类。此方法可用于将现有流重定向到新目标。
如果给出了参数 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); }
static VALUE console_scroll_backward(VALUE io, VALUE val) { return console_scroll(io, -NUM2INT(val)); }
static VALUE console_scroll_forward(VALUE io, VALUE val) { return console_scroll(io, +NUM2INT(val)); }
查找由整数 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); }
返回类型为 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); }
返回流的当前同步模式。当同步模式为 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); }
为流设置同步模式为给定值;返回给定值。
同步模式的值
-
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; }
返回 self
。
static VALUE rb_io_to_io(VALUE io) { return io; }
将给定数据推回到流的缓冲区上(“取消移位”),将数据放置在要读取的下一个位置;返回 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
之一。
您必须需要“io/wait”才能使用此方法。
static VALUE io_wait(int argc, VALUE *argv, VALUE io) { #ifndef HAVE_RB_IO_WAIT rb_io_t *fptr; struct timeval timerec; struct timeval *tv = NULL; int event = 0; int i; GetOpenFile(io, fptr); for (i = 0; i < argc; ++i) { if (SYMBOL_P(argv[i])) { event |= wait_mode_sym(argv[i]); } else { *(tv = &timerec) = rb_time_interval(argv[i]); } } /* rb_time_interval() and might_mode() might convert the argument */ rb_io_check_closed(fptr); if (!event) event = RB_WAITFD_IN; if ((event & RB_WAITFD_IN) && rb_io_read_pending(fptr)) return Qtrue; if (wait_for_single_fd(fptr, event, tv)) return io; return Qnil; #else VALUE timeout = Qundef; rb_io_event_t events = 0; int i, 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 (i = 0; i < argc; i += 1) { if (RB_SYMBOL_P(argv[i])) { events |= wait_mode_sym(argv[i]); } else if (timeout == Qundef) { rb_time_interval(timeout = argv[i]); } else { rb_raise(rb_eArgError, "timeout given more than once"); } } if (timeout == Qundef) 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); #endif }
等到IO
具有优先级,并返回真值,或在超时时返回假值。优先级数据使用 Socket::MSG_OOB 标志发送和接收,通常仅限于流。
您必须需要“io/wait”才能使用此方法。
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
可读,并返回真值,或在超时时返回假值。当有缓冲数据可用时,立即返回真值。
您必须需要“io/wait”才能使用此方法。
static VALUE io_wait_readable(int argc, VALUE *argv, VALUE io) { rb_io_t *fptr; #ifndef HAVE_RB_IO_WAIT struct timeval timerec; struct timeval *tv; #endif GetOpenFile(io, fptr); rb_io_check_readable(fptr); #ifndef HAVE_RB_IO_WAIT tv = get_timeout(argc, argv, &timerec); #endif if (rb_io_read_pending(fptr)) return Qtrue; #ifndef HAVE_RB_IO_WAIT if (wait_for_single_fd(fptr, RB_WAITFD_IN, tv)) { return io; } return Qnil; #else rb_check_arity(argc, 0, 1); VALUE timeout = (argc == 1 ? argv[0] : Qnil); return io_wait_event(io, RUBY_IO_READABLE, timeout, 1); #endif }
等到IO
可写,并返回真值,或在超时时返回假值。
您必须需要“io/wait”才能使用此方法。
static VALUE io_wait_writable(int argc, VALUE *argv, VALUE io) { rb_io_t *fptr; #ifndef HAVE_RB_IO_WAIT struct timeval timerec; struct timeval *tv; #endif GetOpenFile(io, fptr); rb_io_check_writable(fptr); #ifndef HAVE_RB_IO_WAIT tv = get_timeout(argc, argv, &timerec); if (wait_for_single_fd(fptr, RB_WAITFD_OUT, tv)) { return io; } return Qnil; #else rb_check_arity(argc, 0, 1); VALUE timeout = (argc == 1 ? argv[0] : Qnil); return io_wait_event(io, RUBY_IO_WRITABLE, timeout, 1); #endif }
返回控制台大小。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_winsize(VALUE io) { rb_console_size_t ws; int fd = GetWriteFD(io); if (!getwinsize(fd, &ws)) sys_fail(io); return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws))); }
尝试设置控制台大小。效果取决于平台和运行环境。
必须要求 ‘io/console’ 才能使用此方法。
static VALUE console_set_winsize(VALUE io, VALUE size) { rb_console_size_t ws; #if defined _WIN32 HANDLE wh; int newrow, newcol; BOOL ret; #endif VALUE row, col, xpixel, ypixel; const VALUE *sz; long sizelen; int fd; size = rb_Array(size); if ((sizelen = RARRAY_LEN(size)) != 2 && sizelen != 4) { rb_raise(rb_eArgError, "wrong number of arguments (given %ld, expected 2 or 4)", sizelen); } sz = RARRAY_CONST_PTR(size); row = sz[0], col = sz[1], xpixel = ypixel = Qnil; if (sizelen == 4) xpixel = sz[2], ypixel = sz[3]; fd = GetWriteFD(io); #if defined TIOCSWINSZ ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = 0; #define SET(m) ws.ws_##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) SET(row); SET(col); SET(xpixel); SET(ypixel); #undef SET if (!setwinsize(fd, &ws)) sys_fail(io); #elif defined _WIN32 wh = (HANDLE)rb_w32_get_osfhandle(fd); #define SET(m) new##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) SET(row); SET(col); #undef SET if (!NIL_P(xpixel)) (void)NUM2UINT(xpixel); if (!NIL_P(ypixel)) (void)NUM2UINT(ypixel); if (!GetConsoleScreenBufferInfo(wh, &ws)) { rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); } ws.dwSize.X = newcol; ret = SetConsoleScreenBufferSize(wh, ws.dwSize); ws.srWindow.Left = 0; ws.srWindow.Top = 0; ws.srWindow.Right = newcol-1; ws.srWindow.Bottom = newrow-1; if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); } /* retry when shrinking buffer after shrunk window */ if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) { rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); } /* remove scrollbar if possible */ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); } #endif return io; }
将给定的每个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 io.rb, line 120 def write_nonblock(buf, exception: true) Primitive.io_write_nonblock(buf, exception) end