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.
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 🙂