Awk peut-il traiter avec un fichier CSV qui contient une virgule dans un champ Cité?
j'utilise awk pour effectuer le comptage de la somme d'une colonne dans le fichier csv. Le format de données est quelque chose comme:
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99
j'utilisais ce script awk pour compter la somme:
awk -F, '{sum+=} END {print sum}'
une partie de la valeur dans le champ name contient la virgule et ceci brise mon script awk. Ma question Est: awk peut-il résoudre ce problème? Si oui, et comment puis-je le faire?
Merci.
11 réponses
vous écrivez une fonction dans awk comme ci-dessous:
$ awk 'func isnum(x){return(x==x+0)}BEGIN{print isnum("hello"),isnum("-42")}'
0 1
vous pouvez incorporer dans votre script cette fonction et vérifier si le troisième champ est numérique ou non.si non numériques puis aller pour la 4ème champ et si le 4ème champ inturn est pas numberic aller pour la 5ème place ...jusqu'à ce que vous atteindre une valeur numérique.probablement une boucle de l'aide ici, et l'ajouter à la somme.
vous êtes probablement mieux de le faire en perl Avec Texte::CSV, car c'est une solution rapide et robuste.
vous pouvez aider awk travailler avec des champs de données qui contiennent des virgules (ou des lignes) en utilisant un petit script que j'ai écrit appelé csvquote. Il remplace les virgules à l'intérieur des champs cités par des caractères non imprimés. Si vous avez besoin, vous pouvez plus tard restaurer ces virgules - mais dans ce cas, vous n'avez pas besoin.
Voici la commande:
csvquote inputfile.csv | awk -F, '{sum+=} END {print sum}'
voir https://github.com/dbro/csvquote pour le code
pour un fichier d'entrée aussi simple que cela, vous pouvez simplement écrire une petite fonction pour convertir tous les FSS réels en dehors des citations à une autre valeur (J'ai choisi RS puisque le séparateur d'enregistrement ne peut pas faire partie de l'enregistrement) et puis utiliser que comme le FS, par exemple:
$ 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 = "151900920"
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)
"151900920" = 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>
cela ne se complique que lorsque vous avez affaire à des nouvelles lignes intégrées et des guillemets incrustés dans les guillemets et même alors ce n'est pas trop dur et tout a été fait avant...
voir Quel est le moyen le plus robuste pour analyser efficacement CSV en utilisant awk? pour plus d'informations.
vous pouvez toujours aborder le problème à partir de la source. Mettez des guillemets autour du champ de nom, tout comme le champ de "je suis la question". C'est beaucoup plus facile que de passer son temps à coder des solutions pour ça.
mise à Jour (comme Dennis demandé). Un exemple simple
$ 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
comme vous pouvez le voir, en mettant le délimiteur à double guillemet, les champs qui appartiennent aux" guillemets " sont toujours en nombre pair. Depuis l'OP n'a pas le luxe de modifier les données source, cette méthode ne sera pas appropriée pour lui.
si vous êtes sûr que la colonne "valeur" est toujours la dernière colonne:
awk -F, '{sum+=$NF} END {print sum}'
NF représente le nombre de champs, donc $NF est la dernière colonne
cet article m'a aidé à résoudre ce même problème de champ de données. La plupart CSV mettra un devis autour des champs avec des espaces ou des virgules à l'intérieur d'eux. Cela gâche le compte de champ pour awk à moins que vous les filtrez.
si vous avez besoin des données dans les champs qui contiennent les ordures, ce n'est pas pour vous. ghostdog74
a fourni la réponse, qui vide ce champ mais maintient le nombre total de champs à la fin, ce qui est la clé pour garder la sortie de données cohérente. Je n'ai aime pas cette solution introduit de nouvelles lignes. C'est la version de cette solution que j'ai utilisée. Les trois premiers champs n'ont jamais eu ce problème dans les données. Le quatrième champ contenant le nom du client le faisait souvent, mais j'avais besoin de ces données. Les autres champs qui montrent le problème que je pourrais jeter sans problème parce qu'il n'était pas nécessaire dans mon rapport de sortie. Donc j'ai d'abord sorti les ordures du 4ème champ très spécifiquement et j'ai enlevé les deux premières instances de citations. Puis j'applique ce que ghostdog74
a donné pour vider les champs restants qui ont des virgules à l'intérieur - cela supprime aussi les guillemets, mais j'utilise printf
pour maintenir les données dans un seul enregistrement. Je commence avec 85 champs et je finis avec 85 champs dans tous les cas à partir de mes 8000+ lignes de données désordonnées. Un score parfait!
grep -i $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 solution qui vide les champs avec des virgules à l'intérieur d'eux mais maintient aussi le record, bien sûr est:
awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}
merci à ghostdog74 pour la grande solution!
NetsGuy256/
j'utilise
`FPAT="([^,]+)|(\"[^\"]+\")" `
pour définir les champs avec gawk. J'ai trouvé que lorsque le champ est nul, cela ne reconnaît pas le nombre correct de champs. Parce que " + " nécessite au moins 1 caractère dans le champ. Je l'ai changé en:
`FPAT="([^,]*)|(\"[^\"]*\")"`
et remplacer "+"
par "*"
. Il fonctionne correctement.
je trouve aussi que GNU Awk User Guide a aussi ce problème. https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content.html
FPAT est la solution élégante parce qu'elle peut gérer les dreaded virgule à l'intérieur de guillemets problème, mais pour résumer une colonne de nombres dans la dernière colonne quel que soit le nombre de séparateurs précédents, $NF fonctionne bien:
awk -F"," '{sum+=$NF} END {print sum}'
pour accéder à la dernière colonne, vous devez utiliser ceci:
awk -F"," '{sum+=$(NF-1)} END {print sum}'
parseurs CSV à part entière tels que Perl Text::CSV_XS
sont conçus pour gérer ce genre de bizarrerie.
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
est nécessaire car les données d'entrée ont un espace entourant les séparateurs de virgule. Les versions très anciennes de Text::CSV_XS
peuvent ne pas supporter cette option.
j'ai fourni plus d'explication de Text::CSV_XS
dans ma réponse ici: parse fichier csv utilisant gawk