Tableau de sortie en CSV dans Ruby

185

Il est assez facile de lire un fichier CSV dans un tableau avec Ruby mais je ne trouve aucune bonne documentation sur la façon d'écrire un tableau dans un fichier CSV. Quelqu'un peut-il me dire comment faire cela?

J'utilise Ruby 1.9.2 si cela compte.

Jason Swett
la source
3
La réponse que vous avez est excellente, mais laissez-moi vous exhorter à ne pas utiliser CSV. Si vous n'avez pas d'onglets dans vos données, les fichiers délimités par des tabulations sont beaucoup plus faciles à gérer car ils n'impliquent pas tellement de citations et d'échappements fous, etc. Si vous devez utiliser CSV, bien sûr, ce sont les pauses.
Bill Dueber
8
@Bill, le module CSV gère parfaitement les fichiers délimités par des tabulations ainsi que les fichiers csv réels. L'option: col_sep vous permet de spécifier le séparateur de colonne comme "\ t" et tout va bien.
tamouse
1
voici plus d'informations sur CSV docs.ruby-lang.org/en/2.1.0/CSV.html
veeresh yh
Utiliser des fichiers .tab avec ce module est ce que je fais, car ouvrir ceci dans Excel par accident gâcherait sinon l'encodage…
MrVocabulary

Réponses:

326

Vers un fichier:

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

À une chaîne:

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

Voici la documentation actuelle sur CSV: http://ruby-doc.org/stdlib/libdoc/csv/rdoc/index.html

Dylan Markow
la source
1
@David c'est le mode fichier. "w" signifie écrire dans un fichier. Si vous ne le spécifiez pas, il sera par défaut "rb" (mode binaire en lecture seule) et vous obtiendrez une erreur en essayant d'ajouter à votre fichier csv. Voir ruby-doc.org/core-1.9.3/IO.html pour une liste des modes de fichiers valides dans Ruby.
Dylan Markow
15
Je t'ai eu. Et pour les futurs utilisateurs, si vous voulez que chaque itération n'écrase pas le fichier csv précédent, utilisez l'option "ab".
boulder_ruby
1
Voir cette réponse pour les modes d'E / S des fichiers Ruby: stackoverflow.com/a/3682374/224707
Nick
38

J'ai ceci à une seule ligne.

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" 

Faites tout ce qui précède et enregistrez-le dans un csv, sur une seule ligne.

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

REMARQUE:

Pour convertir une base de données d'enregistrement active en csv, ce serait quelque chose comme ça, je pense

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

Hmm @tamouse, cet élément est quelque peu déroutant pour moi sans lire la source csv, mais de manière générique, en supposant que chaque hachage de votre tableau a le même nombre de paires k / v et que les clés sont toujours les mêmes, dans le même ordre (ie si vos données sont structurées), cela devrait faire l'acte:

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

Si vos données ne sont pas structurées, cela ne fonctionnera évidemment pas

boulder_ruby
la source
J'ai extrait un fichier CSV en utilisant CSV.table, fait quelques manipulations, me suis débarrassé de certaines colonnes, et maintenant je veux spouler le tableau de hachages résultant à nouveau au format CSV (vraiment délimité par des tabulations). Comment? gist.github.com/4647196
tamouse
hmm ... cette essence est quelque peu opaque, mais étant donné un tableau de hachages, tous avec le même nombre de paires k / v et les mêmes clés, dans le même ordre ...
boulder_ruby
Merci, @boulder_ruby. Ça marchera. Les données sont un tableau de recensement, et cet essentiel est plutôt opaque si l'on y regarde en arrière. :) Il s'agit essentiellement d'extraire certaines colonnes du tableau de recensement original dans un sous-ensemble.
tamouse
3
Vous abusez injectici, vous voulez vraiment utiliser map. De plus, vous n'avez pas besoin de passer une chaîne vide à join, car c'est la valeur par défaut. Vous pouvez donc le réduire encore plus à ceci:rows.map(&CSV.method(:generate_line).join
iGEL
1
Votre deuxième exemple est trop compliqué, car la bibliothèque CSV est assez puissante. CSV.generate(headers: hsh.first&.keys) { |csv| hsh.each { |e| csv << e } }génère un CSV équivalent.
Amadan
28

Si vous disposez d'un tableau de tableaux de données:

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

Ensuite, vous pouvez écrire ceci dans un fichier avec ce qui suit, ce qui, je pense, est beaucoup plus simple:

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

Si quelqu'un est intéressé, voici quelques one-liners (et une note sur la perte d'informations de type en 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

Remarque: CSV perd toutes les informations de type, vous pouvez utiliser JSON pour conserver les informations de type de base, ou passer à YAML détaillé (mais plus facilement modifiable par l'homme) pour conserver toutes les informations de type - par exemple, si vous avez besoin d'un type de date, qui deviendrait chaînes en CSV et JSON.

Kanat Bolazar
la source
9

En me basant sur la réponse de @ boulder_ruby, c'est ce que je recherche, en supposant qu'il us_ecocontienne la table CSV à partir de mon essence.

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

Mise à jour de l'essentiel à https://gist.github.com/tamouse/4647196

Tamouse
la source
2

J'ai du mal avec ça moi-même. Voici ma prise:

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) ]
Félix Rabe
la source
Btw, méfiez-vous des tableaux multidimensionnels en levier sur JRuby. [ %w(your array), %w(goes here) ]ne sera pas joli. github.com/pry/pry/issues/568
Felix Rabe