I/O stands for Input/Output.
By input we mean all the data & information that comes into something (computer, Ruby method, your brain).
Examples of input:
- Keys your press on the keyboard
- Mouse click
- Books you read
By output we mean everything that comes out as a result of the input.
Examples of output:
- The result of 1 + 1
- Writing a summary of an article you read
- Coffee
In Ruby, when we talk about i/o we usually refer to reading files, working with network sockets & printing information on the screen.
Understanding The IO Class
IO is also a class in Ruby.
It’s the parent class for other objects like File
& Socket
.
All IO objects have these operations:
- open / close
- read
- write
IO objects are based on file descriptors.
Every running process in your computer has an internal information table.
Part of this table includes a list of file descriptors.
A file descriptor is a mapping between an integer & a file name.
You can see this list of file descriptors in Linux.
With this command:
# Assuming you have irb open on another window ls -lh /proc/`pgrep irb`/fd/ # lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 1 -> /dev/pts/3 # lrwx------ 1 jesus jesus 64 Feb 3 15:54 2 -> /dev/pts/3
You can create a new file descriptor in Ruby with the sysopen
method.
Example:
fd = IO.sysopen('/dev/null', 'w') # 10
Now you can open a communication channel with this file descriptor (10
).
Like this:
io = IO.new(fd)
And write to it:
io << 'orange'
Notice that IO
doesn't care if your file descriptor maps to an actual file, a device (like a hard disk), or a network socket.
How to Open A File Descriptor For Writing
When you create a new file descriptor using sysopen
, or open a file using File.open
, you have a few options.
By default the communication is read-only.
You can specify a mode:
r
(read, default)w
(write)w+
(read & write)a
(append)
Example:
fd = IO.sysopen('/dev/tty1', 'w+')
This opens /dev/tty1
with read + write mode.
3 Standard IO Streams
When you open a new program (your editor, terminal, Ruby interpreter, etc.) it opens 3 channels of communication by default.
3 file descriptors.
They are:
- stdin (input)
- stdout (output)
- stderr (errors)
In Ruby you can access these directly, using their constant form, or their global variable form.
Example:
$stdout << "abc\n"
When you write to $stdout
, it's the same as calling the puts method.
Standard output is associated with your terminal.
lrwx------ 1 jesus jesus 64 Feb 3 15:54 0 -> /dev/pts/3
This /dev/pts/3
is a virtual device that your terminal reads from to display text on the screen.
Standard error is also used for output, but it gives your terminal a chance to present this content in a different way, like in a bigger font, or in a different color than regular text.
You can use the warn
method to print to standard error.
Standard input is where user input is read from.
You can use the gets
method to read from standard input.
How to Flush STDOUT & Disable Buffering
IO objects have an internal buffer.
A certain amount of input data will be saved before it can be read.
That's why sometimes you use puts
...
But you don't see anything until the program ends, or the buffer is full!
You can disable buffering like this:
$stdout.sync = true
Or you can manually flush the buffer:
$stdout.flush
With the buffer disabled you'll see the output without having to wait.
Reading In Chunks
When you're reading from an IO object, you read
until the stream ends with an END-OF-FILE (EOF) signal, the stream is closed, or you receive a specific amount of bytes.
For network programming & big files, it's helpful to read in small chunks.
You can read in chunks like this:
data = "" until io.eof? data << io.read(2048) end
With this code you can process data as it becomes available, without having to wait for the whole download to finish.
Notice that eof?
will "block".
Blocking means that your code will go to sleep until the operating system signals that there is new data to be read.
How to Use IO Pipes For Two-Way Communication
Ruby allows you to create pipes.
A pipe is a pair of file descriptors that are connected to each other.
This is used for Inter-Process Communication (IPC).
Example:
read, write = IO.pipe
You can use this with the fork
method (not available in Windows) to create a copy of the current process.
These two processes can communicate using the pipe.
Example:
if fork write.close puts "Message received: #{read.gets}" read.close else read.close write << "Buy some bananas!" write.close end
Which results in:
# Message received: Buy some bananas!
How to Use The Kernel Open Method
Ruby has a very flexible open
method.
It allows you to open files & read data from a URL, and even open pipes to run external commands.
Example:
open "abc.txt"
That's why this method is to be avoided.
It's a security risk that it can do all these things.
Instead, it's better to use the specific open
method that you need.
Example:
File.open # File IO.popen # Process open URI.parse(...).open # URL open
All of these return an IO
object.
Summary
You've learned about input/output in Ruby! You've learned about file descriptors, the IO
class, buffering, reading in chunks & standard IO
streams.
Don't forget to share this article if you like it.
Thanks for reading.
Thank you so much for this post! I think this is an area of Ruby that a lot of us can work for years without properly touching, so really appreciate you putting this together 🙂
Thanks for reading & leaving a comment! 🙂
Another of your posts that taught me something new. Thank you.
Thanks for reading David! I’m glad you found it useful 🙂
Great content! Keep it up 🙂
Thank you! 🙂