Question

Comment analyser un fichier CSV avec gawk? Il ne suffit pas de définir FS = "," , un champ entre guillemets contenant une virgule sera traité comme un champ multiple.

Exemple d'utilisation de FS = "," qui ne fonctionne pas:

contenu du fichier

:

one,two,"three, four",five
"six, seven",eight,"nine"

script gawk:

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) printf "field #%d: %s\n", i, $(i)
  printf "---------------------------\n"
}

mauvaise sortie:

field #1: one
field #2: two
field #3: "three
field #4:  four"
field #5: five
---------------------------
field #1: "six
field #2:  seven"
field #3: eight
field #4: "nine"
---------------------------

sortie souhaitée:

field #1: one
field #2: two
field #3: "three, four"
field #4: five
---------------------------
field #1: "six, seven"
field #2: eight
field #3: "nine"
---------------------------
Était-ce utile?

La solution

La réponse courte est "Je n’utiliserais pas gawk pour analyser CSV si le CSV contient des données difficiles", où "maladroit" signifie, par exemple, des virgules dans les données de champ CSV.

La question suivante est "Quel autre traitement allez-vous effectuer", car cela influera sur les alternatives que vous utilisez.

J'utiliserais probablement Perl et les modules Text :: CSV ou Text :: CSV_XS pour lire et traiter les données. Rappelez-vous, Perl était à l'origine écrit en partie comme un tueur awk et sed - d'où les programmes a2p et s2p distribué avec Perl qui convertit les scripts awk et sed (respectivement) en Perl.

Autres conseils

Le manuel de gawk version 4 dit d'utiliser FPAT = "([^,] *) | (" "[^ \"] + + "") "

Lorsque FPAT est défini, il désactive FS et spécifie les champs par contenu plutôt que par séparateur.

Vous pouvez utiliser une fonction d'encapsuleur simple appelée csvquote pour nettoyer l'entrée et la restaurer après le traitement de awk. Transférez vos données au début et à la fin, et tout devrait bien se passer:

avant:

gawk -f mypgoram.awk input.csv

après:

csvquote input.csv | gawk -f mypgoram.awk | csvquote -u

Voir https://github.com/dbro/csvquote pour le code et la documentation.

Si cela est possible, j'utiliserais le module csv de Python, en effectuant un paiement spécial. attention au dialecte utilisé et paramètres de formatage requis , pour analyser le fichier CSV que vous avez.

csv2delim.awk

# csv2delim.awk converts comma delimited files with optional quotes to delim separated file
#     delim can be any character, defaults to tab
# assumes no repl characters in text, any delim in line converts to repl
#     repl can be any character, defaults to ~
# changes two consecutive quotes within quotes to '

# usage: gawk -f csv2delim.awk [-v delim=d] [-v repl=`"] input-file > output-file
#       -v delim    delimiter, defaults to tab
#       -v repl     replacement char, defaults to ~

# e.g. gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > test.txt

# abe 2-28-7
# abe 8-8-8 1.0 fixed empty fields, added replacement option
# abe 8-27-8 1.1 used split
# abe 8-27-8 1.2 inline rpl and "" = '
# abe 8-27-8 1.3 revert to 1.0 as it is much faster, split most of the time
# abe 8-29-8 1.4 better message if delim present

BEGIN {
    if (delim == "") delim = "\t"
    if (repl == "") repl = "~"
    print "csv2delim.awk v.m 1.4 run at " strftime() > "/dev/stderr" ###########################################
}

