Question
How can I read a text file line by line in Ruby when the input may use different line-ending styles?
This code works for some files, but for others Ruby appears to read the entire file as one line:
line_num = 0
File.open("xxx.txt").each do |line|
print "#{line_num += 1} #{line}"
end
The program must also support standard input, such as:
ruby my_prog.rb < file.txt
Because the input file may use LF, CRLF, or CR line endings, how can Ruby split the input into lines reliably?
Short Answer
You will learn how Ruby decides where one line ends, why files with older or different operating-system line endings can appear as one long line, and how to normalize newline formats when reading from files or STDIN.
Concept
Ruby reads text one record at a time using an input record separator. By default, that separator is a line feed character ("\n").
Common newline formats are:
- LF (
\n) — common on Linux and modern macOS. - CRLF (
\r\n) — common on Windows. - CR (
\r) — used by classic Mac OS files and occasionally encountered in exported data.
A CRLF ending includes \n, so Ruby can usually split it while reading text. A file containing only CR endings does not contain \n; therefore, a default line reader may treat the whole file as one line.
Ruby's newline: :universal option recognizes LF, CRLF, and CR endings and normalizes each one to \n while reading. This is useful when your program accepts text from unknown platforms or external tools.
Line-oriented processing matters because it lets programs handle large files incrementally instead of loading all content into memory at once.
Mental Model
Think of a newline as the end-of-row marker in a spreadsheet.
Ruby's normal reader looks for one specific marker: \n. Most files use it directly or as part of \r\n, so the rows are found. But a CR-only file uses a different marker, \r, and Ruby does not see its usual boundary.
Universal newline mode is like a translator at the door: it recognizes all common row-ending markers and converts them into the same internal marker before your code receives each line.
Syntax and Examples
Read a file line by line with universal newline handling:
line_num = 0
File.open("xxx.txt", "r", newline: :universal) do |file|
file.each_line do |line|
line_num += 1
print "#{line_num} #{line}"
end
end
newline: :universal makes Ruby recognize these endings:
LF: \n
CRLF: \r\n
CR: \r
After conversion, each yielded line ends in \n when the original input had a line ending.
For input redirected through the shell, configure STDIN before iterating:
STDIN.set_encoding(Encoding::UTF_8, nil, newline: :universal)
STDIN.each_line.with_index(1) do |line, line_number|
print "#{line_number} #{line}"
end
with_index(1) starts numbering at 1, so you do not need a separate counter variable.
If the input encoding is not known, do not blindly assume UTF-8. In that case, choose an encoding required by your program or inspect and validate the input separately.
Step by Step Execution
Consider a CR-only input file whose bytes represent:
apple\rbanana\rcherry\r
Use this code:
File.open("fruit.txt", "r", newline: :universal) do |file|
file.each_line.with_index(1) do |line, number|
puts "#{number}: #{line.chomp}"
end
end
Execution proceeds as follows:
File.openopensfruit.txtfor reading.newline: :universalrecognizes each\ras a newline boundary.- Ruby normalizes the newline after
appleto\nand yields"apple\n". with_index(1)suppliesnumberas1.chompremoves the trailing normalized newline, andputsprints1: apple.- The same process repeats for
bananaandcherry.
The output is:
1: apple
2: banana
3: cherry
Real World Use Cases
- Command-line filters: Read redirected logs with
ruby report.rb < exported-log.txt. - CSV-like exports: Process reports produced on Windows, Linux, or legacy systems.
- Data migration scripts: Import partner files without requiring contributors to manually convert newlines.
- Log analysis: Count, search, or transform one log entry at a time.
- ETL jobs: Stream large text inputs line by line to keep memory use predictable.
- Developer tools: Build formatters and linters that accept piped input from other commands.
Real Codebase Usage
In real Ruby programs, line readers are commonly wrapped in a method so files and standard input share the same processing path:
def print_numbered_lines(io)
io.each_line.with_index(1) do |line, number|
puts "#{number}: #{line.chomp}"
end
end
A command-line program can then select its source with a guard clause:
if ARGV.empty?
STDIN.set_encoding(Encoding::UTF_8, nil, newline: :universal)
print_numbered_lines(STDIN)
else
File.open(ARGV.first, "r", newline: :universal) do |file|
print_numbered_lines(file)
end
end
Useful project patterns include:
- Stream processing: Use
each_linerather thanFile.readfor large inputs. - Resource safety: Use the block form of
File.openso Ruby closes the file even if an exception occurs. - Normalization at boundaries: Convert newline variants as data enters the application, then keep the rest of the code simple.
- Validation: Reject invalid encodings or malformed records early when the program requires structured input.
- Separation of concerns: Keep reading logic separate from parsing and business rules.
Common Mistakes
Using an uninitialized counter
This raises an error because line_num has not been assigned:
File.open("xxx.txt").each do |line|
print "#{line_num += 1} #{line}"
end
Initialize it first, or use with_index:
File.open("xxx.txt", "r", newline: :universal) do |file|
file.each_line.with_index(1) do |line, number|
print "#{number} #{line}"
end
end
Assuming every text file uses \n
A CR-only file may become one long record with default settings. Use newline: :universal when inputs can originate from different systems.
Calling File.read for a large file
This loads the entire file into memory:
contents = File.read("large.log")
Prefer streaming when processing line by line:
File.open("large.log", "r", newline: :universal) do |file|
file.each_line { |line| process(line) }
end
Removing too much with strip
strip removes leading and trailing whitespace, not just the newline:
name = line.strip
If leading or trailing spaces are meaningful, use chomp instead:
name = line.chomp
Forgetting that the final line may have no newline
A valid text file can end without a final newline. Ruby still yields that final content as a line; chomp simply has nothing to remove.
Comparisons
| Approach | What it does | Best use | Limitation |
|---|---|---|---|
file.each_line | Streams lines from an IO object | Large files and ordinary line processing | Default behavior may not split CR-only input |
file.each | Alias for line iteration on IO | Concise line processing | Less explicit than each_line for beginners |
File.readlines | Returns all lines in an array | Small files when you need random access to lines | Loads every line into memory |
File.read | Returns the entire file as one string | Small whole-file transformations | You must split it yourself; can use much memory |
newline: :universal | Recognizes LF, CRLF, and CR while reading | Cross-platform or unknown text input | Applies to text line handling, not arbitrary binary formats |
chomp and strip are also different:
| Method | Removes |
|---|---|
chomp | A trailing record separator/newline |
strip | Leading and trailing whitespace, including spaces and tabs |
Cheat Sheet
# Read a known file, accepting LF, CRLF, and CR line endings
File.open("input.txt", "r", newline: :universal) do |file|
file.each_line do |line|
puts line.chomp
end
end
# Number lines starting at 1
File.open("input.txt", "r", newline: :universal) do |file|
file.each_line.with_index(1) do |line, number|
puts "#{number}: #{line.chomp}"
end
end
# Read redirected standard input
STDIN.set_encoding(Encoding::UTF_8, nil, newline: :universal)
STDIN.each_line { |line| puts line.chomp }
- Default line separator:
"\n". - Universal mode recognizes:
\n,\r\n, and\r. - Use
each_lineto stream instead of loading a whole large file. - Use the block form of
File.opento close files automatically. - Use
chompto remove a final newline while preserving other whitespace. - The final line can be yielded even when it has no trailing newline.
FAQ
Why does Ruby read my text file as one line?
The file may use CR-only (\r) line endings while Ruby is looking for its default separator, \n. Open the input with newline: :universal.
Does Ruby handle Windows CRLF line endings?
Yes, CRLF contains \n, so line iteration commonly works. Universal newline mode is still useful when your program must also handle CR-only files consistently.
How do I read lines from STDIN in Ruby?
Use STDIN.each_line or simply ARGF.each_line. For unknown newline styles, configure STDIN with newline: :universal before reading.
Should I use each or each_line on a Ruby file?
Both iterate over lines for IO objects. each_line is clearer because it explicitly states that you are processing lines.
What is the difference between chomp and strip?
chomp removes the trailing newline. strip removes whitespace at both ends, which may accidentally change meaningful spaces.
Can I use readlines instead of each_line?
Yes for small files, but readlines stores the complete file's lines in an array. Use each_line for large or streaming input.
Does universal newline mode fix text encoding problems?
No. Newline format and character encoding are separate concerns. Configure or validate the encoding required by your application.
Mini Project
Description
Build a small command-line line inspector. It accepts either a file path or redirected standard input, handles common newline formats, and prints non-empty lines with their original line numbers. This mirrors utilities used to inspect imports, logs, and exported reports.
Goal
Create a Ruby script that reads cross-platform text input line by line and prints numbered, non-empty records.
Requirements
Use a file path from ARGV when one is provided.
Read from STDIN when no file path is provided.
Accept LF, CRLF, and CR line endings.
Print line numbers starting at 1.
Ignore lines that are empty after removing their newline.
Close opened files automatically.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.