Convertir CSV a TSV

27

Tengo varios archivos CSV grandes y me gustaría tenerlos en TSV (formato separado por tabulaciones). La complicación es que hay comas en los campos del archivo CSV, por ejemplo:

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

Rendimiento esperado:

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

(donde los espacios en blanco en el medio son pestañas "duras")

Tengo Perl, Python y coreutils instalados en este servidor.

Corazón oscuro
fuente
Haría esto con node.js o con perl.
Peter dice reinstalar a Mónica el
1
Reemplazar comas no citadas con pestañas ...
cricket_007
Sí, si tuviera más de 5 minutos para esta pregunta. Pero felizmente apoyaré a los que responden con mis votos. Lo que intenté decir es que las cosas comunes de sed / awk probablemente no son elegibles para eso (al menos en su uso común).
Peter dice reinstalar a Mónica el
66
No estoy seguro de si su ejemplo es representativo de los datos reales, pero si serán cadenas de texto reales, no olvide que es posible que deba manejar el caso en el que la cadena incluye una pestaña ...
AC
3
La otra parte difícil es que CSV es un formato muy poco definido, no hay un estándar real (hay un RFC pero fue escrito años después del hecho). Escribí un código que usaba un analizador CSV proporcionado por el lenguaje y luego tuve que reescribirlo con un analizador personalizado porque descubrí que los datos de entrada estaban en una variante rota del formato csv.
plugwash

Respuestas:

37

Pitón

Agregar al archivo nombrado csv2tab.shy hacerlo ejecutable

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

Pruebas de funcionamiento

$ 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
fuente
55
Un posible error: esta respuesta no escapa a las pestañas internas.
Morgen
44
@ Morgen csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))? Elimina el bucle también.
muru
1
@chx prueba python -c 'import csv,sys; csv.writer(sys.stdout, dialect="excel-tab").writerows(csv.reader(sys.stdin))'. Dudo -mque funcione de esa manera.
muru
18

Para la diversión, sed.

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

Si su sedno es compatible -E, intente con -r. Si sedno admite \tuna pestaña literal, intente colocar una pestaña literal (en muchos shells, ctrl- v tab) o en Bash, use una $'...'cadena de estilo C (en cuyo caso la barra invertida \2debe duplicarse). Si desea mantener las comillas, use en \1lugar de \2(en cuyo caso el par de paréntesis interno es inútil y puede eliminarse).

Esto no intenta manejar comillas dobles escapadas dentro de comillas dobles; algunos dialectos CSV respaldan esto al duplicar la cita doble (sic) citada.

tripleee
fuente
1
Creo que probé unos 100 scripts sed diferentes para lograr este, pero todos mis intentos fallaron. Esto es asombroso
George Vasiliou
16

Usando la csvkitutilidad (Python), por ejemplo:

$ csvformat -T in.csv > out.txt

Hace streaming, con citas y escapes CSV y TSV correctos

Está en apt y otros gestores de paquetes

Neil McGuigan
fuente
13

Una opción podría ser el módulo Text :: CSV de perl, p. Ej.

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

demostrar

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
conductor de acero
fuente
1
No sería correcto si un campo contiene una pestaña
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
}'

Resultado:

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

fuente
La versión +1 de Perl funciona a las
mil maravillas
4

La solución termonuclear flyswatter debe estar usando libreoffice. Mientras 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 / sugiere que esto no es posible pero está mal (¿o simplemente está desactualizado?) y el siguiente comando funciona en mi 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

se envpodría omitir el argumento, pero de esta forma los documentos no aparecerán en su documento reciente.

chx
fuente
2
Creo que el verdadero matamoscas termonuclear estaría escribiendo una utilidad Java para hacerlo a través de la API UNO de LibreOffice :).
Pont
3

Si tiene, o puede instalar, la csvtoolutilidad:

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

Tenga en cuenta que por alguna razón csvtoolno tiene una página de manual, pero csvtool --helpimprimirá un par de cientos de líneas de documentación.

Keith Thompson
fuente
3

Usar mlres casi sucinto, pero deshabilitar los encabezados requiere opciones largas:

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

Salida:

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

Soy autor de un convertidor CSV a TSV de código abierto que maneja las transformaciones descritas. Es bastante rápido, puede valer la pena echarle un vistazo si hay una necesidad continua de convertir grandes archivos CSV. La herramienta es parte del kit de herramientas de utilidades TSV de eBay (documentación de csv2tsv aquí ). Las opciones predeterminadas son suficientes para la entrada descrita:

$ csv2tsv file.csv > file.tsv
JonDeg
fuente
2

Empuje

Solo por diversión, las sustituciones de expresiones regulares se pueden realizar en Vim . Aquí hay una posible solución de cuatro líneas, adaptada de: /programming/33332871/remove-all-commas-between-quotes-with-a-vim-regex

  1. Las comas entre comillas se cambian primero a guiones bajos (u otro carácter ausente),
  2. Todas las demás comas se reemplazan con pestañas,
  3. Los guiones bajos entre comillas se restauran a comas
  4. Las comillas se eliminan.

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

Para escribir un poco la solución, las cuatro líneas anteriores (sin dos puntos principales) se pueden guardar en un archivo, por ejemplo to_tsv.vim. Abra cada CSV para editar con Vim y sourceel to_tsv.vimscript en la línea de comando de Vim (adaptado de /programming/3374179/run-vim-script-from-vim-commandline/8806874#8806874 ):

    :source /path/to/vim/filename/to_tsv.vim
jubilatious1
fuente
1

Aquí está el ejemplo de convertir CSV en TSV usando la jqutilidad :

$ 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

o:

$ 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

Sin embargo, el formato CSV debe estar bien formateado, por lo que cada cadena debe ser citada.

Fuente: formato de salida TSV simple .

kenorb
fuente
1

Con perl, suponiendo que los campos csv no tengan incrustadas "o nuevas líneas o pestañas:

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

Lo siguiente es simplemente una corrección a la respuesta de @tripleee para que elimine las comillas del campo final tal como lo hace con todos los demás campos.

Para mostrar lo que se está corrigiendo, a continuación se muestra la respuesta de un triplicado , más una ligera modificación a los datos de ejemplo del OP con citas agregadas alrededor del campo ' 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"

Puede ver que ' Z ' se deja entre comillas. Esto es diferente a cómo se manejan los campos internos. Por ejemplo, la ' G ' no tiene comillas.

El siguiente comando usa una segunda sustitución para limpiar la columna final:

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
fuente
1
Cuando los datos de entrada 'A,,C,"D,E,F","G",I,"K,L,M","Z,A"'se ingresan a esta respuesta, entonces "Z,A"se reemplaza incorrectamente con Z A, en lugar de la correcta Z,A.
agc