Выходной массив в CSV в Ruby

185

Достаточно просто прочитать CSV-файл в массив с помощью Ruby, но я не могу найти хорошую документацию о том, как записать массив в CSV-файл. Может кто-нибудь сказать мне, как это сделать?

Я использую Ruby 1.9.2, если это имеет значение.

Джейсон Светт
источник
3
У вас отличный ответ, но позвольте мне убедить вас не использовать CSV. Если у вас нет вкладок в ваших данных, с файлами, разделенными табуляцией, будет гораздо легче иметь дело, потому что они не включают в себя слишком много гребаных цитат, экранирования и тому подобного. Если вы должны использовать CSV, конечно, это перерывы.
Билл Дьюбер
8
@Bill, CSV-модуль аккуратно обрабатывает файлы с разделителями табуляции, а также фактические CSV-файлы. Опция: col_sep позволяет указать разделитель столбцов как "\ t", и все хорошо.
tamouse
1
здесь больше информации о CSV docs.ruby-lang.org/en/2.1.0/CSV.html
прощай
Я использую файлы .tab с этим модулем - это то, что я делаю, потому что случайное открытие этого файла в Excel могло бы испортить кодировку…
MrVocabulary

Ответы:

326

К файлу:

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

К строке:

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

Вот текущая документация по CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html

Дилан Марков
источник
1
@ Давид, это файловый режим. «w» означает запись в файл. Если вы не укажете это, по умолчанию будет использоваться «rb» (бинарный режим только для чтения), и вы получите сообщение об ошибке при попытке добавить его в CSV-файл. См. Ruby-doc.org/core-1.9.3/IO.html для получения списка допустимых режимов файлов в Ruby.
Дилан Марков
15
Попался. А для будущих пользователей, если вы хотите, чтобы каждая итерация не перезаписывала предыдущий файл CSV, используйте опцию «ab».
boulder_ruby
1
См. Этот ответ для режимов ввода-вывода файлов Ruby: stackoverflow.com/a/3682374/224707
Ник
38

У меня это до одной строчки.

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" 

Выполните все вышеперечисленное и сохраните в формате csv в одну строку.

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

НОТА:

Я думаю, что конвертировать базу данных активных записей в CSV было бы что-то вроде этого

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

Хм @tamouse, эта суть несколько смущает меня, не читая исходный код csv, но, в общем, предполагается, что каждый хэш в вашем массиве имеет одинаковое количество пар k / v и что ключи всегда одинаковы, в одном и том же порядке (т.е. если ваши данные структурированы), это должно сделать дело:

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

Если ваши данные не структурированы, это явно не сработает

boulder_ruby
источник
Я вытащил файл CSV с помощью CSV.table, сделал некоторые манипуляции, избавился от некоторых столбцов, и теперь я хочу снова превратить получившийся массив хэшей в CSV (действительно с разделителями табуляции). Как? gist.github.com/4647196
тамуза
хм ... эта суть несколько непрозрачна, но с учетом массива хэшей, все с одинаковым количеством пар к / в и одинаковыми ключами, в том же порядке ...
boulder_ruby
Спасибо, @boulder_ruby. Это будет работать. Данные представляют собой таблицу переписи, и суть этого довольно непрозрачна. :) Это в основном извлечение определенных столбцов из исходной таблицы переписи в подмножество.
tamouse
3
Вы неправильно используете injectздесь, вы действительно хотите использовать map. Кроме того, вам не нужно передавать пустую строку join, так как это по умолчанию. Таким образом, вы могли бы сократить это еще дальше к этому:rows.map(&CSV.method(:generate_line).join
iGEL
1
Ваш второй пример слишком сложен, поскольку библиотека CSV довольно мощная. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } }генерирует эквивалент CSV.
Амадан
28

Если у вас есть массив массивов данных:

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

Затем вы можете записать это в файл со следующим, что, я думаю, намного проще:

require "csv"
File.write("ss.csv", rows.map(&:to_csv).join)
jwadsack
источник
20

Если кому-то интересно, вот несколько однострочников (и примечание о потере информации о типах в 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

Примечание: CSV теряет всю информацию о типах, вы можете использовать JSON для сохранения базовой информации о типах или перейти к многословному (но более легко редактируемому) YAML для сохранения всей информации о типах - например, если вам нужен тип даты, который станет строки в CSV и JSON.

Канат Болазар
источник
9

Основываясь на ответе @ boulder_ruby, это то, что я ищу, предполагая, что us_ecoсодержит таблицу CSV, как из моей сути.

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

Обновлен гист по адресу https://gist.github.com/tamouse/4647196

tamouse
источник
2

Борюсь с этим сам. Это мое мнение:

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) ]
Феликс Рабе
источник
Кстати, остерегайтесь многомерных массивов в pry на JRuby. [ %w(your array), %w(goes here) ]не будет выглядеть красиво. github.com/pry/pry/issues/568
Феликс Рабе,