CSV
Added in v1.9 · Updated March 13, 2026 · Modules
ruby stdlib csv parsing data
The CSV module provides utilities for reading and writing CSV (Comma-Separated Values) data in Ruby. It handles various CSV formats including custom delimiters, quoting rules, and encoding options.
Overview
CSV is a common format for spreadsheets, data exports, and data interchange. Ruby’s CSV module makes it easy to:
- Parse: Read CSV data into arrays, hashes, or table objects
- Generate: Create CSV output from Ruby data structures
- Transform: Filter, map, and process CSV data
- Stream: Process large files row by row
Loading CSV
require "csv"
Reading CSV Data
CSV.parse
CSV.parse converts a CSV string into an array of arrays.
require "csv"
csv_string = "name,age,city\nAlice,30,NYC\nBob,25,LA"
rows = CSV.parse(csv_string)
rows[0] # => ["name", "age", "city"]
rows[1] # => ["Alice", "30", "NYC"]
With headers:
rows = CSV.parse(csv_string, headers: true)
rows.first["name"] # => "Alice"
rows.first["age"] # => "30"
CSV.read
CSV.read reads directly from a file.
require "csv"
# Read entire file into array
data = CSV.read("data.csv")
# Read with headers
data = CSV.read("data.csv", headers: true)
CSV.foreach
Process large files row by row without loading everything into memory:
require "csv"
CSV.foreach("large_file.csv") do |row|
# Process each row
puts row.join(", ")
end
With headers and conversion:
CSV.foreach("data.csv", headers: true, converters: :numeric) do |row|
puts "#{row["name"]} is #{row["age"]} years old"
end
Generating CSV
CSV.generate
Create CSV strings programmatically:
require "csv"
csv_string = CSV.generate do |csv|
csv << ["name", "age", "city"]
csv << ["Alice", 30, "NYC"]
csv << ["Bob", 25, "LA"]
end
puts csv_string
# name,age,city
# Alice,30,NYC
# Bob,25,LA
CSV.generate_line
Quickly generate a single CSV row:
require "csv"
CSV.generate_line(["Alice", 30, "NYC"])
# => "Alice,30,NYC\n"
# Custom separator
CSV.generate_line(["a,b", "c"], quote_char: "\"")
# => "\"a,b\",c\n"
CSV.table
Read CSV into a table with column names as symbols:
require "csv"
table = CSV.table("data.csv")
table[0][:name] # => "Alice"
table[0][:age] # => 30
Common Options
| Option | Default | Description |
|---|---|---|
col_sep | "," | Column separator |
row_sep | "\r\n" or "\n" | Row separator |
quote_char | "\" | Quote character |
headers | false | Parse first row as headers |
skip_blanks | false | Skip empty rows |
encoding | "UTF-8" | Character encoding |
Custom Delimiters
# Tab-separated values
CSV.parse(tsv_string, col_sep: "\t")
# Semicolon-separated
CSV.parse(str, col_sep: ";")
# Read pipe-delimited file
CSV.read("data.txt", col_sep: "|")
Handling Quotes
# Force quoting of all fields
CSV.generate_line(["a", "b", "c"], force_quotes: true)
# => "\"a\",\"b\",\"c\"\n"
# Use single quotes
CSV.generate_line(["it's", "fine"], quote_char: "'")
# => "'it''s','fine'\n"
Working with Headers
Header Options
# Array of symbols as headers
CSV.parse(csv_string, headers: [:name, :age, :city])
# True uses first row as headers
CSV.parse(csv_string, headers: true)
# First row is removed from data
# Header converters
CSV.parse(csv_string, headers: true, header_converters: :symbol)
# Headers become symbols, spaces -> underscores
Accessing Header Data
data = CSV.parse(csv_string, headers: true)
# Get headers
data.headers # => ["name", "age", "city"]
# Find column index
data.headers.index("age") # => 1
Data Conversion
Built-in Converters
# Convert numeric fields
CSV.parse(csv_string, converters: :numeric)
# [["Alice", 30, "NYC"], ["Bob", 25, "LA"]]
# Convert dates
CSV.parse(csv_string, converters: :date)
# Multiple converters
CSV.parse(csv_string, converters: [:numeric, :date])
Custom Converters
CSV::Converters[:uppercase] = ->(field) { field.upcase }
CSV.parse("hello,world", converters: :uppercase)
# => [["HELLO", "WORLD"]]
Practical Examples
Processing Survey Data
require "csv"
results = []
CSV.foreach("survey.csv", headers: true, converters: :numeric) do |row|
if row["satisfaction"] >= 4
results << {
name: row["name"],
score: row["satisfaction"]
}
end
end
puts "High satisfaction: #{results.length}"
Export Database to CSV
require "csv"
CSV.open("export.csv", "wb") do |csv|
csv << ["id", "name", "email", "created_at"]
User.find_each do |user|
csv << [user.id, user.name, user.email, user.created_at]
end
end
Transform CSV Format
require "csv"
# Convert tab-separated to comma-separated
input = "name\tage\nAlice\t30"
output = CSV.generate do |csv|
CSV.parse(input, col_sep: "\t").each do |row|
csv << row
end
end
Handle Large Files
require "csv"
# Count rows without loading entire file
count = 0
CSV.foreach("large.csv", headers: true) { count += 1 }
puts "Total rows: #{count}"
# Filter and write to new file
CSV.foreach("input.csv", headers: true) do |row|
if row["status"] == "active"
CSV.open("filtered.csv", "a") do |output|
output << row
end
end
end
CSV::Row and CSV::Field
Working with Rows
require "csv"
row = CSV::Row.new(["name", "age"], ["Alice", 30])
row[0] # => "Alice"
row["name"] # => "Alice" (with headers)
row.fields # => ["Alice", 30]
row.to_a # => ["Alice", 30]
row.to_h # => {"name" => "Alice", "age" => 30}
Field Manipulation
row = CSV::Row.new(["name", "age"], ["Alice", 30], headers: ["name", "age"])
# Check if field exists
row["name"] # => "Alice"
row["missing"] # => nil
# Delete field
row.delete("age")
row.to_h # => {"name" => "Alice"}
Ruby Version History
- Ruby 1.9: Added built-in CSV library (based on FasterCSV)
- Ruby 2.0: Improved encoding support
- Ruby 2.6: Added
skip_blanksoption - Ruby 3.0: Fixed various parsing edge cases