Pregunta

¿Cómo se analiza un archivo CSV usando gawk? Simplemente establecer FS = ", " no es suficiente, ya que un campo entre comillas con una coma dentro se tratará como múltiples campos.

Ejemplo usando FS = ", " que no funciona:

contenido del archivo:

one,two,"three, four",five
"six, seven",eight,"nine"

script gawk:

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) printf "field #%d: %s\n", i, $(i)
  printf "---------------------------\n"
}

salida incorrecta:

field #1: one
field #2: two
field #3: "three
field #4:  four"
field #5: five
---------------------------
field #1: "six
field #2:  seven"
field #3: eight
field #4: "nine"
---------------------------

salida deseada:

field #1: one
field #2: two
field #3: "three, four"
field #4: five
---------------------------
field #1: "six, seven"
field #2: eight
field #3: "nine"
---------------------------
¿Fue útil?

Solución

La respuesta corta es "no usaría gawk para analizar CSV si el CSV contiene datos incómodos", donde "incómodo" significa cosas como comas en los datos del campo CSV.

La siguiente pregunta es "¿Qué otro procesamiento vas a hacer?", ya que eso influirá en las alternativas que uses.

Probablemente usaría Perl y los módulos Text :: CSV o Text :: CSV_XS para leer y procesar los datos. Recuerde, Perl se escribió originalmente en parte como un asesino awk y sed , de ahí que los programas a2p y s2p todavía distribuido con Perl que convierte los scripts awk y sed (respectivamente) en Perl.

Otros consejos

El manual de la versión 4 de gawk dice usar FPAT = " ([^,] *) | (\ " [^ \ "] + \ ") "

Cuando se define FPAT , deshabilita FS y especifica los campos por contenido en lugar de por separador.

Puede usar una función de contenedor simple llamada csvquote para desinfectar la entrada y restaurarla después de que awk termine de procesarla. Canalice sus datos al principio y al final, y todo debería funcionar bien:

antes:

gawk -f mypgoram.awk input.csv

después de:

csvquote input.csv | gawk -f mypgoram.awk | csvquote -u

Consulte https://github.com/dbro/csvquote para obtener el código y la documentación.

Si está permitido, usaría el módulo Python csv , pagando especial atención al dialecto utilizado y los parámetros de formato necesarios , para analizar el archivo CSV que tienes.

csv2delim.awk

# csv2delim.awk converts comma delimited files with optional quotes to delim separated file
#     delim can be any character, defaults to tab
# assumes no repl characters in text, any delim in line converts to repl
#     repl can be any character, defaults to ~
# changes two consecutive quotes within quotes to '

# usage: gawk -f csv2delim.awk [-v delim=d] [-v repl=`"] input-file > output-file
#       -v delim    delimiter, defaults to tab
#       -v repl     replacement char, defaults to ~

# e.g. gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > test.txt

# abe 2-28-7
# abe 8-8-8 1.0 fixed empty fields, added replacement option
# abe 8-27-8 1.1 used split
# abe 8-27-8 1.2 inline rpl and "" = '
# abe 8-27-8 1.3 revert to 1.0 as it is much faster, split most of the time
# abe 8-29-8 1.4 better message if delim present

BEGIN {
    if (delim == "") delim = "\t"
    if (repl == "") repl = "~"
    print "csv2delim.awk v.m 1.4 run at " strftime() > "/dev/stderr" ###########################################
}

