class IO
Expect library adds the IO instance method expect, which does similar act to tcl's expect extension.
In order to use this method, you must require expect:
require 'expect'
Please see expect for usage.
The IO class is the basis for all input and output in Ruby. An I/O stream may be duplexed (that is, bidirectional), and so may use more than one native operating system stream.
Many of the examples in this section use the File class, the only standard subclass of IO. The two classes are closely associated. Like the File class, the Socket library subclasses from IO (such as TCPSocket or UDPSocket).
The Kernel#open method can create an IO (or File) object for these types of arguments:
-
A plain string represents a filename suitable for the underlying operating system.
-
A string starting with
"|"
indicates a subprocess. The remainder of the string following the"|"
is invoked as a process with appropriate input/output channels connected to it. -
A string equal to
"|-"
will create another Ruby instance as a subprocess.
The IO may be opened with different file modes (read-only, write-only) and encodings for proper conversion. See ::new for these options. See Kernel#open for details of the various command formats described above.
::popen, the Open3 library, or Process#spawn may also be used to communicate with subprocesses through an IO.
Ruby will convert pathnames between different operating system conventions
if possible. For instance, on a Windows system the filename
"/gumby/ruby/test.rb"
will be opened as
"\gumby\ruby\test.rb"
. When specifying a
Windows-style filename in a Ruby string, remember to escape the
backslashes:
"c:\\gumby\\ruby\\test.rb"
Our examples here will use the Unix-style forward slashes; File::ALT_SEPARATOR can be used to get the platform-specific separator character.
The global constant ARGF (also accessible as $<) provides an IO-like stream which allows access to all files mentioned on the command line (or STDIN if no files are mentioned). ARGF#path and its alias ARGF#filename are provided to access the name of the file currently being read.
io/console¶ ↑
The io/console extension provides methods for interacting with the console. The console can be accessed from ::console or the standard input/output/error IO objects.
Requiring io/console adds the following methods:
Example:
require 'io/console' rows, columns = $stdin.winsize puts "Your screen is #{columns} wide and #{rows} tall"
Constants
Public Class Methods
Opens the file, optionally seeks to the given offset, then returns
length bytes (defaulting to the rest of the file).
binread
ensures the file is closed before returning. The open
mode would be “rb:ASCII-8BIT”.
IO.binread("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" IO.binread("testfile", 20) #=> "This is line one\nThi" IO.binread("testfile", 20, 10) #=> "ne one\nThis is line "
static VALUE rb_io_s_binread(int argc, VALUE *argv, VALUE io) { VALUE offset; struct foreach_arg arg; rb_scan_args(argc, argv, "12", NULL, NULL, &offset); FilePathValue(argv[0]); arg.io = rb_io_open(argv[0], rb_str_new_cstr("rb:ASCII-8BIT"), Qnil, Qnil); if (NIL_P(arg.io)) return Qnil; arg.argv = argv+1; arg.argc = (argc > 1) ? 1 : 0; if (!NIL_P(offset)) { rb_io_seek(arg.io, offset, SEEK_SET); } return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); }
Same as IO.write
except opening the file in binary mode and
ASCII-8BIT encoding (“wb:ASCII-8BIT”).
static VALUE rb_io_s_binwrite(int argc, VALUE *argv, VALUE io) { return io_s_write(argc, argv, 1); }
Returns an File instance opened console.
You must require 'io/console' to use this method.
static VALUE console_dev(VALUE klass) { VALUE con = 0; rb_io_t *fptr; 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)) { if ((fptr = RFILE(con)->fptr) && GetReadFD(fptr) != -1) return con; } rb_const_remove(klass, id_console); } { VALUE args[2]; #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; #ifdef CONSOLE_DEVICE_FOR_WRITING fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0); if (fd < 0) return Qnil; rb_update_max_fd(fd); args[1] = INT2FIX(O_WRONLY); args[0] = INT2NUM(fd); out = rb_class_new_instance(2, args, klass); #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; } rb_update_max_fd(fd); args[1] = INT2FIX(O_RDWR); args[0] = INT2NUM(fd); con = rb_class_new_instance(2, args, klass); GetOpenFile(con, fptr); #ifdef HAVE_RUBY_IO_H fptr->pathv = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE)); #else fptr->path = ruby_strdup(CONSOLE_DEVICE); #endif #ifdef CONSOLE_DEVICE_FOR_WRITING GetOpenFile(out, ofptr); # ifdef HAVE_RB_IO_GET_WRITE_IO ofptr->pathv = fptr->pathv; fptr->tied_io_for_writing = out; # else fptr->f2 = ofptr->f; ofptr->f = 0; # endif ofptr->mode |= FMODE_SYNC; #endif fptr->mode |= FMODE_SYNC; rb_const_set(klass, id_console, con); } return con; }
::copy_stream copies src to dst. src and dst is either a filename or an IO.
This method returns the number of bytes copied.
If optional arguments are not given, the start position of the copy is the beginning of the filename or the current file offset of the IO. The end position of the copy is the end of file.
If copy_length is given, No more than copy_length bytes are copied.
If src_offset is given, it specifies the start position of the copy.
When src_offset is specified and src is an IO, ::copy_stream doesn't move the current file offset.
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; if (NIL_P(length)) st.copy_length = (off_t)-1; else st.copy_length = NUM2OFFT(length); if (NIL_P(src_offset)) st.src_offset = (off_t)-1; else st.src_offset = NUM2OFFT(src_offset); rb_fd_init(&st.fds); rb_ensure(copy_stream_body, (VALUE)&st, copy_stream_finalize, (VALUE)&st); return OFFT2NUM(st.total); }
Synonym for 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; }
Executes the block for every line in the named I/O port, where lines are separated by sep.
If no block is given, an enumerator is returned instead.
IO.foreach("testfile") {|x| print "GOT ", x }
produces:
GOT This is line one GOT This is line two GOT This is line three GOT And so on...
If the last argument is a hash, it's the keyword argument to open. See
IO.read
for detail.
static VALUE rb_io_s_foreach(int argc, VALUE *argv, VALUE self) { VALUE opt; int orig_argc = argc; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); RETURN_ENUMERATOR(self, orig_argc, argv); open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_foreach, (VALUE)&arg, rb_io_close, arg.io); }
Returns a new IO object (a stream) for the given
integer file descriptor fd
and mode
string.
opt
may be used to specify parts of mode
in a
more readable fashion. See also ::sysopen and ::for_fd.
::new is called by various File and IO opening methods such as ::open, Kernel#open, and File.open.
Open Mode¶ ↑
When mode
is an integer it must be combination of the modes
defined in File::Constants
(File::RDONLY
, +File::WRONLY | File::CREAT+). See the open(2)
man page for more information.
When mode
is a string it must be in one of the following
forms:
fmode fmode ":" ext_enc fmode ":" ext_enc ":" int_enc fmode ":" "BOM|UTF-*"
fmode
is an IO open mode string,
ext_enc
is the external encoding for the IO and int_enc
is the internal encoding.
IO Open Mode¶ ↑
Ruby allows the following open modes:
"r" Read-only, starts at beginning of file (default mode). "r+" Read-write, starts at beginning of file. "w" Write-only, truncates existing file to zero length or creates a new file for writing. "w+" Read-write, truncates existing file to zero length or creates a new file for reading and writing. "a" Write-only, each write call appends data at end of file. Creates a new file for writing if file does not exist. "a+" Read-write, each write call appends data at end of file. Creates a new file for reading and writing if file does not exist.
The following modes must be used separately, and along with one or more of the modes seen above.
"b" Binary file mode Suppresses EOL <-> CRLF conversion on Windows. And sets external encoding to ASCII-8BIT unless explicitly specified. "t" Text file mode
When the open mode of original IO is read only, the mode cannot be changed to be writable. Similarly, the open mode cannot be changed from write only to readable.
When such a change is attempted the error is raised in different locations according to the platform.
IO Encoding¶ ↑
When ext_enc
is specified, strings read will be tagged by the
encoding when reading, and strings output will be converted to the
specified encoding when writing.
When ext_enc
and int_enc
are specified read
strings will be converted from ext_enc
to int_enc
upon input, and written strings will be converted from int_enc
to ext_enc
upon output. See Encoding for further details of transcoding on
input and output.
If “BOM|UTF-8”, “BOM|UTF-16LE” or “BOM|UTF16-BE” are used, ruby checks for
a Unicode BOM in the input document to help determine the encoding. For
UTF-16 encodings the file open mode must be binary. When present, the BOM
is stripped and the external encoding from the BOM is used. When the BOM
is missing the given Unicode encoding is used as ext_enc
.
(The BOM-set encoding option is case insensitive, so “bom|utf-8” is also
valid.)
Options¶ ↑
opt
can be used instead of mode
for improved
readability. The following keys are supported:
- :mode
-
Same as
mode
parameter - :external_encoding
-
External encoding for the IO. “-” is a synonym for the default external encoding.
- :internal_encoding
-
Internal encoding for the IO. “-” is a synonym for the default internal encoding.
If the value is nil no conversion occurs.
- :encoding
-
Specifies external and internal encodings as “extern:intern”.
- :textmode
-
If the value is truth value, same as “t” in argument
mode
. - :binmode
-
If the value is truth value, same as “b” in argument
mode
. - :autoclose
-
If the value is
false
, thefd
will be kept open after this IO instance gets finalized.
Also, opt
can have same keys in String#encode for controlling
conversion between the external encoding and the internal encoding.
Example 1¶ ↑
fd = IO.sysopen("/dev/tty", "w") a = IO.new(fd,"w") $stderr.puts "Hello" a.puts "World"
Produces:
Hello World
Example 2¶ ↑
require 'fcntl' fd = STDERR.fcntl(Fcntl::F_DUPFD) io = IO.new(fd, mode: 'w:UTF-16LE', cr_newline: true) io.puts "Hello, World!" fd = STDERR.fcntl(Fcntl::F_DUPFD) io = IO.new(fd, mode: 'w', cr_newline: true, external_encoding: Encoding::UTF_16LE) io.puts "Hello, World!"
Both of above print “Hello, World!” in UTF-16LE to standard error output
with converting EOL generated by puts
to CR.
static VALUE rb_io_initialize(int argc, VALUE *argv, VALUE io) { VALUE fnum, vmode; rb_io_t *fp; int fd, fmode, oflags = O_RDONLY; convconfig_t 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) == -1) 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 if (!NIL_P(opt) && rb_hash_aref(opt, sym_autoclose) == Qfalse) { fmode |= FMODE_PREP; } MakeOpenFile(io, fp); fp->fd = fd; fp->mode = fmode; fp->encs = convconfig; 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; }
With no associated block, IO.open
is a synonym for ::new. If the optional code block is
given, it will be passed io
as an argument, and the IO object will automatically be closed when the block
terminates. In this instance, ::open
returns the value of the block.
See ::new for a description of the
fd
, mode
and opt
parameters.
static VALUE rb_io_s_open(int argc, VALUE *argv, VALUE klass) { VALUE io = rb_class_new_instance(argc, argv, klass); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); } return io; }
Creates a pair of pipe endpoints (connected to each other) and returns them
as a two-element array of IO
objects: [
read_io, write_io ]
.
If a block is given, the block is called and returns the value of the block. read_io and write_io are sent to the block as arguments. If read_io and write_io are not closed when the block exits, they are closed. i.e. closing read_io and/or write_io doesn't cause an error.
Not available on all platforms.
If an encoding (encoding name or encoding object) is specified as an optional argument, read string from pipe is tagged with the encoding specified. If the argument is a colon separated two encoding names “A:B”, the read string is converted from encoding A (external encoding) to encoding B (internal encoding), then tagged with B. If two optional arguments are specified, those must be encoding objects or encoding names, and the first one is the external encoding, and the second one is the internal encoding. If the external encoding and the internal encoding is specified, optional hash argument specify the conversion option.
In the example below, the two processes close the ends of the pipe that
they are not using. This is not just a cosmetic nicety. The read end of a
pipe will not generate an end of file condition if there are any writers
with the pipe still open. In the case of the parent process, the
rd.read
will never return if it does not first issue a
wr.close
.
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
produces:
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; int fmode = 0; VALUE ret; argc = rb_scan_args(argc, argv, "02:", &v1, &v2, &opt); if (rb_pipe(pipes) == -1) 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); io_encoding_set(fptr, v1, v2, opt); 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 DEFAULT_TEXTMODE if ((fptr->mode & FMODE_TEXTMODE) && (fmode & FMODE_BINMODE)) { fptr->mode &= ~FMODE_TEXTMODE; setmode(fptr->fd, O_BINARY); } #if defined(RUBY_TEST_CRLF_ENVIRONMENT) || defined(_WIN32) 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; }
Runs the specified command as a subprocess; the subprocess's standard
input and output will be connected to the returned IO
object.
The PID of the started process can be obtained by #pid method.
cmd is a string or an array as follows.
cmd: "-" : fork commandline : command line string which is passed to a shell [env, cmdname, arg1, ..., opts] : command name and zero or more arguments (no shell) [env, [cmdname, argv0], arg1, ..., opts] : command name, argv[0] and zero or more arguments (no shell) (env and opts are optional.)
If cmd is a String
“-
'', then a
new instance of Ruby is started as the subprocess.
If cmd is an Array
of String
, then it
will be used as the subprocess's argv
bypassing a shell.
The array can contains a hash at first for environments and a hash at last
for options similar to spawn
.
The default mode for the new file object is “r'', but mode may be set to any of the modes listed in the description for class IO. The last argument opt qualifies mode.
# 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 # spawn option. See the document of Kernel.spawn. IO.popen(["ls", "/", :err=>[:child, :out]]) {|ls_io| ls_result_with_error = ls_io.read } # spawn options can be mixed with IO options IO.popen(["ls", "/"], :err=>[:child, :out]) {|ls_io| ls_result_with_error = ls_io.read }
Raises exceptions which IO.pipe
and Kernel.spawn
raise.
If a block is given, Ruby will run the command as a child connected to Ruby
with a pipe. Ruby's end of the pipe will be passed as a parameter to
the block. At the end of block, Ruby close the pipe and sets
$?
. In this case IO.popen
returns the value of
the block.
If a block is given with a cmd of “-
'', the
block will be run in two separate processes: once in the parent, and once
in a child. The parent process will be passed the pipe object as a
parameter to the block, the child version of the block will be passed
nil
, and the child's standard in and standard out will be
connected to the parent through the pipe. Not available on all platforms.
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 }
produces:
["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;
static VALUE rb_io_s_popen(int argc, VALUE *argv, VALUE klass) { const char *modestr; VALUE pname, pmode = Qnil, port, tmp, opt = Qnil, env = Qnil, execarg_obj = Qnil; int oflags, fmode; convconfig_t convconfig; 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); } } tmp = rb_check_array_type(pname); if (!NIL_P(tmp)) { long len = RARRAY_LEN(tmp); #if SIZEOF_LONG > SIZEOF_INT if (len > INT_MAX) { rb_raise(rb_eArgError, "too many arguments"); } #endif tmp = rb_ary_dup(tmp); RBASIC_CLEAR_CLASS(tmp); execarg_obj = rb_execarg_new((int)len, RARRAY_PTR(tmp), FALSE); rb_ary_clear(tmp); } else { SafeStringValue(pname); execarg_obj = Qnil; if (!is_popen_fork(pname)) execarg_obj = rb_execarg_new(1, &pname, TRUE); } if (!NIL_P(execarg_obj)) { if (!NIL_P(opt)) opt = rb_execarg_extract_options(execarg_obj, opt); if (!NIL_P(env)) rb_execarg_setenv(execarg_obj, env); } rb_io_extract_modeenc(&pmode, 0, opt, &oflags, &fmode, &convconfig); modestr = rb_io_oflags_modestr(oflags); port = pipe_open(execarg_obj, modestr, fmode, &convconfig); if (NIL_P(port)) { /* child */ if (rb_block_given_p()) { rb_yield(Qnil); rb_io_flush(rb_stdout); rb_io_flush(rb_stderr); _exit(0); } return Qnil; } RBASIC_SET_CLASS(port, klass); if (rb_block_given_p()) { return rb_ensure(rb_yield, port, io_close, port); } return port; }
Opens the file, optionally seeks to the given offset
, then
returns length
bytes (defaulting to the rest of the file).
read
ensures the file is closed before returning.
If the last argument is a hash, it specifies option for internal open(). The key would be the following. open_args: is exclusive to others.
- encoding
-
string or encoding
specifies encoding of the read string.
encoding
will be ignored if length is specified. - mode
-
string
specifies mode argument for open(). It should start with “r” otherwise it will cause an error.
- open_args
-
array of strings
specifies arguments for open() as an array.
Examples:
IO.read("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" IO.read("testfile", 20) #=> "This is line one\nThi" IO.read("testfile", 20, 10) #=> "ne one\nThis is line "
static VALUE rb_io_s_read(int argc, VALUE *argv, VALUE io) { VALUE opt, offset; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, &offset, NULL, &opt); open_key_args(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); }
Reads the entire file specified by name as individual lines, and returns those lines in an array. Lines are separated by sep.
a = IO.readlines("testfile") a[0] #=> "This is line one\n"
If the last argument is a hash, it's the keyword argument to open. See
IO.read
for detail.
static VALUE rb_io_s_readlines(int argc, VALUE *argv, VALUE io) { VALUE opt; struct foreach_arg arg; argc = rb_scan_args(argc, argv, "13:", NULL, NULL, NULL, NULL, &opt); open_key_args(argc, argv, opt, &arg); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_readlines, (VALUE)&arg, rb_io_close, arg.io); }
Calls select(2) system call. It monitors given arrays of IO
objects, waits one or more of IO
objects ready for reading,
are ready for writing, and have pending exceptions respectively, and
returns an array that contains arrays of those IO
objects. It will return nil
if optional timeout
value is given and no IO
object is ready in timeout
seconds.
IO.select
peeks the buffer of IO
objects for
testing readability. If the IO
buffer is not empty,
IO.select
immediately notify readability. This “peek” is only
happen for IO
objects. It is not happen for IO-like objects
such as OpenSSL::SSL::SSLSocket.
The best way to use IO.select
is invoking it after nonblocking
methods such as read_nonblock
, write_nonblock
,
etc. The methods raises an exception which is extended by
IO::WaitReadable
or IO::WaitWritable
. The modules
notify how the caller should wait with IO.select
. If
IO::WaitReadable
is raised, the caller should wait for
reading. If IO::WaitWritable
is raised, the caller should wait
for writing.
So, blocking read (readpartial
) can be emulated using
read_nonblock
and IO.select
as follows:
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
Especially, the combination of nonblocking methods and
IO.select
is preferred for IO
like objects such
as OpenSSL::SSL::SSLSocket
. It has to_io
method
to return underlying IO
object. IO.select
calls
to_io
to obtain the file descriptor to wait.
This means that readability notified by IO.select
doesn't
mean readability from OpenSSL::SSL::SSLSocket
object.
Most possible situation is OpenSSL::SSL::SSLSocket
buffers
some data. IO.select
doesn't see the buffer. So
IO.select
can block when
OpenSSL::SSL::SSLSocket#readpartial
doesn't block.
However several more complicated situation exists.
SSL is a protocol which is sequence of records. The record consists
multiple bytes. So, the remote side of SSL sends a partial record,
IO.select
notifies readability but
OpenSSL::SSL::SSLSocket
cannot decrypt a byte and
OpenSSL::SSL::SSLSocket#readpartial
will blocks.
Also, the remote side can request SSL renegotiation which forces the local
SSL engine writes some data. This means
OpenSSL::SSL::SSLSocket#readpartial
may invoke
write
system call and it can block. In such situation,
OpenSSL::SSL::SSLSocket#read_nonblock
raises IO::WaitWritable<