web-dev-qa-db-fra.com

Analyser un CSV en utilisant awk et en ignorant les virgules à l'intérieur d'un champ

J'ai un fichier CSV où chaque ligne définit une pièce dans un bâtiment donné. En plus de la pièce, chaque rangée a un terrain. Ce que je veux extraire, ce sont tous les étages de tous les bâtiments. 

Mon fichier ressemble à ça ... 

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

Ce que je veux, ce sont tous les étages de tous les bâtiments. 

J'utilise cat, awk, sort et uniq pour obtenir cette liste bien que je rencontre un problème avec le "," dans le champ du nom du bâtiment, tel que "BRICKER HALL, JOHN W", qui jette toute ma génération de fichiers csv.

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

Comment puis-je obtenir awk pour utiliser la virgule mais ignorer une virgule entre "" d'un champ? Sinon, quelqu'un a-t-il une meilleure solution? 

Sur la base de la réponse suggérant un analyseur awk csv, j'ai pu obtenir la solution:

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

Là nous voulons utiliser le programme csv awk et à partir de là je veux utiliser un "-> 2 |" qui est un formatage basé sur le programme csv awk. L'impression $ 2 n'imprime que le contenu analysé par csv, car le programme imprime la ligne d'origine suivie de "-> #" où # est le nombre analysé à partir de csv. (Par exemple, les colonnes.) À partir de là, je peux scinder ce résultat awk csv sur le "|" c'est ce qui remplace la virgule. Ensuite, triez, uniq et dirigez vers un fichier et faites! 

Merci pour l'aide. 

32
Chris

La sortie supplémentaire que vous obtenez à partir de csv.awk provient du code de démonstration. Il est prévu que vous utilisiez les fonctions du script pour analyser et que vous l'exécutiez comme vous le souhaitez. 

À la fin de csv.awk se trouve la boucle { ... } qui illustre l'une des fonctions. C'est ce code qui produit le -> 2|

Au lieu de cela, appelez simplement la fonction d'analyse et faites print csv[1], csv[2]

Cette partie du code ressemblerait alors à ceci:

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

Enregistrez-le sous le nom your_script (par exemple).

Faites chmod +x your_script.

Et cat est inutile. De plus, vous pouvez faire sort -u au lieu de sort | uniq.

Votre commande ressemblerait alors à:

./yourscript Buildings.csv | sort -u > floors.csv
9
Dennis Williamson
gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq

Il s’agit d’une superbe extension GNU Awk 4, dans laquelle vous définissez un modèle de champ au lieu d’un modèle de séparateur de champ. Fait des merveilles pour CSV. ( docs )

ETA (thanks mitchus): Pour supprimer les guillemets environnants, gsub("^\"|\"$","",$3); s'il y a plus de champs que $3 à traiter de cette façon, il suffit de les parcourir en boucle.
Notez que cette approche simple ne tolère pas les entrées mal formées, ni certains caractères spéciaux possibles entre guillemets - une telle couverture irait au-delà de la portée d’une simple ligne.

33
hemflit

Ma solution consiste à supprimer les virgules du csv à l'aide de:

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

C’est-à-dire qu’on substitue d’abord les guillemets ouvrants avec "((" et les guillemets fermants avec "))", puis remplace "((" quoi que ce soit, "))" par "tout ce que", puis modifie toutes les instances restantes de "((" et "))" retour à ".

6
Vitalik Buterin

Vous pouvez essayer ce paser csv basé sur awk:

http://lorance.freeshell.org/csv/

3
Marcus Whybrow

Vous pouvez utiliser un script que j'ai écrit appelé csvquote pour permettre à awk d'ignorer les virgules à l'intérieur des champs cités. La commande deviendrait alors:

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

et couper pourrait être un peu plus facile que awk pour ceci:

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

Vous pouvez trouver le code csvquote ici: https://github.com/dbro/csvquote

2
D Bro

Étant donné que le problème consiste réellement à distinguer une virgule dans un champ CSV de celle qui sépare les champs, nous pouvons remplacer le premier type de virgule par un autre élément afin de faciliter l'analyse ultérieure, c'est-à-dire:

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

Ce script gawk (replace-comma.awk) fait cela:

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

Cela utilise une fonctionnalité gawk qui capture le séparateur d’enregistrements dans une variable appelée RT. Il divise chaque caractère en un enregistrement et, lorsque nous lisons tous les enregistrements, nous remplaçons la virgule dans une citation (\x022) par <comma>.

La solution FPAT échoue dans un cas particulier où vous avez à la fois des guillemets et une virgule entre guillemets, mais cette solution fonctionne dans tous les cas, c'est-à-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

Comme une couche pour un copier-coller facile:

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