File I/O in Ruby
Reading and writing data to external sources, like disk files, is known as file input/output, or I/O, in programming. Ruby’s fundamental classes IO and File are mostly responsible for handling I/O operations.
A stream is a readable source or a writable sink for bytes or characters, and it is represented by the IO class. Reading, writing, and other aspects of file manipulation are covered in great detail throughout the sources and are frequently introduced in basic programming exercises such as “Reading Files” and “Reading and Writing Files”.
Opening and Closing Files
You must use the File.open
method or File.new
to open a file before you can work with it. Blocks are used as the primary mechanism for automatic closure, which guarantees that files are handled correctly.
File Open Modes
The second argument when opening a file is a mode string that specifies how the file should be accessed. This mode string needs to start with one of the values listed below:
Mode | Description |
"r" | Open for reading only (the default mode). Starts at the beginning of the file. |
"r+" | Open for reading and writing. Starts at the beginning of the file. |
"w" | Open for writing only. Creates a new file or truncates an existing file to zero length. |
"w+" | Open for reading and writing. Creates a new file or truncates an existing one. |
"a" | Open for appending (write-only). Starts writing at the end of the file if it exists, otherwise creates a new file. |
"a+" | Open for appending and reading. Allows reads also. |
Moreover, the binary file mode can be specified by appending the letter “b
” to the mode string (for example, “rb
“), which prevents Windows systems from converting End-of-Line (EOL) to Carriage Return-Line Feed (CRLF).
Automatic Resource Management with Blocks
It is advised to utilize File.open
with a related block when working with files. File.open
automatically closes the file when the block has completed running and sends the open file object to the block when one is supplied. This procedure guarantees that, even in the event of an exception, files are appropriately closed.
Code Example (Automatic Closing):
# The "w" mode ensures that if the file doesn't exist, it will be created.
# If it does exist, it will be overwritten.
# The `File.open` method is wrapped in a begin/rescue block to handle potential permission errors.
begin
File.open("test.txt", "w") do |f|
f.puts "This is some test content."
puts "Successfully wrote to 'test.txt'."
end
rescue Errno::EACCES
puts "Permission denied: Could not write to 'test.txt'."
puts "Please check the file permissions for the directory or the file itself."
end
Output
Permission denied: Could not write to 'test.txt'.
Please check the file permissions for the directory or the file itself.
In the absence of a block, File.open
returns the File
object itself, and the programmer must manually use the close
method.
Reading Files
If the stream is opened for reading ("r"
or "r+"
modes), the many methods in the IO
class for reading content from streams will function.
Reading Entire Contents
You can use class methods of IO
or File
to read the contents of a named file without starting an IO
stream explicitly:
Read into a single string (IO.read
/ File.read
): Returns the full file as a single string after reading it (or a designated section of it).
Read into an array of lines (IO.readlines
/ File.readlines
): Reads the whole file into an array, with each line—including the line terminator—representing an element.
Reading Iteratively (Line-by-Line)
Since iteration methods only load one line into memory at a time, they are effective for processing content line by line, particularly when working with big files.
Using the each iterator: This results in text lines that are read from the IO
object. By taking a filename, opening it, iterating over the lines, and then automatically shutting the file, the IO.foreach
method makes this process simpler.
Using gets or readline: With these techniques, a single line is read
. At the end of the file, readline
produces an EOFError, while gets returns nil
(EOF).
Reading Binary or Bytes
Regarding byte-level access:
IO#read(int)
: Reads the stream’s maximum number of int
bytes.
IO#each_byte
: Each 8-bit byte read from the stream is passed as an integer (0 to 255) to a block, which is called once.
Writing Files
Opening the file in the appropriate mode (such as "w"
, "w+"
, "a"
, or "a+"
) is necessary in order to write data.
Writing Text
Several ways to write strings to the stream are provided by the IO
class:
Using the stream append operator (<<
): Converts the object to a string and writes it to the stream.
Using IO#puts
: Writes objects that have been converted to strings and, unless the string already ends with one, makes sure a newline character is added.
Using IO#write
: The number of bytes written is returned, along with its sole parameter (converted to a string).
Code Example (Writing with open block):
begin
# The 'w' mode opens the file for writing, creating it if it doesn't exist.
open('beans.txt', "w") do |file|
file.puts('lima beans')
file.puts('pinto beans')
end
puts "Successfully wrote to 'beans.txt'."
rescue Errno::EACCES
puts "Permission denied: Could not write to 'beans.txt'."
puts "Please check your permissions for the current directory or the file itself."
end
Permission denied: Could not write to 'beans.txt'.
Please check your permissions for the current directory or the file itself.
Code Example (Simple write operation):
The File.write
method offers a shorthand for basic write operations that manages opening and shutting automatically. The file is overwritten by default when using this approach.
begin
# This first line overwrites the file or creates it if it doesn't exist.
File.write('tmp.txt', "NaNaNaNa\n" * 4 + 'Batman!')
# This second line appends "More data." to the end of the file.
File.write('tmp.txt', "More data.\n", { mode: 'a' })
puts "Successfully wrote to 'tmp.txt'."
rescue Errno::EACCES
puts "Permission denied: Could not write to 'tmp.txt'."
puts "Please check the file permissions for the directory or the file itself."
end
Output
Permission denied: Could not write to 'tmp.txt'.
Please check the file permissions for the directory or the file itself.
Low-Level and Random Access I/O
Lower-level I/O methods are available for efficiency or necessity (e.g., processing binary files where precise byte offsets matter).
Low-Level I/O: Low-level unbuffered operations are carried out by sysread
and syswrite
. Avoid combining these techniques with other buffered reading or writing techniques on the same stream since this could lead to unexpected outcomes.
Random Access: Some streams allow random access, such as disk files but not network sockets. Among the techniques are:
IO#pos
/ IO#tell
: Gives back in bytes the file cursor’s current location.
IO#pos=
: Sets a given byte position for the file cursor.
IO#seek(offset, whence)
: Adjusts the cursor’s location in relation to a given starting point (whence), such as the file’s beginning (IO::SEEK_SET
), current position (IO::SEEK_CUR
), or end (IO::SEEK_END
).
You can also read What Is Mean By Mixins In Ruby With Practical Code Examples