Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Ruby’s CSV library can parse a string or an IO source and can generate CSV output. The reliable approach is to identify the file’s actual separators, line endings, quoting, headers, and encoding, then set options deliberately instead of assuming every file uses the defaults. The examples here target the Ruby 3.3 CSV class reference and CSV gem 3.3.2 documentation.

How do I parse a CSV file in Ruby?

For a small string, use CSV.parse. For a file or other stream, pass an IO to CSV.foreach, CSV.read, or construct a CSV object. With default settings, Ruby expects comma-separated columns, double-quote quoting, automatically detected row separators, and no header row.

require "csv"

text = "name,agenAda,36nGrace,28n"
rows = CSV.parse(text)
# => [["name", "age"], ["Ada", "36"], ["Grace", "28"]]

CSV.foreach("people.csv") do |row|
  p row
end

CSV.parse returns all records, which is convenient when the input fits comfortably in memory. CSV.foreach yields one record at a time and is the better shape for a large file.

String input versus an IO source

Input pattern Typical API Result Use it when
String CSV.parse(text, ...) All parsed records The data is already in memory or is small enough to load at once
File or other IO CSV.foreach(path, ...) Records yielded incrementally You want bounded memory use while processing a file
String or IO wrapped in an object CSV.new(source, ...) A configurable CSV parser/generator object You need to call methods on a reusable CSV object

CSV.new wraps a String in a StringIO positioned at the beginning. An IO should be open for reading and positioned at the beginning. Options supplied when the object is constructed remain in effect; do not assume a later call will replace them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall

How do I read CSV headers in Ruby?

Set headers: true or headers: :first_row to treat the first record as the header row. Subsequent records can then be returned as CSV::Row objects, so fields can be accessed by header name instead of only by index.

require "csv"

CSV.parse("name,agenAda,36nGrace,28n", headers: true).each do |row|
  puts "#{row["name"]}: #{row["age"]}"
end

Turning headers on changes the data shape: code written for arrays should be changed to use header-aware access. You can also supply header names yourself, or pass a string that represents a header row.

rows = CSV.parse(
  "Ada,36nGrace,28n",
  headers: ["name", "age"]
)

rows.each { |row| puts row["name"] }

Header converters are separate from field converters. Use a header converter when names need normalization, and a field converter when values such as numbers or dates need transformation.

What are Ruby CSV’s default options?

Option Documented default What it controls
col_sep "," Column separator
row_sep :auto Record separator; automatic detection for common line endings
quote_char '"' Character used to quote fields
headers false Whether the first record or supplied names define headers
converters nil Field conversion rules
skip_blanks false Whether blank rows are skipped
liberal_parsing false Whether selected non-compliant input is tolerated
force_quotes false Whether generated fields are always quoted
quote_empty true Whether empty generated fields are quoted

These are the defaults documented for the Ruby 3.3 CSV API and CSV gem 3.3.2 references. Set an option explicitly when the producer or consumer requires a different format.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I change the column separator?

Pass col_sep with the delimiter used by the file. This is common for tab-separated data and semicolon-delimited exports.

text = "nametagenAdat36n"
rows = CSV.parse(text, col_sep: "t")

CSV.open("report.csv", "wb", col_sep: ";") do |csv|
  csv << ["name", "age"]
  csv << ["Ada", 36]
end

The separator string is transcoded into the data’s encoding for use. A different delimiter does not repair an otherwise malformed file; confirm the file’s structure and encoding first.

How do I handle different line endings?

row_sep: :auto detects common line-ending conventions when parsing. If the format is known, or a downstream system requires a specific convention, provide an explicit separator for parsing or generation.

rows = CSV.parse(text, row_sep: "rn")

CSV.generate(row_sep: "rn") do |csv|
  csv << ["name", "age"]
  csv << ["Ada", 36]
end

Use an explicit value when you control the output contract. Do not treat row_sep as a substitute for investigating mixed, truncated, or otherwise invalid input.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I convert CSV fields to numbers?

Without converters, parsed fields are strings. The :numeric converter turns numeric-looking values into integers or floating-point values. Converters are applied to fields, while header converters handle header names independently.

rows = CSV.parse(
  "name,age,scorenAda,36,98.5n",
  headers: true,
  converters: :numeric
)

row = rows.first
p row["age"]   # => 36
p row["score"] # => 98.5

Choose converters only when that transformation matches the application’s data contract. Keeping raw strings can be preferable when formatting, leading zeroes, or exact textual values matter.

How does Ruby CSV handle character encodings?

CSV operates in the encoding of its input String or IO and returns strings in that encoding. It does not transcode data automatically. Custom separators and quote characters must be compatible with the data encoding.

File.open("legacy.csv", "r:Windows-1252:UTF-8") do |io|
  CSV.foreach(io, headers: true) do |row|
    puts row["name"]
  end
end

The file-opening mode above asks Ruby to transcode from Windows-1252 to UTF-8 before CSV parses it. Confirm the source encoding and the exact Ruby/CSV runtime in your deployment; choosing the wrong source encoding can corrupt characters before parsing begins.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should I handle blank lines, comments, and malformed data?

Blank records

Blank rows are retained by default. Set skip_blanks: true when empty records have no meaning in the input format.

Comment lines

CSV parsing supports comment-line skipping. Configure the comment option only when the producer’s format defines comment lines; otherwise a line that looks like a comment may contain real data.

Non-compliant quoting

liberal_parsing: true is an explicit accommodation for inputs that do not follow strict CSV quoting rules. It helps a parser accept selected irregular forms, but it does not validate or repair the source. Keep the strict default when rejecting malformed data is important.

Large fields

The Ruby 3.3 reference marks field_size_limit as deprecated since 3.2.3 and points to max_field_size instead. Use the option documented for the CSV gem version deployed by your application, and treat limits as a defensive resource policy rather than a way to fix bad structure.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I generate CSV output?

Use CSV.generate for a string and CSV.open or a CSV object for a file or other IO. Arrays are written as records; values that contain the separator, quote character, or a row separator are quoted as required.

csv_text = CSV.generate do |csv|
  csv << ["name", "note"]
  csv << ["Ada", "Uses, commas"]
end

CSV.open("people.csv", "wb", force_quotes: true) do |csv|
  csv << ["name", "age"]
  csv << ["Ada", 36]
end

force_quotes: true quotes every generated field. The documented generation default is false; quote_empty defaults to true. Set row_sep, col_sep, and quote_char to match the receiving system rather than relying on defaults.

A practical configuration checklist

  • Identify whether the source is a String, a seekable IO, or a large stream.
  • Confirm the column separator and quote character from the actual file.
  • Let row_sep: :auto handle ordinary mixed-platform line endings, or set an explicit separator for a fixed contract.
  • Decide whether the first row is a header and update access code for CSV::Row records when it is.
  • Choose field and header converters independently.
  • Decide whether blank rows and comment lines are meaningful.
  • Keep strict parsing unless the producer’s non-compliant format requires liberal_parsing.
  • Verify the input encoding before selecting custom separators, quote characters, or transcoding modes.
  • For output, specify the separators, quoting policy, and encoding expected by the consumer.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.