56

All of the following API do the same thing: open a file and call a block for each line. Is there any preference we should use one than another?

File.open("file").each_line {|line| puts line}
open("file").each_line {|line| puts line}   
IO.foreach("file") {|line | puts line}
David Grayson
  • 84,103
  • 24
  • 152
  • 189
pierrotlefou
  • 39,805
  • 37
  • 135
  • 175

1 Answers1

90

There are important differences beetween those 3 choices.

File.open("file").each_line { |line| puts line }

  • File.open opens a local file and returns a file object
  • the file stays open until you call IO#close on it

open("file").each_line { |line| puts line }

Kernel.open looks at the string to decide what to do with it.

open(".irbrc").class # => File
open("http://google.com/").class # => StringIO
File.open("http://google.com/") # => Errno::ENOENT: No such file or directory - http://google.com/

In the second case the StringIO object returned by Kernel#open actually holds the content of http://google.com/. If Kernel#open returns a File object, it stays open untill you call IO#close on it.

IO.foreach("file") { |line| puts line }

  • IO.foreach opens a file, calls the given block for each line it reads, and closes the file afterwards.
  • You don't have to worry about closing the file.

File.read("file").each { |line| puts line }

You didn't mention this choice, but this is the one I would use in most cases.

  • File.read reads a file completely and returns it as a string.
  • You don't have to worry about closing the file.
  • In comparison to IO.foreach this makes it clear, that you are dealing with a file.
  • The memory complexity for this is O(n). If you know you are dealing with a small file, this is no drawback. But if it can be a big file and you know your memory complexity can be smaller than O(n), don't use this choice.

It fails in this situation:

 s= File.read("/dev/zero") # => never terminates
 s.each …

ri

ri is a tool which shows you the ruby documentation. You use it like this on your shell.

ri File.open
ri open
ri IO.foreach
ri File#each_line

With this you can find almost everything I wrote here and much more.

johannes
  • 7,262
  • 5
  • 38
  • 57
  • Thanks for the pointer to ri. It could be more friendly, though. "ri File.open" doesn't find anything for me. Trying to find "open" I eventually find Kernel#orig_open and then try looking up "Kernel#open" only to be told there are multiple matches without any apparent way to just give me the docs for exactly Kernel#open instead of searching for Kernel#open*. – Wodin Oct 06 '12 at 10:10
  • 11
    If you pass a block to `File.open` it will be closed automatically when the block terminates, no need to call `IO#close` – Tombart Feb 05 '13 at 21:34
  • 13
    File is a subclass of IO, so you can use File.foreach instead of IO.foreach if you want to make it more self-documenting that you are working with a file. Be aware that File.read().each is loading the entire file into memory. If you're working with large files this may not be advisable. I would stick with File.foreach. – Jason Heiss Dec 12 '13 at 21:40
  • 1
    Thank you for the RI tip. That's something so useful. – Nikkolasg Oct 02 '14 at 08:31
  • `File.read("file").each { |line| puts line }` doesn't seem to work in all cases as it cannot process it line-by-line. My failing example is a plain text file with `\r\n` line endings. – Joshua Pinter Jul 02 '15 at 15:12
  • If you need access to the line numbers of the file (or index), you can chain `with_index` onto `File.foreach`, like so: `File.foreach("file").with_index { |line, index| row = index + 1 }` – Joshua Pinter Jul 02 '15 at 15:28
  • I needed to run `rvm docs generate` . (I know, I´m new to ruby) – morhook Feb 14 '18 at 14:22
  • So when i have to deal with large files and want to take care of memory I always should use `IO.foreach (File.foreach)`. That i know, but I always thought that `File.open().each_line` was a alias for `IO.foreach`. That is wrong right? I couldn't find out clearly in relation of memory usage. – Quackerjack Jul 18 '18 at 14:37