2010-11-17 9 views
26

Tengo un archivo csv donde cada fila define una habitación en un edificio determinado. Junto con la sala, cada fila tiene un campo de piso. Lo que quiero extraer son todos los pisos en todos los edificios.Analizar un csv usando awk e ignorar comas dentro de un campo

Mi archivo tiene este aspecto ...

"u_floor","u_room","name" 
0,"00BDF","AIRPORT TEST   " 
0,0,"BRICKER HALL, JOHN W " 
0,3,"BRICKER HALL, JOHN W " 
0,5,"BRICKER HALL, JOHN W " 
0,6,"BRICKER HALL, JOHN W " 
0,7,"BRICKER HALL, JOHN W " 
0,8,"BRICKER HALL, JOHN W " 
0,9,"BRICKER HALL, JOHN W " 
0,19,"BRICKER HALL, JOHN W " 
0,20,"BRICKER HALL, JOHN W " 
0,21,"BRICKER HALL, JOHN W " 
0,25,"BRICKER HALL, JOHN W " 
0,27,"BRICKER HALL, JOHN W " 
0,29,"BRICKER HALL, JOHN W " 
0,35,"BRICKER HALL, JOHN W " 
0,45,"BRICKER HALL, JOHN W " 
0,59,"BRICKER HALL, JOHN W " 
0,60,"BRICKER HALL, JOHN W " 
0,61,"BRICKER HALL, JOHN W " 
0,63,"BRICKER HALL, JOHN W " 
0,"0006M","BRICKER HALL, JOHN W " 
0,"0008A","BRICKER HALL, JOHN W " 
0,"0008B","BRICKER HALL, JOHN W " 
0,"0008C","BRICKER HALL, JOHN W " 
0,"0008D","BRICKER HALL, JOHN W " 
0,"0008E","BRICKER HALL, JOHN W " 
0,"0008F","BRICKER HALL, JOHN W " 
0,"0008G","BRICKER HALL, JOHN W " 
0,"0008H","BRICKER HALL, JOHN W " 

Lo que quiero es todas las plantas en todos los edificios.

Estoy usando cat, awk, sort y uniq para obtener esta lista aunque estoy teniendo un problema con el "," en el campo de nombre del edificio como "BRICKER HALL, JOHN W" y está tirando todo mi generación csv

cat Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq > Floors.csv 

¿Cómo puedo obtener awk para usar la coma, pero ignorar una coma entre "" de un campo? Alternativamente, ¿alguien tiene una mejor solución?

Sobre la base de la respuesta dada sugiere un analizador awk csv yo era capaz de obtener la solución:

cat Buildings.csv | awk -f csv.awk | awk -F" -> 2|" '{print $2}' | awk -F"|" '{print $2","$3}' | sort | uniq > floors.csv 

No queremos usar el programa csv awk y luego desde allí Quiero usar un "-> 2 | " que es el formato basado en el programa csv awk. La impresión $ 2 allí imprime únicamente el contenido analizado csv, esto es porque el programa imprime la línea original seguida de "-> #" donde # es el conteo analizado desde csv. (Es decir, las columnas.) A partir de ahí puedo dividir este resultado awk csv en el "|" que es con lo que reemplaza la coma. ¡Entonces el género, uniq y pipe a un archivo y listo!

Gracias por la ayuda.

Respuesta

7

la producción adicional que está recibiendo de csv.awk es a partir del código de demostración. Se pretende que use las funciones dentro del script para realizar el análisis sintáctico y luego lo muestre como lo desee.

Al final de csv.awk es el lazo { ... } que muestra una de las funciones. Es ese código que está sacando el -> 2|.

lugar la mayor parte de que, a llamar a la función de análisis y hacer print csv[1], csv[2].

Esa parte del código sería el siguiente aspecto:

{ 
    num_fields = parse_csv($0, csv, ",", "\"", "\"", "\\n", 1); 
    if (num_fields < 0) { 
     printf "ERROR: %s (%d) -> %s\n", csverr, num_fields, $0; 
    } else { 
#  printf "%s -> ", $0; 
#  printf "%s", num_fields; 
#  for (i = 0;i < num_fields;i++) { 
#   printf "|%s", csv[i]; 
#  } 
#  printf "|\n"; 
     print csv[1], csv[2] 
    } 
} 

Guardar como your_script (por ejemplo).

Do chmod +x your_script.

Y cat es innecesaria. Además, puede hacer sort -u en lugar de sort | uniq.

su comando sería el siguiente aspecto:

./yourscript Buildings.csv | sort -u > floors.csv 
+0

Esto funciona muy bien excepto "csv de impresión [1], csv [2]" en realidad debería ser "csv de impresión [0], csv [1]" Gracias! – Chris

