Domanda

Come analizzi un file CSV usando gawk? La semplice impostazione di FS = ", " non è sufficiente, poiché un campo tra virgolette con una virgola all'interno verrà trattato come più campi.

Esempio usando FS = ", " che non funziona:

contenuto del file:

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

gawk script:

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

output errato:

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

output desiderato:

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

Soluzione

La risposta breve è "Non userei gawk per analizzare CSV se il CSV contiene dati scomodi", dove "scomodo" significa cose come virgole nei dati del campo CSV.

La domanda successiva è " Quale altra elaborazione stai per fare " ;, poiché ciò influenzerà le alternative che usi.

Probabilmente userò i moduli Perl e Text :: CSV o Text :: CSV_XS per leggere ed elaborare i dati. Ricorda, Perl è stato originariamente scritto in parte come un killer awk e sed - quindi i programmi a2p e s2p sono ancora distribuito con Perl che converte gli script awk e sed (rispettivamente) in Perl.

Altri suggerimenti

Il manuale gawk versione 4 dice di usare FPAT = " ([^,] *) | (\ " [^ \ "] + \ ") "

Quando viene definito FPAT , disabilita FS e specifica i campi in base al contenuto anziché al separatore.

È possibile utilizzare una semplice funzione wrapper chiamata csvquote per disinfettare l'input e ripristinarlo dopo che awk ha terminato l'elaborazione. Inserisci i tuoi dati all'inizio e alla fine e tutto dovrebbe funzionare bene:

prima

gawk -f mypgoram.awk input.csv

dopo:

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

Vedi https://github.com/dbro/csvquote per codice e documentazione.

Se consentito, utilizzerei il modulo Python csv , pagando uno speciale attenzione al usato e ai parametri di formattazione richiesti , per analizzare il file CSV che hai.

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

<*>

Non sono esattamente sicuro che questo sia il modo giusto di fare le cose. Preferirei lavorare su un file CSV in cui tutti i valori devono essere citati o nessuno. A proposito, awk consente alle regex di essere separatori di campo. Controlla se è 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
  }
}

I pattern che seguono questo possono accedere ai campi nella Colonna []. ColumnCount indica il numero di elementi nella colonna [] trovati. Se non tutte le righe contengono lo stesso numero di colonne, la colonna [] contiene dati aggiuntivi dopo la colonna [ColumnCount] durante l'elaborazione delle righe più brevi.

Questa implementazione è lenta, ma sembra emulare la funzione FPAT / patsplit () che si trova in gawk > = 4.0.0 menzionato in una risposta precedente.

Riferimento

Ecco cosa mi è venuto in mente. Eventuali commenti e / o soluzioni migliori sarebbero apprezzati.

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'idea di base è che eseguo il ciclo tra i campi e ogni campo che inizia con una citazione ma non termina con una citazione viene aggiunto al campo successivo.

Perl ha il modulo Text :: CSV_XS che è costruito appositamente per gestire la stranezza tra virgolette.
In alternativa, prova il modulo Text :: CSV.

perl -MText :: CSV_XS -ne 'INIZIA {$ csv = Text :: CSV_XS- > new ()} if ($ csv- > parse ($ _)) {@ f = $ csv - > fields (); per $ n (0 .. $ # f) {stampa " campo # $ n: $ f [$ n] \ n "}; stampa " --- \ n "} 'file .csv

Produce questo output:

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

Ecco una versione leggibile dall'uomo.
Salvalo come parsecsv, chmod + x ed eseguilo come " 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";
    }
}

Potrebbe essere necessario puntare a una versione diversa di perl sul computer in uso, poiché il modulo Text :: CSV_XS potrebbe non essere installato sulla versione predefinita di 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.

Se in nessuna delle tue versioni di Perl è installato Text :: CSV_XS, dovrai:
sudo apt-get install cpanminus
sudo cpanm Text :: CSV_XS

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top