rubyguides

String#lines — Split Lines Into an Array

str.lines(line_sep = $/, chomp: false) → array

Splits text into an array of lines based on a separator. By default it splits on "\n" (the global input record separator, $/), returning each line as a separate element. Use chomp: true to strip the separators from each element in one step.

Splitting on Newlines

With no arguments, lines splits on the default line separator "\n":

"This is line one\nThis is line two\n".lines
# => ["This is line one\n", "This is line two\n"]

Each element keeps the trailing newline by default — a detail that trips people up constantly. If you want clean text without the line endings, pass chomp: true. The method does not strip anything for you; you either call chomp on every element afterward or pass the chomp: true option upfront.

Removing line separators with chomp

The chomp: true option strips the separator from every element as it’s split:

"This is line one\nThis is line two\n".lines(chomp: true)
# => ["This is line one", "This is line two"]

This saves you from calling chomp on each element afterward. The chomp option saves a full pass through the array, which becomes noticeable when the file is large. Beyond newlines, lines works with any delimiter — you can split on a colon, a space, or any short character sequence.

# Without chomp: true — manual cleanup needed
File.read("config.txt").lines.map(&:chomp)

# With chomp: true — done in one step
File.read("config.txt").lines(chomp: true)

The chomp option saves a full pass through the array, which becomes noticeable when the file is large. But lines works with separators beyond the default newline — you can split on any character or short delimiter. The examples below show how a custom separator behaves the same way as the newline default: the separator stays attached to each element.

Custom Separators

You can split on any separator string, not just newlines:

"apple:banana:cherry".lines(':')
# => ["apple:", "banana:", "cherry"]

"word1 word2 word3".lines(' ')
# => ["word1 ", "word2 ", "word3"]

The separator appears at the end of each element when using a custom separator (just like newlines in the default case). Passing an empty string instead triggers paragraph mode, where lines looks for two or more consecutive newlines as the split point. This groups related text into logical blocks — useful for email bodies, log sections, or any text where blank lines carry meaning.

Paragraph mode with empty separator

An empty string "" as separator splits on two or more consecutive newlines, giving you paragraphs:

"Para one.\n\nPara two.\n\n\nPara three.".lines('')
# => ["Para one.\n\n", "Para two.\n\n\n", "Para three."]

The trailing newlines stay in each paragraph element. If you want clean paragraphs without the trailing whitespace, chain map(&:strip) or similar afterward. Ruby handles line ending conventions smoothly across platforms — the runtime strips a leading \r before \n, so Windows CRLF becomes plain LF, but a lone \r (old Mac style) does not trigger a split.

Line separator variations

Ruby handles the common line ending conventions:

# Unix (LF)
"line1\nline2".lines
# => ["line1\n", "line2"]

# Windows (CRLF)
"line1\r\nline2".lines
# => ["line1\n", "line2"]

# Old Mac (CR only — no split happens)
"line1\rline2".lines
# => ["line1\rline2"]

Edge cases around empty input and trailing separators are worth knowing. When you call lines on an empty string, you get back an empty array — no surprises there. Input ending with the separator does not produce a trailing empty element either. But a double separator — two newlines in a row — does create an extra blank element, which can surprise you when processing files that may or may not end with a trailing newline character.

Empty string and trailing separators

An empty input returns an empty array:

"".lines
# => []

"".lines(chomp: true)
# => []

Input ending with the separator does not produce a trailing empty element. But a double separator — two newlines in a row — does create an extra blank element. This behavior can catch you off guard when processing files that may or may not end with a trailing newline character.

"a\nb\n".lines
# => ["a\n", "b\n"]

"a\nb\n\n".lines
# => ["a\n", "b\n", "\n"]  # blank line becomes an empty string element

Watch out for this when processing files that may or may not have a trailing newline.

Versus each_line

lines and each_line share the same splitting logic, but they differ in what they return:

  • lines — returns a plain Array, no block supported
  • each_line — returns an Enumerator (or self with a block), enabling lazy iteration

If you’re processing a large file and want to avoid loading the whole thing into memory, each_line with a block is the better choice:

File.foreach("large_log.txt").each_line do |line|
  process(line)
end

For smaller files or when you need an array upfront, lines is straightforward.

See Also