Può awk accordo con file CSV che contiene una virgola all'interno di un campo citato?
-
01-10-2019 - |
Domanda
Sto usando awk per eseguire contare la somma di una colonna nel file CSV. Il formato dei dati è qualcosa di simile:
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99
Io sto usando questo script awk per contare la somma:
awk -F, '{sum+=$3} END {print sum}'
Alcuni del valore nel campo Nome contiene virgola e questa pausa il mio script awk. La mia domanda è: può awk risolvere questo problema? Se sì, e come posso farlo?
Grazie.
Soluzione
si scrive una funzione in awk come di seguito:
$ awk 'func isnum(x){return(x==x+0)}BEGIN{print isnum("hello"),isnum("-42")}'
0 1
è possibile incorporare nello script di questa funzione e controllare se il terzo campo è numerico o NOT.IF non numerico poi andare per il 4 ° campo e se il campo inturn 4 ° non è andare numberic per il 5 ° ... fino a raggiungere un numerica value.probably un ciclo aiuterà qui, e aggiungerlo alla somma.
Altri suggerimenti
Un modo utilizzando GNU awk
e FPAT
awk 'BEGIN { FPAT = "([^, ]+)|(\"[^\"]+\")" } { sum+=$3 } END { print sum }' file.txt
Risultato:
192
Sei probabilmente meglio farlo in Perl con il testo :: CSV, dato che è una soluzione veloce e robusto.
Si può aiutare awk i campi di dati che contengono le virgole (o ritorni a capo) utilizzando un piccolo script che ho scritto chiamato csvquote. Esso sostituisce le virgole offendere all'interno campi citati con caratteri non stampabili. Se è necessario, in seguito sarà possibile ripristinare quelle virgole -. Ma in questo caso, non è necessario
Ecco il comando:
csvquote inputfile.csv | awk -F, '{sum+=$3} END {print sum}'
https://github.com/dbro/csvquote per il codice
sto usando
`FPAT="([^,]+)|(\"[^\"]+\")" `
per definire i campi con gawk. Ho trovato che quando il campo è nullo questo non riconosce corretto numero di campi. Perché "+" richiede almeno 1 carattere nel campo. Ho cambiato in:
`FPAT="([^,]*)|(\"[^\"]*\")"`
e sostituirlo con "+"
"*"
. Esso funziona correttamente.
Trovo anche che Guida GNU Awk l'utente ha anche questo problema. https://www.gnu.org/software/gawk /manual/html_node/Splitting-By-Content.html
Per come semplice file di input, come che si può solo scrivere una piccola funzione per convertire tutto il reale al di fuori FS delle citazioni per qualche altro valore (ho scelto RS poiché il separatore di record non può far parte del record) e poi l'uso che come FS, ad esempio:
$ cat decsv.awk
BEGIN{ fs=FS; FS=RS }
{
decsv()
for (i=1;i<=NF;i++) {
printf "Record %d, Field %d is <%s>\n" ,NR,i,$i
}
print ""
}
function decsv( curr,head,tail)
{
tail = $0
while ( match(tail,/"[^"]+"/) ) {
head = substr(tail, 1, RSTART-1);
gsub(fs,RS,head)
curr = curr head substr(tail, RSTART, RLENGTH)
tail = substr(tail, RSTART + RLENGTH)
}
gsub(fs,RS,tail)
$0 = curr tail
}
$ cat file
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99
$ awk -F", " -f decsv.awk file
Record 1, Field 1 is <id>
Record 1, Field 2 is <name>
Record 1, Field 3 is <value>
Record 2, Field 1 is <1>
Record 2, Field 2 is <foo>
Record 2, Field 3 is <17>
Record 3, Field 1 is <2>
Record 3, Field 2 is <bar>
Record 3, Field 3 is <76>
Record 4, Field 1 is <3>
Record 4, Field 2 is <"I am the, question">
Record 4, Field 3 is <99>
E 'solo si complica quando si ha a che fare con a capo incorporati e citazioni sfuggiti incorporati all'interno delle citazioni e anche allora non è troppo duro e tutto è stato fatto prima ...
Qual è il modo più robusto per efficiente CSV parse utilizzando awk? per ulteriori informazioni.
È sempre possibile affrontare il problema alla fonte. Mettere le virgolette intorno al campo del nome, proprio come il campo di "Io sono il, domanda". Questo è molto più facile che spendere il vostro tempo di codifica soluzioni alternative per questo.
Aggiorna (come Dennis richiesto). Un semplice esempio
$ s='id, "name1,name2", value 1, foo, 17 2, bar, 76 3, "I am the, question", 99'
$ echo $s|awk -F'"' '{ for(i=1;i<=NF;i+=2) print $i}'
id,
, value 1, foo, 17 2, bar, 76 3,
, 99
$ echo $s|awk -F'"' '{ for(i=2;i<=NF;i+=2) print $i}'
name1,name2
I am the, question
Come si può vedere, impostando il delimitatore di virgolette, i campi che appartengono ai "virgolette" sono sempre in numero pari. Poiché OP non ha il lusso di modificare i dati di origine, questo metodo non sarà il caso.
Se si sa per certo che la colonna 'valore' è sempre l'ultima colonna:
awk -F, '{sum+=$NF} END {print sum}'
NF rappresenta il numero di campi, in modo da $ NF è l'ultima colonna
Questo articolo ha aiutato a risolvere questo stesso problema campo di dati. La maggior parte CSV metterà un preventivo intorno campi con spazi o virgole al loro interno. Questo scombina il campo contano per awk a meno che non filtrare fuori.
Se avete bisogno dei dati all'interno di quei campi che contengono la spazzatura, questo non è per voi. ghostdog74
fornito la risposta, che svuota quel campo ma mantiene il conteggio campo totale, alla fine, che è la chiave per mantenere l'uscita dati coerenti. Non mi piaceva come questa soluzione ha introdotto nuove linee. Questa è la versione di questa soluzione che ho usato. Il pugno tre campi mai avuto questo problema nei dati. Il campo di quarta che contiene il nome del cliente spesso ha fatto, ma avevo bisogno di quei dati. I restanti campi che presentano il problema potevo buttare via senza problema perché non era necessario nel mio emissione del report. Così ho sed fuori spazzatura il 4 ° del campo in modo molto specifico e rimuovere le prime due istanze di citazioni. Poi applico quello che ghostdog74
gave per svuotare i campi rimanenti che hanno le virgole al loro interno - questo rimuove anche le virgolette, ma io uso printf
to mantenere i dati in un singolo record. Comincio con 85 campi e finire con 85 campi in tutti i casi da parte dei miei 8000+ righe di dati disordinato. Un punteggio perfetto!
grep -i $1 $dbfile | sed 's/\, Inc.//;s/, LLC.//;s/, LLC//;s/, Ltd.//;s/\"//;s/\"//' | awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}' > $tmpfile
La soluzione che svuota i campi con le virgole al loro interno, ma mantiene anche il record, ovviamente è:
awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}
mega di grazie al ghostdog74 per la grande soluzione!
NetsGuy256 /
FPAT è la soluzione elegante perché è in grado di gestire le virgole temute all'interno problema citazioni, ma per riassumere una colonna di numeri nell'ultima colonna a prescindere dal numero di precedenti separatori, $ NF funziona bene:
awk -F"," '{sum+=$NF} END {print sum}'
Per accedere alla penultima colonna, si può usare questa:
awk -F"," '{sum+=$(NF-1)} END {print sum}'
parser CSV a tutti gli effetti, come Text::CSV_XS
di Perl sono costruiti appositamente per gestire questo tipo di stranezza.
perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new({allow_whitespace => 1})} if($csv->parse($_)){@f=$csv->fields();$sum+=$f[2]} END{print $sum}' file
allow_whitespace
poiché i dati di ingresso ha spazi che lo circondano i separatori virgola. Molto vecchie versioni di Text::CSV_XS
potrebbero non supportare questa opzione.
ho fornito una spiegazione più approfondita di Text::CSV_XS
nel mio risposta qui: parse csv file utilizzando gawk