Parsen einer CSV-Datei gaffen mit
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"
---------------------------
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 , die CSV-Datei, die Sie haben zu analysieren.
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.
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