Reputation: 11
I've found the following bit of code used frequently when you want to read in a file line-by-line in Ruby:
while (line = fileobject.gets)
# code block such as 'puts line' or something
end
I just need some help understanding what is going on there. I know that 'while' is to be followed by a boolean expression, and then the code block will be repeated until the expression returns 'false'.
So here, the boolean expression is line = fileobject.gets
...but how is that evaluated as true or false? To me it looks like an assignment statement, that is, you're assigning 'line' to be whatever the next line of the fileobject is.
I understand that this WILL work for reading in from text files line by line, but I'm not comfortable using it until I know WHY it works. Maybe I'm just too used to C++ with its counters and incrementing. Thanks!
Upvotes: 1
Views: 2108
Reputation: 84343
while (line = fileobject.gets); end
This isn't really idiomatic Ruby; it's more of a Perl-ish way of doing things, although it certainly works. The reason it works is that almost everything in Ruby is an expression that returns some sort of value. For example:
# Open some file for reading.
fileobject = File.open '/etc/passwd'
# As long as File#gets evaluates as truthy, keep going.
while line = fileobject.gets
puts line
end
The "magic" here is that the expression line = fileobject.gets
returns nil when it reaches EOF, and so the while condition evaluates as falsey. Until then, every time fileobject.gets
returns a string the expression evaluates as truthy, so the while-loop just keeps chugging along and assigning successive lines to your line variable. See IO#gets for more on this useful method.
A more idiomatic way to do this would be to do this in a self-closing block. for example:
File.open '/etc/passwd' do |file|
file.each_line { |line| puts line }
end
Self-closing blocks are (possibly) just as "magical" as while-loops that leverage the truthiness of IO#gets, but it certainly seems clearer and more Ruby-like than the original code sample. YMMV.
As another answer reminded me, the following is also very idiomatic in Ruby:
IO.foreach('/etc/passwd') { |line| puts line }
This leverages IO::foreach to implicitly open the named file, pass each line through the block for processing, and then closing the file automatically when the block ends. It is very similar to the previous example above, but has the advantage of being shorter and (in some cases) semantically clearer. Whether or not the implicit file handling is too "magical" or not for your taste, it's definitely worth knowing this particular construct too.
Upvotes: 1
Reputation: 48599
I've found the following bit of code used frequently when you want to read in a file line-by-line
I don't know what articles you've been reading, but no well grounded rubyist would ever read a file like that. Instead they would do something like this:
IO.foreach('data1.txt') do |line|
print line
end
As for this:
line = fileobject.gets
In order for ruby to execute that assignment, ruby has to first execute fileobject.gets
. And fileobject.gets
either returns a string or nil(when end of file is reached). And any string is considered true in ruby--even blank strings. For example:
str = ""
if str
puts "true"
else
puts "false"
end
--output:--
true
So the code:
line = fileobject.gets
is equivalent to:
line = "some string"
or
line = nil
Finally, an assignment returns the right hand side, so you are left with:
while "some string"
or
while nil
and ruby evaluates while "some string"
as while true
; and ruby evaluates while nil
as while false
because in ruby only nil and false are false in a boolean context, e.g. in an if or while conditional.
Upvotes: 1
Reputation: 18351
The first principle that will help you understand this is that in Ruby, conditions aren't expected to be just true or false, they can be any value. The value is then considered 'truthy' or 'falsey' - that is - like being true of like being false. In Ruby, there are only two values that are falsey (act like false): false
itself and nil
.
So for example:
if nil # nil is 'falsey'
# Won't go here!
else
# Will go here!
end
if 'randomstring' # any string is 'truthy'
# will go here!
end
This idea applies the same way to while loops.
Next up is the gets
method. If you check out the documentation, you can see that the IO::gets
method returns nil
when it reaches the end of the file. So, when that happens, line
is set to nil
, which is a fasley value, and the loop exits.
Short version: nil
is like false
and gets
returns nil
when it reaches the end of the file.
Upvotes: 2