Conversion de CSV en TSV

27

J'ai un certain nombre de fichiers CSV volumineux et je les souhaite au format TSV (format séparé par des tabulations). La complication est qu'il y a des virgules dans les champs du fichier CSV, par exemple:

 A,,C,"D,E,F","G",I,"K,L,M",Z

Production attendue:

 A      C   D,E,F   G   I   K,L,M   Z

(où les espaces entre les deux sont des onglets «durs»)

J'ai Perl, Python et coreutils installés sur ce serveur.

Cœur sombre
la source
Je le ferais avec node.js ou avec perl.
Peterh dit de réintégrer Monica le
1
Remplacez les virgules non citées par des tabulations ...
cricket_007
Oui, si j'avais plus de 5 minutes pour cette question. Mais je soutiendrai volontiers les répondeurs avec mes votes. Ce que j'ai essayé de dire, c'est que les choses sed / awk communes ne sont probablement pas éligibles pour cela (au moins dans leur utilisation couramment utilisée).
peterh dit réintégrer Monica le
6
Je ne sais pas si votre exemple est représentatif des données réelles, mais si elles doivent être de véritables chaînes de texte, n'oubliez pas que vous devrez peut-être gérer le cas où la chaîne comprend un onglet ...
AC
3
L'autre partie délicate est que CSV est un format défini de manière très vague, il n'y a pas de véritable standard (il y a un RFC mais il a été écrit des années après le fait). J'ai écrit du code qui utilisait un analyseur CSV fourni par la langue, puis j'ai dû le réécrire avec un analyseur personnalisé car j'ai trouvé que les données d'entrée étaient dans une variante cassée du format csv.
plugwash

Réponses:

37

Python

Ajouter au fichier nommé csv2tab.shet le rendre exécutable

#!/usr/bin/env python
import csv, sys
csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))

Essais

$ echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' | ./csv2tab.sh                         
A       C   D,E,F   G   I   K,L,M   Z

$ ./csv2tab.sh < data.csv > data.tsv && head data.tsv                                                   
1A      C   D,E,F   G   I   K,L,M   Z
2A      C   D,E,F   G   I   K,L,M   Z
3A      C   D,E,F   G   I   K,L,M   Z
cricket_007
la source
5
Un bug possible: cette réponse n'échappe pas aux onglets internes.
Morgen
4
@Morgen csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))? Élimine également la boucle.
muru
1
@chx essayez python -c 'import csv,sys; csv.writer(sys.stdout, dialect="excel-tab").writerows(csv.reader(sys.stdin))'. Je doute -mque cela fonctionne de cette façon.
muru
18

Pour le plaisir, sed.

sed -E 's/("([^"]*)")?,/\2\t/g' file

Si votre sedne prend pas en charge -E, essayez avec -r. Si votre sedne prend pas en charge \tun onglet littéral, essayez de mettre un onglet littéral (dans de nombreux shells, ctrl- v tab) ou dans Bash, utilisez une $'...'chaîne de style C (auquel cas la barre oblique inversée \2doit être doublée). Si vous souhaitez conserver les guillemets, utilisez \1au lieu de \2(auquel cas la paire de parenthèses internes est inutile et peut être supprimée).

Cela n'essaie pas de gérer les guillemets doubles échappés à l'intérieur des guillemets doubles; certains dialectes CSV le supportent en doublant le double guillemet cité (sic).

tripleee
la source
1
Je pense que j'ai essayé environ 100 scripts sed différents pour réaliser celui-ci mais toutes mes tentatives ont échoué. C'est génial.
George Vasiliou
16

Utilisation d'un csvkitutilitaire (Python), par exemple:

$ csvformat -T in.csv > out.txt

Est-ce que le streaming, avec des citations et échappements CSV et TSV corrects

C'est dans apt et autres gestionnaires de paquets

Neil McGuigan
la source
13

Une option pourrait être le module Text :: CSV de perl, par exemple

perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
' somefile

démontrer

echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' |
  perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
'
A       C   D,E,F   G   I   K,L,M   Z
tournevis
la source
1
Ne serait pas correct si un champ contient un onglet
Neil McGuigan
6

Perl