+0

¿Alguna idea de cómo obtener awk para deshacerse del espacio en blanco adicional en los campos y no usar un ancho fijo? "AIRPORT TEST" Quiero ser "AIRPORT TEST" – Chris

+0

@Chris: Es el espacio en blanco una pregunta separada, porque si 'imprimo csv [0], csv [1]' obtengo "0 00BDF" en lugar de "AIRPORT TEST" ? –

4

Mi solución es quitar comas de la CSV usando:

decommaize() { 
    cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2 
} 

Es decir, la primera apertura sustituto cita con "((" y el cierre comillas con "))", luego sustituya "(" lo que sea, lo que sea ")) con" whateverwhatever ", luego cambie todas las instancias restantes de" (("y")) "de nuevo a".

+4

No entiendo cómo ayuda quitar las comas de un CSV? – Chris

2

Puede usar un script que escribí c alled csvquote para que awk ignore las comas dentro de los campos cotizados. El comando se convertiría entonces en:

csvquote Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq | csvquote -u > Floors.csv 

y corte podría ser un poco más fácil que awk para esto:

csvquote Buildings.csv | cut -d, -f1,2 | sort | uniq | csvquote -u > Floors.csv 

Puede encontrar el código csvquote aquí: https://github.com/dbro/csvquote

31
gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq 

Este es una asombrosa extensión de GNU Awk 4, donde se define un patrón de campo en lugar de un patrón de separador de campo. Hace maravillas para CSV. (docs)

ETA (mitchus gracias): para quitar las comillas rodean, gsub("^\"|\"$","",$3); si hay más campos que solo $3 para procesar de esa manera, simplemente repáselos.
Tenga en cuenta que este enfoque simple no es tolerante a las entradas mal formadas, ni a algunos posibles caracteres especiales entre comillas, que cubren todos los que irían más allá del alcance de un único borrador.

+0

¡Este es un gran hallazgo! Hace innecesaria una lib de CSV externa en muchos casos. – MattK

+0

¡Impresionante! - también podría modificarse para que las comillas se eliminen si están presentes. Tengo un resultado que solo tiene comillas si hay una coma en el campo – nwaltham

+1

Solo para otras personas que usan Mac: OS X no viene con GAWK, tienen awk desde 2007. Entonces, básicamente, necesitas instalarlo tú mismo 'brew install gawk' y realmente hace maravillas para CSV. –

0

Analizadores de CSV completos como Perl's Text::CSV_XS están especialmente diseñados para manejar ese tipo de rareza.

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "$f[0],$f[1]" }' file

La línea de entrada se divide en array @f
Campo 1 es $f[0] desde Perl inicia la indexación a 0

salida:

u_floor,u_room 
0,00BDF 
0,0 
0,3 
0,5 
0,6 
0,7 
0,8 
0,9 
0,19 
0,20 
0,21 
0,25 
0,27 
0,29 
0,35 
0,45 
0,59 
0,60 
0,61 
0,63 
0,0006M 
0,0008A 
0,0008B 
0,0008C 
0,0008D 
0,0008E 
0,0008F 
0,0008G 
0,0008H 

I, siempre más explicación de Text::CSV_XS dentro de mi respuesta aquí: parse csv file using gawk

0

Dado que el problema es realmente distinguir entre una coma dentro de un campo CSV y la que separa campos, podemos reemplazar el primer tipo de coma con otra cosa para que sea más fácil analizar más, es decir, algo como esto:

0,"00BDF","AIRPORT TEST   " 
0,0,"BRICKER HALL<comma> JOHN W " 

Este script gawk (replace-comma.awk) hace eso:

BEGIN { RS = "(.)" } 
RT == "\x022" { inside++; } 
{ if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); } 

Esta función utiliza un gawk que captura el separador de registro real en una variable llamada RT. Divide cada carácter en un registro, y mientras leemos los registros, reemplazamos la coma encontrada dentro de una cotización (\x022) con <comma>.

La solución FPAT falla en un caso especial en el que ambos han escapado citas y una coma entre comillas, pero esta solución funciona en todos los casos, es decir,

§ echo '"Adams, John ""Big Foot""",1' | gawk -vFPAT='[^,]*|"[^"]*"' '{ print $1 }' 
"Adams, John " 
§ echo '"Adams, John ""Big Foot""",1' | gawk -f replace-comma.awk | gawk -F, '{ print $1; }' 
"Adams<comma> John ""Big Foot""",1 

Como una sola línea para una fácil copiar y pegar :

gawk 'BEGIN { RS = "(.)" } RT == "\x022" { inside++; } { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }' 
Cuestiones relacionadas