Skip to content

Ruby File I/O

Ruby provides a comprehensive set of I/O (Input/Output) related methods, primarily through the IO class and its subclasses like File. Many basic I/O operations are also available as methods in the Kernel module, making them globally accessible.

The IO class offers fundamental methods such as read, write, gets, puts, readline, getc, and printf. This chapter covers basic I/O functions and file/directory manipulation in Ruby.

puts (put string) writes its arguments to the standard output (STDOUT), each followed by a newline character. If an argument is an array, it writes each element on a new line. If called without arguments, it just prints a newline.

#!/usr/bin/env ruby
# frozen_string_literal: true
val1 = "This is variable one"
val2 = "This is variable two"
puts val1
puts val2
puts [1, 2, 3] # Prints each number on a new line

This produces:

This is variable one
This is variable two
1
2
3

gets (get string) reads a line of input from the standard input (STDIN). It returns the line including the trailing newline character, or nil if end-of-file is reached. Use chomp to remove the newline.

#!/usr/bin/env ruby
# frozen_string_literal: true
puts "Enter your name:"
name = gets
puts "Hello, #{name.chomp}! You entered: #{name.inspect}" # .chomp removes trailing newline
puts "Enter your age:"
age_str = gets.chomp
if age_str
age = age_str.to_i
puts "You will be #{age + 5} in 5 years."
else
puts "No age entered."
end

Example interaction:

Enter your name:
Alice
Hello, Alice! You entered: "Alice\n"
Enter your age:
30
You will be 35 in 5 years.

putc (put character) writes a single character to standard output. If given a string, it writes the first character. If given an integer, it writes the character corresponding to that byte value.

#!/usr/bin/env ruby
# frozen_string_literal: true
str = "Hello Ruby!"
putc str # Outputs: H
putc 65 # Outputs: A (ASCII value for 'A')

print is similar to puts but does not automatically append a newline. It calls to_s on its arguments and writes them to STDOUT.

#!/usr/bin/env ruby
# frozen_string_literal: true
print "Hello World"
print " Good Morning"
print "\nNext line."

This produces:

Hello World Good Morning
Next line.

The File.open method is used to open a file. It can take a block, which is the recommended way as it ensures the file is automatically closed when the block finishes, even if errors occur.

# Syntax with a block (recommended)
File.open("filename.txt", "mode") do |file_object|
# ... process the file using file_object ...
end # File is automatically closed here
# Syntax without a block (manual close needed)
# aFile = File.new("filename.txt", "mode")
# ... process the file ...
# aFile.close

File.new is similar to File.open but does not accept a block.

File Modes:

ModeDescription
rRead-only. Starts at the beginning of the file (default mode).
r+Read-write. Starts at the beginning of the file.
wWrite-only. Truncates existing file to zero length or creates a new file for writing.
w+Read-write. Truncates existing file or creates a new file for reading and writing.
aWrite-only (append). Starts at the end of the file if it exists, or creates a new file. Data is always written to the end.
a+Read-write (append). Starts at the end for writing. Reading can happen from any position. Creates a new file if it doesn’t exist.
bBinary file mode (optional, can be combined with other modes e.g., “rb”, “wb+”). On some systems (like Windows), this influences newline handling.

Once a file is open, you can use IO methods like read, gets, puts, write on the file object.

The sysread and syswrite Methods (Low-Level):

Section titled “The sysread and syswrite Methods (Low-Level):”

sysread reads a specified number of bytes from the file directly from the OS buffer, bypassing Ruby’s internal buffering. syswrite does the same for writing. These are less common than buffered I/O methods.

#!/usr/bin/env ruby
# frozen_string_literal: true
# Create a sample file
File.open("sample.txt", "w") { |f| f.puts "This is line one."; f.puts "This is line two." }
# Using sysread
begin
File.open("sample.txt", "r") do |file|
content_chunk = file.sysread(20) # Read up to 20 bytes
puts "First 20 bytes (sysread): #{content_chunk.inspect}"
end
rescue EOFError
puts "sysread: End of file reached prematurely."
end
# Using syswrite (overwrites beginning of file)
begin
File.open("sample.txt", "r+") do |file|
bytes_written = file.syswrite("HELLO ")
puts "Bytes written (syswrite): #{bytes_written}"
end
rescue SystemCallError => e
puts "syswrite error: #{e.message}"
end
# Check content after syswrite
puts "Content after syswrite: #{File.read('sample.txt').inspect}"

Iterates over each byte of the file (as an integer).

#!/usr/bin/env ruby
File.open("sample.txt", "r") do |file|
puts "Bytes in sample.txt:"
file.each_byte { |byte| print "#{byte.chr}." if byte < 128 } # Print printable ASCII chars
puts
end

The IO.readlines and File.readlines Methods:

Section titled “The IO.readlines and File.readlines Methods:”

Reads the entire file into an array of strings, where each element is a line from the file (including newlines). Use with caution for very large files as it loads everything into memory.

#!/usr/bin/env ruby
# frozen_string_literal: true
begin
lines_array = File.readlines("sample.txt") # IO.readlines is similar
puts "Line 0: #{lines_array[0].chomp if lines_array[0]}"
puts "Line 1: #{lines_array[1].chomp if lines_array[1]}"
rescue Errno::ENOENT
puts "File not found."
end

Iterates over each line of the file, passing it to a block. More memory-efficient for large files than readlines as it processes line by line.

#!/usr/bin/env ruby
# frozen_string_literal: true
begin
File.foreach("sample.txt") do |line|
puts "Processing line: #{line.chomp}"
end
rescue Errno::ENOENT
puts "File not found."
end

