2010-11-17 7 views
26

Ho un file csv in cui ogni riga definisce una stanza in un determinato edificio. Insieme alla stanza, ogni fila ha un campo di pavimento. Quello che voglio estrarre sono tutti i piani di tutti gli edifici.Analizza un csv usando awk e ignorando le virgole all'interno di un campo

Il mio file si presenta così ...

"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 " 

Quello che voglio è tutti i piani di tutti gli edifici.

Sto usando cat, awk, sort e uniq per ottenere questo elenco anche se ho un problema con il "," nel campo del nome dell'edificio come "BRICKER HALL, JOHN W" e sta buttando via il mio intero generazione csv.

Come posso ottenere awk per utilizzare la virgola ma ignorare una virgola tra "" di un campo? In alternativa, qualcuno ha una soluzione migliore?

Sulla base della risposta fornita suggerendo un parser awk csv ero in grado di ottenere la soluzione:

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

Non vogliamo usare il programma csv awk e poi da lì che voglio usare un "-> 2 |" che è una formattazione basata sul programma csv awk. La stampa $ 2 stampa solo il contenuto analizzato csv, questo perché il programma stampa la riga originale seguita da "-> #" dove # è il conteggio analizzato da csv. (Cioè le colonne.) Da lì posso dividere questo risultato di awk csv sul "|" con ciò sostituisce la virgola con. Quindi ordina, uniq e condividi un file e fallo!

Grazie per l'aiuto.

risposta

7

L'uscita in più che stai ricevendo dal csv.awk da codice demo. Si intende utilizzare le funzioni all'interno dello script per eseguire l'analisi e quindi inviarlo come desiderato.

Alla fine di csv.awk è il ciclo { ... } che dimostra una delle funzioni. È quel codice che sta emettendo lo -> 2|.

Invece la maggior parte di questo, basta chiamare la funzione di analisi e fare print csv[1], csv[2].

Quella parte del codice sarà quindi simile:

{ 
    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] 
    } 
} 

Salva come your_script (per esempio).

Do chmod +x your_script.

E cat non è necessario. Inoltre, è possibile eseguire sort -u anziché sort | uniq.

Il comando sarebbe quindi simile:

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

Questa grande opera ad eccezione di "csv stampa [1], csv [2]" dovrebbe in realtà essere "csv stampa [0], csv [1]" Grazie! – Chris

+0

Qualche idea su come ottenere awk per eliminare gli spazi bianchi extra sui campi e non utilizzare una larghezza fissa? "TEST AEROPORTO" Voglio essere "TEST AEROPORTO" – Chris

+0

@Chris: lo spazio bianco è una domanda separata, perché se stampo csv [0], csv [1] 'ottengo" 0 00BDF "anziché" AIRPORT TEST " ? –

4

La mia soluzione è mettere a nudo le virgole dal CSV utilizzando:

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

Cioè, prima apertura sostituto cita con "((" e la chiusura cita con "))", quindi sostituisci "((" qualunque cosa, qualunque "))" con "qualunque cosa", quindi modifica tutte le istanze rimanenti di "(" ("e")) ".

+4

Non riesco a capire come sia possibile rimuovere le virgole da un CSV? – Chris

2

Puoi usa una sceneggiatura che ho scritto c csvquote alleato per lasciare che awk ignori le virgole nei campi citati. Il comando diventerebbe allora:

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

e taglio potrebbe essere un po 'più facile che awk per questo:

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

È possibile trovare il codice csvquote qui: https://github.com/dbro/csvquote

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

Questo è un'eccellente estensione GNU Awk 4, in cui si definisce un modello di campo al posto di un modello separatore di campo. Fa miracoli per CSV. (docs)

ETA (mitchus grazie): per rimuovere le virgolette che circondano, gsub("^\"|\"$","",$3); se ci sono più campi oltre a $3 per elaborarli, è sufficiente eseguirne il ciclo.
Nota questo approccio semplice non è tollerante per l'input malformato, né di alcuni possibili caratteri speciali tra virgolette - che coprono tutti questi elementi andrebbero oltre lo scopo di un semplice one-liner.

+0

Questa è una grande scoperta! Rende superfluo un lib di CSV esterno in molti casi. – MattK

+0

Fantastico !! - potrebbe anche essere modificato in modo che le virgolette siano spogliate se presenti. Ho un output che ha solo virgolette se è presente una virgola nel campo stesso – nwaltham

+1

Solo per le altre persone che usano mac: OS X non viene fornito con GAWK, hanno awk dal 2007. Quindi in pratica è necessario installarlo da soli 'brew install gawk' e fa davvero miracoli per CSV. –

0

I parser CSV completi come Perl Text::CSV_XS sono costruiti appositamente per gestire questo tipo di stranezze.

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

La linea di ingresso è diviso in matrice @f
Campo 1 è $f[0] dal Perl inizia a indicizzare a 0

uscita:

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 

ho fornito ulteriori spiegazioni di Text::CSV_XS nel mio rispondi qui: parse csv file using gawk

0

Poiché il problema consiste in realtà nella distinzione tra una virgola all'interno di un campo CSV e quella che separa i campi, possiamo sostituire il primo tipo di virgola con qualcos'altro in modo che sia più semplice analizzare ulteriormente, ad esempio:

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

Questo script gawk (sostituire-comma.awk) lo fa:

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

Questo utilizza una funzione di gawk che cattura il separatore di record attuale in una variabile denominata RT. Si divide ogni personaggio in un record, e mentre stiamo leggendo i record, sostituiamo la virgola rilevata all'interno di una citazione (\x022) con <comma>.

La soluzione FPAT non riesce in un caso particolare in cui si dispone sia sfuggito citazioni e una virgola all'interno di citazioni, ma questa soluzione funziona in tutti i casi, vale a dire,

§ 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 

come un one-liner per un facile copia-incolla :

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