In Ruby, what does IO#gets return at the end of a file, and what does its chomp: true keyword change?
answer
- the line includes its separator
- nil at end of file
- while (line = f.gets) idiom
- readline raises EOFError instead
- chomp: true strips \n and \r\n
basics
~20 sIO#gets returns the next line including its trailing newline, and nil at end of file, which is why while (line = f.gets) terminates. With chomp: true the line separator, including a preceding carriage return, is removed.
solid answer
~40 sOn an open `File`, `gets` reads up to and including the next line separator (`$/`, normally `"\n"`) and returns it as a `String`; at end of file it returns `nil`, so `while (line = f.gets)` stops cleanly. It also assigns the line to `$_`. `gets(chomp: true)` drops the separator, and with the default separator also a `"\r"` before it, so Windows-style `"\r\n"` lines come back clean. A blank line then returns `""`, which is truthy in Ruby, so the loop still continues until the real `nil`. `IO#readline` reads the same way but raises `EOFError` at end of file instead of returning `nil`. `gets` also takes a separator (`nil` reads everything, `""` reads paragraphs) and a byte limit.
code
ruby · 13 lines# orders.csv contains "id,total\r\n1,9.50\r\n\r\n2,12.00\r\n"
File.open("orders.csv") do |f|
f.gets # => "id,total\r\n"
f.gets(chomp: true) # => "1,9.50"
f.gets(chomp: true) # => "" (blank line, still truthy)
f.gets(chomp: true) # => "2,12.00"
f.gets # => nil at end of file
begin
f.readline
rescue EOFError
puts "readline raises at EOF"
end
endgo deeper
Recall that gets returns the line with its newline and nil at end of file, and that chomp: true removes the newline.
Explain why while (line = f.gets) terminates, why a blank line does not stop it, how readline differs, and which other line methods accept chomp.
Use readline where missing input is an error, cap line length with a limit on untrusted files, and normalise CRLF input with chomp rather than ad-hoc string surgery.
Decide where a parser should fail fast on truncated input versus stop quietly, and make that choice explicit through readline versus gets.
## What IO#gets does `IO#gets` reads **one line** from an open stream — here a `File` you opened with `File.open` — and returns it. Its signatures, from the rdoc in `io.c`: - `gets(sep = $/, chomp: false)` → `String` or `nil` - `gets(limit, chomp: false)` → `String` or `nil` - `gets(sep, limit, chomp: false)` → `String` or `nil` By default the separator is `$/`, the input record separator, which is `"\n"`. The returned string **includes** that separator: the first call on a file whose first line is `First line` returns `"First line\n"`. `gets` also assigns its result to the special variable `$_`. ## End of file: nil, not an exception When nothing is left, `gets` returns **`nil`**. That is what makes the classic loop work: ```ruby File.open("orders.csv") do |f| header = f.gets(chomp: true) while (line = f.gets(chomp: true)) process(header, line) end end ``` The assignment inside the `while` condition evaluates to the line, and the loop ends on `nil`. Two details interviewers check: 1. **A blank line is not end of file.** With `chomp: true`, an empty line comes back as `""`. In Ruby only `nil` and `false` are falsy, so `""` keeps the loop going. 2. **`readline` is the raising twin.** `IO#readline` accepts the same arguments but raises `EOFError` at end of file. Use it when running out of input is an error, as in "the header line must exist". ## What chomp: true changes | Call | Line `"abc\n"` | Line `"abc\r\n"` | Blank line | At EOF | |---|---|---|---|---| | `f.gets` | `"abc\n"` | `"abc\r\n"` | `"\n"` | `nil` | | `f.gets(chomp: true)` | `"abc"` | `"abc"` | `""` | `nil` | | `f.readline` | `"abc\n"` | `"abc\r\n"` | `"\n"` | raises `EOFError` | With the default newline separator, `chomp: true` also removes a carriage return in front of the newline, so files written on Windows do not leave a stray `"\r"` on every value. The same keyword exists on `IO#each_line`, `IO#readlines`, `File.foreach` and `File.readlines`, so you rarely need to call `String#chomp` on each line yourself. ## Separator and limit arguments - **Custom separator.** `f.gets(";")` reads up to and including the next semicolon. - **`nil` separator.** `f.gets(nil)` reads the rest of the stream as one string. - **Empty-string separator.** `f.gets("")` is paragraph mode: it reads up to a run of blank lines. - **Limit.** `f.gets(1024)` returns at most about 1024 bytes, even if the line is longer; the next call continues where it stopped. This guards against a single enormous line. ## Line numbers for error messages Every successful `gets` (and every line `each_line` yields) increments the stream's line counter, readable with `IO#lineno`. That makes precise error messages cheap: ```ruby File.open("orders.csv") do |f| header = f.readline(chomp: true) while (line = f.gets(chomp: true)) next if line.empty? id, total = line.split(",") raise ArgumentError, "orders.csv:#{f.lineno}: missing total" if total.nil? end end ``` Here `readline` makes an empty file fail loudly at the header, `line.empty?` skips blank lines without ending the loop, and `f.lineno` names the exact line of a bad record. `IO#rewind` resets the counter to zero along with the position. ## gets versus each_line `gets` gives you **pull** control: read a header, skip a line, stop early, or interleave reads with other logic. `each_line` is **push**: it yields every remaining line and returns the file. Both read through the same buffer and both stream, so neither loads the file; pick whichever reads more clearly. ## Common mistakes - Comparing a `gets` result to a string without chomping: `line == "END"` is false for `"END\n"`. - Expecting `gets` to raise at end of file and wrapping the loop in `rescue EOFError`; that is `readline`'s behaviour.
- In Ruby, when would you choose IO#readline over IO#gets?When reaching end of file is a bug rather than the normal way to stop. `readline` raises `EOFError` instead of returning `nil`, so reading a required header with `f.readline(chomp: true)` fails loudly on an empty file instead of passing `nil` into code that expects a string.
- Why is gets(limit) useful on input you do not control?Without a limit, one line can be arbitrarily long: a file with no newlines is a single line of its full size. `f.gets(65_536)` returns at most about that many bytes and leaves the rest for the next call, so a malformed or hostile file cannot force one huge allocation.
saying these in an interview costs you the question
- IO#gets raises EOFError when the file has no more lines.
- IO#gets strips the trailing newline from each line by default.
- A blank line returns nil from gets, so the while loop stops early.
- chomp: true leaves the carriage return from Windows line endings.
- gets reads the whole file into memory and returns one line at a time.