perl -lne '
   my $re = qr/,(?=(?:[^"]*"[^"]*")*(?![^"]*"))/;
   print join "\t", map { s/(?<!\\)"//gr =~ s/\\"/"/gr } split $re;
'

Awk

awk -v Q=\" -v FPAT="([^,]*)|(\"[^\"]+\")" -v OFS="\t" '{
   for (i=1; i<=NF; ++i)
      if ( substr($i, 1, 1) == Q )
         $i = substr($i, 2, length($i) - 2)
   print $1, $2, $3, $4, $5, $6, $7, $8
}'

Résultat:

A               C       D,E,F   G       I       K,L,M   Z

la source
+1 La version Perl fonctionne comme un charme
ATorras
4

La solution thermonucléaire de tapette à mouches doit utiliser libreoffice. Alors que https://ask.libreoffice.org/en/question/19042/is-is-possible-to-convert-comma-separated-value-csv-to-tab-separated-value-tsv-via-headless-mode / suggère que ce n'est pas possible mais que c'est faux (ou juste obsolète?) et la commande suivante fonctionne sur mon 5.3:

loffice "-env:UserInstallation=file:///tmp/LibO_Conversion" --convert-to csv:"Text - txt - csv (StarCalc)":9,34,UTF8 --headless --outdir some/path --infilter='csv:44,34,UTF8' *.csv

l' envargument peut être ignoré mais de cette façon, les documents n'apparaîtront pas dans votre document récent.

chx
la source
2
Je pense que le véritable flyswatter thermonucléaire serait d'écrire un utilitaire Java pour le faire via l'API UNO de LibreOffice :).
Pont
3

Si vous avez ou pouvez installer l' csvtoolutilitaire:

csvtool -t COMMA -u TAB cat in.csv > out.ctv

Notez que pour une raison quelconque, il csvtooln'a pas de page de manuel, mais csvtool --helpimprimera quelques centaines de lignes de documentation.

Keith Thompson
la source
3

L'utilisation mlrest presque succincte, mais la désactivation des en-têtes nécessite de longues options:

mlr --c2t --implicit-csv-header --headerless-csv-output cat file.csv 

Sortie:

A       C   D,E,F   G   I   K,L,M   Z
agc
la source
3

J'ai créé un convertisseur CSV vers TSV open source qui gère les transformations décrites. C'est assez rapide, peut valoir le coup d'œil s'il y a un besoin continu de convertir de gros fichiers CSV. L'outil fait partie de la boîte à outils des utilitaires TSV d' eBay (documentation csv2tsv ici ). Les options par défaut suffisent pour l'entrée décrite:

$ csv2tsv file.csv > file.tsv
JonDeg
la source
2

Vim

Juste pour le plaisir, des substitutions d'expression régulière peuvent être effectuées dans Vim . Voici une solution potentielle à quatre lignes, adaptée de: /programming/33332871/remove-all-commas-between-quotes-with-a-vim-regex

  1. Les virgules entre guillemets sont d'abord remplacées par des traits de soulignement (ou tout autre caractère absent),
  2. Toutes les autres virgules sont remplacées par des tabulations,
  3. Les soulignements à l'intérieur des guillemets sont restaurés en virgules,
  4. Les guillemets sont supprimés.

    :%s/".\{-}"/\=substitute(submatch(0), ',', '_' , 'g')/g
    :%s/,/\t/g
    :%s/_/,/g
    :%s/"//g

Pour écrire un peu la solution, les quatre lignes ci-dessus (sans signe deux-points) peuvent être enregistrées dans un fichier, par exemple to_tsv.vim. Ouvrez chaque CSV pour le modifier avec Vim et sourcele to_tsv.vimscript sur la ligne de commande Vim (adapté de /programming/3374179/run-vim-script-from-vim-commandline/8806874#8806874 ):

    :source /path/to/vim/filename/to_tsv.vim
jubilatoire1
la source
1

Voici l'exemple de conversion de CSV en TSV à l'aide de l' jqutilitaire :

$ jq -rn '@tsv "\(["A","","C","D,E,F","G","I","K,L,M","Z"])"'
A       C   D,E,F   G   I   K,L,M   Z

ou:

$ echo '["A","","C","D,E,F","G","I","K,L,M","Z"]' | jq -r @tsv
A       C   D,E,F   G   I   K,L,M   Z

Cependant, le format CSV doit être bien formaté, donc chaque chaîne doit être citée.

Source: format de sortie TSV simple .

kenorb
la source
1

Avec perl, en supposant que les champs csv n'ont pas de "tabulations ou de nouvelles lignes intégrées ou:

perl -pe 's{"(.*?)"|,}{$1//"\t"}ge'
Stéphane Chazelas
la source
0

Ce qui suit est simplement une correction à la réponse de @tripleee afin qu'il supprime toutes les citations du champ final comme il le fait pour tous les autres champs.

Pour montrer ce qui est corrigé, voici la réponse d' un tripleee , plus une légère modification des données d'exemple de l'OP avec des guillemets ajoutés autour du champ « Z » final .

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g'
A       C   D,E,F   G   I   K,L,M   "Z"

Vous pouvez voir que « Z » est laissé avec des guillemets autour. Ceci est différent de la façon dont les champs internes sont traités. Par exemple, le « G » n'a pas de guillemets dessus.

La commande suivante utilise une deuxième substitution pour nettoyer la dernière colonne:

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g' \
                                                -e 's/\t"([^"]*)"$/\t\1/'
A       C   D,E,F   G   I   K,L,M   Z
Fonnae
la source
1
Lorsque les données d'entrée 'A,,C,"D,E,F","G",I,"K,L,M","Z,A"'sont entrées dans cette réponse, le "Z,A"est incorrectement remplacé par Z A, plutôt que correct Z,A.
agc