{
    #if (
"first","second","third"
"fir,st","second","third"
"first","sec""ond","third"
" first ",sec   ond,"third"
"first" , "second","th  ird"
"first","sec;ond","third"
"first","second","th;ird"
1,2,3
,2,3
1,2,
,2,
1,,2
1,"2",3
"1",2,"3"
"1",,"3"
1,"",3
"","",""
"","""aiyn","oh"""
"""","""",""""
11,2~2,3
~ repl) { # print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr" #} if (
rem test csv2delim
rem default is: -v delim={tab} -v repl=~
gawk                      -f csv2delim.awk test.csv > test.txt
gawk -v delim=;           -f csv2delim.awk test.csv > testd.txt
gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > testdr.txt
gawk            -v repl=` -f csv2delim.awk test.csv > testr.txt
~ delim) { print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr" print " replaced by " repl > "/dev/stderr" } gsub(delim, repl) <*> = gensub(/([^,])\"\"/, "\\1'", "g") # <*> = gensub(/\"\"([^,])/, "'\\1", "g") # not needed above covers all cases out = "" #for (i = 1; i <= length(<*>); i++) n = length(<*>) for (i = 1; i <= n; i++) if ((ch = substr(<*>, i, 1)) == "\"") inString = (inString) ? 0 : 1 # toggle inString else out = out ((ch == "," && ! inString) ? delim : ch) print out } END { print NR " records processed from " FILENAME " at " strftime() > "/dev/stderr" }

test.csv

<*>

test.bat

<*>

Je ne sais pas si c'est la bonne façon de faire les choses. Je préférerais travailler sur un fichier csv dans lequel toutes les valeurs sont à citer ou aucune. Btw, awk permet aux expressions rationnelles d'être des séparateurs de champs. Vérifiez si cela est utile.

{
  ColumnCount = 0
  <*> = <*> ","                           # Assures all fields end with comma
  while(<*>)                             # Get fields by pattern, not by delimiter
  {
    match(<*>, / *"[^"]*" *,|[^,]*,/)    # Find a field with its delimiter suffix
    Field = substr(<*>, RSTART, RLENGTH) # Get the located field with its delimiter
    gsub(/^ *"?|"? *,$/, "", Field)     # Strip delimiter text: comma/space/quote
    Column[++ColumnCount] = Field       # Save field without delimiter in an array
    <*> = substr(<*>, RLENGTH + 1)        # Remove processed text from the raw data
  }
}

Les modèles qui suivent celui-ci peuvent accéder aux champs de la colonne []. ColumnCount indique le nombre d'éléments trouvés dans Column []. Si toutes les lignes ne contiennent pas le même nombre de colonnes, Column [] contient des données supplémentaires après Column [ColumnCount] lors du traitement des lignes les plus courtes.

Cette implémentation est lente, mais elle semble émuler la fonctionnalité FPAT / patsplit () trouvée dans gawk > = 4.0.0 mentionné dans une réponse précédente.

Référence

Voici ce que j'ai proposé. Tous les commentaires et / ou les meilleures solutions seraient les bienvenus.

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) {
    f[++n] = $i
    if (substr(f[n],1,1)=="\"") {
      while (substr(f[n], length(f[n]))!="\"" || substr(f[n], length(f[n])-1, 1)=="\\") {
        f[n] = sprintf("%s,%s", f[n], $(++i))
      }
    }
  }
  for (i=1; i<=n; i++) printf "field #%d: %s\n", i, f[i]
  print "----------------------------------\n"
}

L'idée de base est que je parcourt les champs. Tout champ commençant par une citation mais ne se terminant pas par une citation se voit attribuer le champ suivant qui y est ajouté.

Perl a le module Text :: CSV_XS spécialement conçu pour gérer l'étrangeté entre virgules et guillemets.
Sinon, essayez le module Text :: CSV.

perl -MText :: CSV_XS -ne 'BEGIN {$ csv = Texte :: CSV_XS- & new ()} if ($ csv- > parse ($ _)) {@ f = $ csv - "gt; fields (); for $ n (0 .. $ # f) {print" champ # $ n: $ f [$ n] \ n "quot;}; fichier" --- \ n "quot}} .csv

Produit cette sortie:

field #0: one
field #1: two
field #2: three, four
field #3: five
---
field #0: six, seven
field #1: eight
field #2: nine
---

Voici une version lisible par l'homme.
Enregistrez-le en tant que parsecsv, chmod + x et exécutez-le en tant que "parsecsv file.csv"

.
#!/usr/bin/perl
use warnings;
use strict;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new();
open(my $data, '<', $ARGV[0]) or die "Could not open '$ARGV[0]' $!\n";
while (my $line = <$data>) {
    if ($csv->parse($line)) {
        my @f = $csv->fields();
        for my $n (0..$#f) {
            print "field #$n: $f[$n]\n";
        }
        print "---\n";
    }
}

Vous devrez peut-être indiquer une version différente de perl sur votre ordinateur, car le module Text :: CSV_XS n'est peut-être pas installé sur votre version par défaut de perl.

Can't locate Text/CSV_XS.pm in @INC (@INC contains: /home/gnu/lib/perl5/5.6.1/i686-linux /home/gnu/lib/perl5/5.6.1 /home/gnu/lib/perl5/site_perl/5.6.1/i686-linux /home/gnu/lib/perl5/site_perl/5.6.1 /home/gnu/lib/perl5/site_perl .).
BEGIN failed--compilation aborted.

Si Text :: CSV_XS n'est installé dans aucune de vos versions de Perl, vous devez:
sudo apt-get install cpanminus
sudo cpanm Text :: CSV_XS

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top