Other common reading methods: File.read(filename) reads the entire file content into a single string. file.read(length) reads length bytes.

File.rename(old_name, new_name) renames a file.

File.delete(filename) or File.unlink(filename) deletes a file.

#!/usr/bin/env ruby
# Create a file to work with
File.write("original.txt", "Some content.")
# Rename the file
if File.exist?("original.txt")
File.rename("original.txt", "renamed.txt")
puts "original.txt renamed to renamed.txt"
end
# Delete the file
if File.exist?("renamed.txt")
File.delete("renamed.txt")
puts "renamed.txt deleted"
end

Use File.chmod(mode, filename, ...) to change the permissions (mode) of one or more files. The mode is an octal number.

#!/usr/bin/env ruby
File.write("script.sh", "#!/bin/bash\necho Hello")
# Make it executable by owner, readable by owner/group/others
File.chmod(0755, "script.sh")
puts "Permissions for script.sh changed."
# File.delete("script.sh") # Cleanup

Common octal permission masks:

MaskDescription (Permissions)
0700rwx------ (Owner: read, write, execute)
0600rw------- (Owner: read, write)
0755rwxr-xr-x (Owner: rwx; Group: rx; Others: rx) - Common for executables/directories
0644rw-r—r— (Owner: rw; Group: r; Others: r) - Common for regular files

Ruby provides many methods to query file attributes (often ending with ?):

#!/usr/bin/env ruby
# frozen_string_literal: true
filename = "sample.txt"
File.write(filename, "Test content for inquiries.") unless File.exist?(filename)
puts "File '#{filename}' exists? #{File.exist?(filename)}" # => true
puts "Is it a file? #{File.file?(filename)}" # => true
puts "Is it a directory? #{File.directory?(filename)}" # => false
puts "Is it readable? #{File.readable?(filename)}" # => true (usually)
puts "Is it writable? #{File.writable?(filename)}" # => true (usually)
puts "Is it executable? #{File.executable?(filename)}" # => false (for a text file usually)
puts "Is it zero size? #{File.zero?(filename)}" # => false (if it has content)
puts "Size: #{File.size(filename)} bytes" # => (size of content)
puts "Size (or nil if zero): #{File.size?(filename)} bytes" # => (size of content, or nil if zero)
puts "File type: #{File.ftype(filename)}" # => "file"
puts "Created time: #{File.ctime(filename)}"
puts "Modified time: #{File.mtime(filename)}"
puts "Last accessed time: #{File.atime(filename)}"
# File.delete(filename) # Cleanup

The ftype method can return: file, directory, characterSpecial, blockSpecial, fifo, link, socket, or unknown.

The Dir class is used for directory operations.

Dir.chdir(path): Changes the current working directory.

Dir.pwd: Returns the current working directory path.

Dir.entries(path): Returns an array of all entries (files and directories, including . and ..) in the specified path.

Dir.foreach(path) { |entry| ... }: Iterates over entries.

Dir.glob(pattern) or Dir[pattern]: Returns an array of filenames matching the pattern (wildcards like *, ?, []).

#!/usr/bin/env ruby
# frozen_string_literal: true
puts "Current directory: #{Dir.pwd}"
# List entries in current directory (first 5 for brevity)
puts "\nEntries in '.':"
Dir.entries(".").first(5).each { |entry| puts " #{entry}" }
puts "\nRuby files in '.':"
Dir.glob("*.rb").each { |file| puts " #{file}" }
# Or: Dir["*.rb"].each { |file| puts " #{file}" }

Dir.mkdir(path, mode=0777): Creates a directory. mode specifies permissions.

Dir.delete(path) or Dir.rmdir(path) or Dir.unlink(path): Deletes an empty directory.

#!/usr/bin/env ruby
# frozen_string_literal: true
dir_name = "mynewdir"
begin
unless Dir.exist?(dir_name)
Dir.mkdir(dir_name, 0755)
puts "Directory '#{dir_name}' created."
else
puts "Directory '#{dir_name}' already exists."
end
# ... do something with the directory ...
if Dir.exist?(dir_name)
Dir.delete(dir_name)
puts "Directory '#{dir_name}' deleted."
end
rescue SystemCallError => e
puts "Directory operation failed: #{e.message}"
end

For temporary files, Ruby’s standard library tmpdir and tempfile are useful.

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'tmpdir' # For Dir.tmpdir
require 'tempfile' # For Tempfile class
# Using Dir.tmpdir (less common for direct file creation)
temp_dir_path = Dir.tmpdir
custom_temp_filename = File.join(temp_dir_path, "my_app_temp_#{Process.pid}.dat")
puts "Custom temp file might be at: #{custom_temp_filename}"
# Manual creation and deletion needed if using this approach directly.
# Using Tempfile (recommended for temporary files)
begin
temp_file = Tempfile.new('app_data_prefix') # Creates a temp file with a unique name
temp_file.puts "This is some temporary data."
temp_file.puts "It will be automatically deleted when the object is finalized or closed."
puts "Tempfile path: #{temp_file.path}"
temp_file.rewind # Go back to the beginning to read
puts "Tempfile content: #{temp_file.read.chomp}"
ensure
# Tempfile handles its own deletion upon garbage collection or explicit close and unlink
temp_file.close # Closes the file
temp_file.unlink # Deletes the file from the filesystem (optional, close often does it)
puts "Tempfile closed and unlinked."
end

Built-in Functions for Files and Directories

Section titled “Built-in Functions for Files and Directories”

Ruby’s File and Dir classes, along with methods in FileUtils (a standard library module require 'fileutils'), offer a rich API for all common file and directory manipulations. Consult the official Ruby documentation for a complete list of methods and their options.