235

It's easy enough to read a CSV file into an array with Ruby but I can't find any good documentation on how to write an array into a CSV file. Can anyone tell me how to do this?

I'm using Ruby 1.9.2 if that matters.

4
  • 4
    The answer you have is great, but let me urge you to not use CSV. If you don't have tabs in your data, tab-delimited files are much easier to deal with because they don't involve so much freakin' quoting and escaping and such. If you must use CSV, of course, them's the breaks. Commented Jan 28, 2011 at 1:45
  • 8
    @Bill, the CSV module neatly handles tab-delimited files as well as actual csv files. The :col_sep option lets you specify the column separator as "\t" and all's well. Commented Nov 13, 2013 at 17:42
  • 1
    here is more Info about CSV docs.ruby-lang.org/en/2.1.0/CSV.html Commented Aug 19, 2016 at 5:21
  • Using .tab files with this module is what I am doing, because opening this in Excel by accident would othrwise mess up the encoding… Commented Jan 24, 2020 at 8:03

6 Answers 6

404

To a file:

require 'csv'
CSV.open("myfile.csv", "w") do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

To a string:

require 'csv'
csv_string = CSV.generate do |csv|
  csv << ["row", "of", "CSV", "data"]
  csv << ["another", "row"]
  # ...
end

Here's the current documentation on CSV: (until 3.1.1 just replace 1.9.2)

https://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html#class-CSV-label-Writing

while 3.1.2 up to latest (3.2.2 as of this edit).

https://ruby-doc.org/3.2.2/stdlibs/csv/CSV.html

Sign up to request clarification or add additional context in comments.

3 Comments

@David it's the file mode. "w" means write to a file. If you don't specify this, it'll default to "rb" (read-only binary mode) and you would get an error when trying to add to your csv file. See ruby-doc.org/core-1.9.3/IO.html for a list of valid file modes in Ruby.
Gotcha. And for future users, if you want each iteration to not overwrite the previous csv file, use the "ab" option.
See this answer for Ruby File IO Modes: stackoverflow.com/a/3682374/224707
79

If you have an array of arrays of data:

rows = [["a1", "a2", "a3"],["b1", "b2", "b3", "b4"], ["c1", "c2", "c3"]]

Then you can write this to a file with the following, which I think is much simpler:

require "csv"
File.write("ss.csv", rows.map(&:to_csv).join)

2 Comments

yeah but every time it rewrite the file , and not add the data at the end
@HumayunNaseer I haven't needed a situation where I was appending data to a CSV but you could certainly do that using the mode: "a" option for File.write.
45

I've got this down to just one line.

rows = [['a1', 'a2', 'a3'],['b1', 'b2', 'b3', 'b4'], ['c1', 'c2', 'c3'], ... ]
csv_str = rows.inject([]) { |csv, row|  csv << CSV.generate_line(row) }.join("")
#=> "a1,a2,a3\nb1,b2,b3\nc1,c2,c3\n" 

Do all of the above and save to a csv, in one line.

File.open("ss.csv", "w") {|f| f.write(rows.inject([]) { |csv, row|  csv << CSV.generate_line(row) }.join(""))}

NOTE:

To convert an active record database to csv would be something like this I think

CSV.open(fn, 'w') do |csv|
  csv << Model.column_names
  Model.where(query).each do |m|
    csv << m.attributes.values
  end
end

Hmm @tamouse, that gist is somewhat confusing to me without reading the csv source, but generically, assuming each hash in your array has the same number of k/v pairs & that the keys are always the same, in the same order (i.e. if your data is structured), this should do the deed:

rowid = 0
CSV.open(fn, 'w') do |csv|
  hsh_ary.each do |hsh|
    rowid += 1
    if rowid == 1
      csv << hsh.keys# adding header row (column labels)
    else
      csv << hsh.values
    end# of if/else inside hsh
  end# of hsh's (rows)
end# of csv open

If your data isn't structured this obviously won't work

6 Comments

I pulled in a CSV file using CSV.table, did some manipulations, got rid of some columns, and now I want to spool the resulting Array of Hashes out again as CSV (really tab-delimited). How to? gist.github.com/4647196
hmm...that gist is somewhat opaque, but given an array of hashes, all with the same number of k/v pairs and the same keys, in the same order...
Thanks, @boulder_ruby. That will work. The data is a census table, and that gist is rather opaque looking back at it. :) It's basically extracting certain columns from the original census table into a subset.
You're misusing inject here, you really want to use map. Also, you don't need to pass an empty string to join, as this is the default. So you could shrink it even further to this: rows.map(&CSV.method(:generate_line).join
Your second example is overcomplicated, as CSV library is quite powerful. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } } generates an equivalent CSV.
|
28

If anyone is interested, here are some one-liners (and a note on loss of type information in CSV):

require 'csv'

rows = [[1,2,3],[4,5]]                    # [[1, 2, 3], [4, 5]]

# To CSV string
csv = rows.map(&:to_csv).join             # "1,2,3\n4,5\n"

# ... and back, as String[][]
rows2 = csv.split("\n").map(&:parse_csv)  # [["1", "2", "3"], ["4", "5"]]

# File I/O:
filename = '/tmp/vsc.csv'

# Save to file -- answer to your question
IO.write(filename, rows.map(&:to_csv).join)

# Read from file
# rows3 = IO.read(filename).split("\n").map(&:parse_csv)
rows3 = CSV.read(filename)

rows3 == rows2   # true
rows3 == rows    # false

Note: CSV loses all type information, you can use JSON to preserve basic type information, or go to verbose (but more easily human-editable) YAML to preserve all type information -- for example, if you need date type, which would become strings in CSV & JSON.

Comments

11

Building on @boulder_ruby's answer, this is what I'm looking for, assuming us_eco contains the CSV table as from my gist.

CSV.open('outfile.txt','wb', col_sep: "\t") do |csvfile|
  csvfile << us_eco.first.keys
  us_eco.each do |row|
    csvfile << row.values
  end
end

Updated the gist at https://gist.github.com/tamouse/4647196

Comments

3

Struggling with this myself. This is my take:

https://gist.github.com/2639448:

require 'csv'

class CSV
  def CSV.unparse array
    CSV.generate do |csv|
      array.each { |i| csv << i }
    end
  end
end

CSV.unparse [ %w(your array), %w(goes here) ]

1 Comment

Btw, beware of multi-dimensional arrays in pry on JRuby. [ %w(your array), %w(goes here) ] won't look pretty. github.com/pry/pry/issues/568

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.