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.


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


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


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)

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.


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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.