Marcos Castilho

Ruby File IO Primer - Part 1 - The File Class

June 06, 2011

Ruby offers a very powerful API for File IO, allowing for very complex operations with very little code, but the myriad of methods and classes with similar names can be a little confusing for the Ruby uneducated like me.

The File Class

The File class, built in the language (you don't have to require it), offers the most commonly needed facilities for manipulating files in Ruby. Like all other IO things in Ruby, File is an subclass of the IO, which mixes in the Enumerable module. Let's go over the basics.

Reading from a File

The simplest way to get a file is just calling File.new with the correct path (you get an exception otherwise). You can then call File.read to get the entire content of the file as a string.

  f = File.new("lib/file.rb")
  content = f.read
  f.close

That may suffice for some simple uses, but most of the times we want a little more control. You can read the file line by line with the File.readline method or the File.gets method. The difference is that File.readline throws an exceptions if the file is over, while File.gets just returns nil.

  f = File.new("lib/file.rb")
  while line = f.gets
    puts line
  end
  f.close

You can also read the file char by char, with the File.readchar, or byte by byte with the File.readbyte. Both have their get* counterpart, with similar behaviour to those described above. The amount of bytes in a file depends on the encoding.

Since File is an Enumerable, you can also use File.each or File.each_line to loop through the file and access each line with a block.

  f = File.new("lib/file.rb")
  f.each do |line|
    #do something with the line
  end
  f.close

You can have even more read precision by moving the File "inner pointer" with more low level methods like File.seek.

Writing into a File

To write into a file you must first open it on write or append mode. Write mode creates the file if it does not exist or overwrites the old version, and is indicated by a second argument 'w' passed to the File.open method. Append mode creates a file if it doesn't exist or appends to the existing content, and is indicated by the 'a' flag.

The methods used to write on a File instance are puts, which places a newline after the content, or print, that does not append the newline. You can also use File.write, witch returns the number of bytes written into the file.

  f = File.new('lib/file.rb', 'w')
  f.puts("a new line will be appended")
  f.print("no new line")
  f.print(" at all")
  puts f.read
  >> "a new line will be appended\nno new line at all"
  f.close

Using blocks

When you use the File.new method, you have to manually close the file with the File.close method. That is quite boring and also prone to mistakes. A better approach is the File.open method, that pass the file to a code block and then closes the file for you. The open method accepts the same filemode flags as new. If you provide no code blocks, File.open is identical to File.new.

  File.open("lib/file.rb") do |f|
    f.each do
      #do something
    end  
  end

The Enumerability of Files

The fact that File is an Enumerable gives the class a lot of nifty abilities. Its a little strange at first, thinking about files as an array of lines, but you get used to it very quickly. Here are some cool samples.

  #counting commented lines 
  File.open("lib/file.rb").count{ |line| line.starts_with?("#") } 

  #get all the file require lines as an array
  File.open("lib/file.rb").grep(/^require/)

  #the avg of words from each line
  File.open("lib/file.rb").inject(0) do |total, line|
    total += line.split.size
  end