Frage

Wie analysieren Sie eine CSV-Datei gaffen mit? Einfach Einstellung FS="," nicht genug ist, als zitierte Feld mit einem Komma innerhalb als mehrere Felder behandelt werden.

Beispiel FS="," verwendet, die nicht funktionieren:

Dateiinhalt:

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

gaffen Skript:

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

schlechte Ausgabe:

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

gewünschte Ausgabe:

field #1: one
field #2: two
field #3: "three, four"
field #4: five
---------------------------
field #1: "six, seven"
field #2: eight
field #3: "nine"
---------------------------
War es hilfreich?

Lösung

Die kurze Antwort lautet: „Ich gafft nicht CSV verwenden würde zu analysieren, wenn die CSV peinliche Daten enthält“, wobei ‚ungeschickt‘ bedeutet, dass Dinge wie Kommata in den CSV-Felddaten.

Die nächste Frage ist: „Was andere Verarbeitung werden Sie tun werden“, denn das beeinflussen, welche Alternativen Sie verwenden.

Ich würde wahrscheinlich verwenden Perl und den Text :: CSV oder Text :: CSV_XS Module zu lesen und die Daten zu verarbeiten. Denken Sie daran, Perl ursprünglich geschrieben wurde teilweise als awk und sed Killer -. Damit die a2p und s2p Programme noch mit Perl verteilt, die awk und sed Skripte (jeweils) in Perl konvertieren

Andere Tipps

Die gawk Version 4 Handbuch sagt FPAT = "([^,]*)|(\"[^\"]+\")" verwenden

Wenn FPAT definiert ist, deaktiviert es FS und spezifiziert Felder durch Gehalt statt durch Trennzeichen.

Sie können einen einfachen Wrapper-Funktion csvquote genannt verwenden, um die Eingabe zu sanieren und wiederherzustellen, nachdem awk es getan verarbeitet. Rohr Ihre Daten durch sie am Anfang und Ende, und alles sollte funktionieren ok:

vor:

gawk -f mypgoram.awk input.csv

nach:

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

Siehe https://github.com/dbro/csvquote für Code und Dokumentation.

Wenn zulässig, würde ich den Python verwenden csv Modul unter besonderem die Aufmerksamkeit auf die Dialekt Parameter verwendet und die Formatierung erforderlich

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 ($0 ~ repl) {
    #   print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr"
    #}
    if ($0 ~ delim) {
        print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr"
        print "    replaced by " repl > "/dev/stderr"
    }
    gsub(delim, repl)

    $0 = gensub(/([^,])\"\"/, "\\1'", "g")
#   $0 = gensub(/\"\"([^,])/, "'\\1", "g")  # not needed above covers all cases

    out = ""
    #for (i = 1;  i <= length($0);  i++)
    n = length($0)
    for (i = 1;  i <= n;  i++)
        if ((ch = substr($0, 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

"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

test.bat

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

Ich bin nicht ganz sicher, ob dies der richtige Weg ist, die Dinge zu tun. Ich würde lieber arbeiten auf einer CSV-Datei, in denen entweder alle Werte notierten oder keine. Btw, ermöglicht awk Regexes Feldtrennern zu sein. Überprüfen Sie, ob das sinnvoll ist.

{
  ColumnCount = 0
  $0 = $0 ","                           # Assures all fields end with comma
  while($0)                             # Get fields by pattern, not by delimiter
  {
    match($0, / *"[^"]*" *,|[^,]*,/)    # Find a field with its delimiter suffix
    Field = substr($0, 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
    $0 = substr($0, RLENGTH + 1)        # Remove processed text from the raw data
  }
}

Muster, der dieses folgen können die Felder in der Spalte [] zugreifen. Spaltenanzahl gibt die Anzahl der Elemente in der Spalte [], die gefunden wurden. Wenn nicht alle Zeilen die gleiche Anzahl von Spalten enthalten, Spalte [] enthält zusätzliche Daten nach Spalte [Spaltenanzahl], wenn die Verarbeitung der kürzeren Reihen.

Diese Implementierung ist langsam, aber es scheint, die FPAT / patsplit() Funktion in gawk> = 4.0.0 erwähnte in einer früheren Antwort gefunden zu emulieren.

Referenz

Hier ist, was ich kam mit. Alle Kommentare und / oder bessere Lösungen würden geschätzt.

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

Die Grundidee ist, dass ich Schleife durch die Felder, und alle Felder, die mit einem Zitat beginnt aber mit einem Zitat wird nicht in das nächste Feld angehängt beenden.

Perl hat den Text :: CSV_XS Modul, das speziell gebaut ist der zitierte-Komma Seltsamkeit zu handhaben.
Alternativ versuchen, den Text :: CSV-Modul.

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

erzeugt diese Ausgabe:

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

Hier ist eine menschenlesbare Version.
Speichern Sie es als ParseCSV, chmod + x, und führen Sie es als "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";
    }
}

Sie müssen möglicherweise auf eine andere Version von Perl auf Ihrem Rechner zeigen, da der Text :: CSV_XS Modul kann nicht auf Ihrem Standard-Version von Perl installiert werden.

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.

Wenn keine Ihrer Perl-Versionen Text haben :: CSV_XS installiert haben, müssen Sie:
sudo apt-get install cpanminus
sudo cpanm Text::CSV_XS

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top