{
    #if (
"first","second","third"
"fir,st","second","third"
"first","sec""ond","third"
" first ",sec   ond,"third"
"first" , "second","th  ird"
"first","sec;ond","third"
"first","second","th;ird"
1,2,3
,2,3
1,2,
,2,
1,,2
1,"2",3
"1",2,"3"
"1",,"3"
1,"",3
"","",""
"","""aiyn","oh"""
"""","""",""""
11,2~2,3
~ repl) { # print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr" #} if (
rem test csv2delim
rem default is: -v delim={tab} -v repl=~
gawk                      -f csv2delim.awk test.csv > test.txt
gawk -v delim=;           -f csv2delim.awk test.csv > testd.txt
gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > testdr.txt
gawk            -v repl=` -f csv2delim.awk test.csv > testr.txt
~ delim) { print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr" print " replaced by " repl > "/dev/stderr" } gsub(delim, repl) <*> = gensub(/([^,])\"\"/, "\\1'", "g") # <*> = gensub(/\"\"([^,])/, "'\\1", "g") # not needed above covers all cases out = "" #for (i = 1; i <= length(<*>); i++) n = length(<*>) for (i = 1; i <= n; i++) if ((ch = substr(<*>, i, 1)) == "\"") inString = (inString) ? 0 : 1 # toggle inString else out = out ((ch == "," && ! inString) ? delim : ch) print out } END { print NR " records processed from " FILENAME " at " strftime() > "/dev/stderr" }

test.csv

<*>

test.bat

<*>

No estoy exactamente seguro de si esta es la forma correcta de hacer las cosas. Prefiero trabajar en un archivo csv en el que se citan todos los valores o ninguno. Por cierto, awk permite que las expresiones regulares sean separadores de campo. Comprueba si eso es útil.

{
  ColumnCount = 0
  <*> = <*> ","                           # Assures all fields end with comma
  while(<*>)                             # Get fields by pattern, not by delimiter
  {
    match(<*>, / *"[^"]*" *,|[^,]*,/)    # Find a field with its delimiter suffix
    Field = substr(<*>, RSTART, RLENGTH) # Get the located field with its delimiter
    gsub(/^ *"?|"? *,$/, "", Field)     # Strip delimiter text: comma/space/quote
    Column[++ColumnCount] = Field       # Save field without delimiter in an array
    <*> = substr(<*>, RLENGTH + 1)        # Remove processed text from the raw data
  }
}

Los patrones que siguen a este pueden acceder a los campos en la Columna []. ColumnCount indica el número de elementos en la columna [] que se encontraron. Si no todas las filas contienen el mismo número de columnas, Column [] contiene datos adicionales después de Column [ColumnCount] al procesar las filas más cortas.

Esta implementación es lenta, pero parece emular la función FPAT / patsplit () que se encuentra en gawk > = 4.0.0 mencionado en una respuesta anterior.

Referencia

Esto es lo que se me ocurrió. Cualquier comentario y / o mejores soluciones serán apreciados.

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) {
    f[++n] = $i
    if (substr(f[n],1,1)=="\"") {
      while (substr(f[n], length(f[n]))!="\"" || substr(f[n], length(f[n])-1, 1)=="\\") {
        f[n] = sprintf("%s,%s", f[n], $(++i))
      }
    }
  }
  for (i=1; i<=n; i++) printf "field #%d: %s\n", i, f[i]
  print "----------------------------------\n"
}

La idea básica es que recorro los campos, y cualquier campo que comienza con una cita pero no termina con una cita obtiene el siguiente campo adjunto.

Perl tiene el módulo Text :: CSV_XS que está especialmente diseñado para manejar la rareza entre comillas.
Alternativamente, pruebe el módulo Text :: CSV.

perl -MText :: CSV_XS -ne 'BEGIN {$ csv = Text :: CSV_XS- > new ()} if ($ csv- > parse ($ _)) {@ f = $ csv - > fields (); para $ n (0 .. $ # f) {print " field # $ n: $ f [$ n] \ n "}; print " --- \ n "} 'file .csv

Produce esta salida:

field #0: one
field #1: two
field #2: three, four
field #3: five
---
field #0: six, seven
field #1: eight
field #2: nine
---

Aquí hay una versión legible para humanos.
Guárdelo como parsecsv, chmod + x y ejecútelo como " parsecsv file.csv "

#!/usr/bin/perl
use warnings;
use strict;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new();
open(my $data, '<', $ARGV[0]) or die "Could not open '$ARGV[0]' $!\n";
while (my $line = <$data>) {
    if ($csv->parse($line)) {
        my @f = $csv->fields();
        for my $n (0..$#f) {
            print "field #$n: $f[$n]\n";
        }
        print "---\n";
    }
}

Puede que necesite apuntar a una versión diferente de perl en su máquina, ya que el módulo Text :: CSV_XS puede no estar instalado en su versión predeterminada de perl.

Can't locate Text/CSV_XS.pm in @INC (@INC contains: /home/gnu/lib/perl5/5.6.1/i686-linux /home/gnu/lib/perl5/5.6.1 /home/gnu/lib/perl5/site_perl/5.6.1/i686-linux /home/gnu/lib/perl5/site_perl/5.6.1 /home/gnu/lib/perl5/site_perl .).
BEGIN failed--compilation aborted.

Si ninguna de sus versiones de Perl tiene instalado Text :: CSV_XS, necesitará:
sudo apt-get install cpanminus
sudo cpanm Text :: CSV_XS

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top