Output array in CSV in Ruby


185

È abbastanza facile leggere un file CSV in un array con Ruby ma non riesco a trovare una buona documentazione su come scrivere un array in un file CSV. Qualcuno può dirmi come farlo?

Sto usando Ruby 1.9.2 se è importante.


3
La risposta che hai è ottima, ma lascia che ti spinga a non usare CSV. Se non hai schede nei tuoi dati, i file delimitati da tabulazioni sono molto più facili da gestire perché non implicano così tanto la citazione e la fuga in modo strano e così via. Se devi usare CSV, ovviamente, sono le pause.
Bill Dueber,

8
@Bill, il modulo CSV gestisce ordinatamente i file delimitati da tabulazioni così come i file CSV effettivi. L'opzione: col_sep ti consente di specificare il separatore di colonna come "\ t" e tutto va bene.
domina il

1
ecco maggiori informazioni su CSV docs.ruby-lang.org/it/2.1.0/CSV.html
veeresh yh

Usare i file .tab con questo modulo è quello che sto facendo, perché aprirlo in Excel per caso altrimenti rovinerebbe la codifica ...
MrVocabulary

Risposte:


326

In un file:

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

A una stringa:

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

Ecco la documentazione corrente su CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html


1
@ David è la modalità file. "w" significa scrivere su un file. Se non lo specifichi, per impostazione predefinita sarà "rb" (modalità binaria di sola lettura) e otterrai un errore quando provi ad aggiungere al tuo file CSV. Vedi ruby-doc.org/core-1.9.3/IO.html per un elenco di modalità file valide in Ruby.
Dylan Markow,

15
Gotcha. E per gli utenti futuri, se si desidera che ogni iterazione non sovrascriva il file CSV precedente, utilizzare l'opzione "ab".
boulder_ruby,

1
Vedi questa risposta per le modalità IO del file Ruby: stackoverflow.com/a/3682374/224707
Nick,

38

Ho questo fino a una sola riga.

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" 

Fai tutto quanto sopra e salva in un csv, in una riga.

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

NOTA:

Convertire un database di record attivo in CSV sarebbe qualcosa del genere, penso

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

Hmm @tamouse, quella sostanza mi confonde in qualche modo senza leggere la fonte CSV, ma in generale, supponendo che ogni hash nell'array abbia lo stesso numero di coppie k / v e che le chiavi siano sempre le stesse, nello stesso ordine (es. se i tuoi dati sono strutturati), questo dovrebbe fare l'atto:

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

Se i tuoi dati non sono strutturati, ovviamente non funzionerà


Ho inserito un file CSV usando CSV.table, ho fatto alcune manipolazioni, mi sono sbarazzato di alcune colonne e ora voglio spoolare di nuovo l'array di hash risultante come CSV (delimitato da tabulazioni). Come? gist.github.com/4647196
tamouse

hmm ... che sostanza è un po 'opaca, ma data una serie di hash, tutti con lo stesso numero di coppie k / v e le stesse chiavi, nello stesso ordine ...
boulder_ruby

Grazie, @boulder_ruby. Funzionerà I dati sono una tabella di censimento e quell'essenza è piuttosto opaca guardando indietro. :) In pratica sta estraendo alcune colonne dalla tabella censu originale in un sottoinsieme.
domina il

3
Stai abusando injectqui, vuoi davvero usare map. Inoltre, non è necessario passare una stringa vuota a join, poiché questa è l'impostazione predefinita. Quindi potresti ridurlo ulteriormente a questo:rows.map(&CSV.method(:generate_line).join
iGEL

1
Il tuo secondo esempio è troppo complicato, poiché la libreria CSV è piuttosto potente. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } }genera un CSV equivalente.
Amadan,

28

Se si dispone di una matrice di matrici di dati:

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

Quindi puoi scrivere questo in un file con il seguente, che penso sia molto più semplice:

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

20

Se qualcuno è interessato, ecco alcune parole chiave (e una nota sulla perdita di informazioni sul tipo 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

Nota: CSV perde tutte le informazioni sul tipo, è possibile utilizzare JSON per conservare le informazioni di base sul tipo o passare a YAML dettagliato (ma più facilmente modificabile dall'uomo) per conservare tutte le informazioni sul tipo, ad esempio se è necessario il tipo di data, che diventerebbe stringhe in CSV e JSON.


9

Basandomi sulla risposta di @ boulder_ruby, questo è quello che sto cercando, supponendo che us_ecocontenga la tabella CSV come da mia idea.

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

Aggiornato il contenuto su https://gist.github.com/tamouse/4647196


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.