
Bei einer Datei mit Zeilenumbrüchen in Feldern (eingebettet in Anführungszeichen) habe ich versucht, NUL als Datensatztrennzeichen zu verwenden und dann die gewünschten Datensätze auszuwählen. Dazu habe ich die Zeilenenden durch NUL ersetzt und dann die durch einen Zeilenumbruch getrennten Felder korrigiert (durchgeführt mit sed
). Allerdings schlägt dann die exakte Übereinstimmung des ersten Felds in (GNU) awk
mit einem String fehl. Interessanterweise schlägt eine String-Musterübereinstimmung im ersten Feld fehl, was mich annehmen lässt, dass dies RS="\x00"
korrekt angewendet wird.
Warum sollte es fehlschlagen? Warum funktioniert der Mustervergleich?
Beispieldatei input.txt
:
head1,head2,head3
a,b,c
b,no a in first field,c
a,"with quotes",c
a,"with ,",c
b,a,1
a,"with
newline",c
b,1,a
Datensatzauswahl per awk
mit exaktem String vor der Eingabe von NUL funktioniert:
$awk 'BEGIN {FS=OFS=","} {if ($1=="a") print}' input.txt
Ergebnis:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
Die Einführung von NUL und die Korrektur von „Newline-Splits“ funktioniert (beachten Sie den "with\n newline"
Eintrag):
$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt | cat -A
head1,head2,head3^@$
a,b,c^@$
b,no a in first field,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
b,a,1^@$
a,"with$
newline",c^@$
b,1,a^@$
Die Verwendung einer Musterübereinstimmung für ein in Feld 1 funktioniert (beachten Sie, dass "a"
in anderen Feldern dies fehlschlägt, aber "head1"
übereinstimmt):
$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","}
{ if ($1~"a") print}' |
cat -A
head1,head2,head3^@$
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
newline",c^@
JEDOCH: die genaue Übereinstimmung für "a"
in Feld 1 schlägt fehl:
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}'
##<no output>##
Wo liege ich falsch? Warum funktioniert es, bevor NUL als verwendet wird RS
?
Antwort1
Ihr sed-Befehl ändert Zeilenumbrüche ( \n
) nicht in NULs ( \0
), sondern in NULs + Zeilenumbrüche ( \0\n
) (wie cat -A
gezeigt).
Wenn Sie GNU awk mit RS auf gesetzt verwenden \0
, ist das erste Zeichen eines nachfolgenden Datensatzes (und seines ersten Felds) \n
, wodurch Ihre exakte Übereinstimmung unterbrochen wird.
Und die 's/\(,"[^,"]*\)\x00/\1/'
Korrektur der Zeilenumbruchteilung ändert daran überhaupt nichts – sie hängt den newline",c
Datensatz lediglich an den vorherigen an.
Eine schnelle und einfache „Lösung“ besteht darin, RS
auf \0\n
statt nur zu setzen \0
. Aber diese Art, CSV-Dateien so zu bearbeiten, dass sie von awk analysiert werden können, ist nicht zuverlässig, also sollten Sie WIRKLICH etwas Besseres finden.
Zu Ihrem letzten Beispiel:
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS=ORS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}' | cat -A
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
newline",c^@$
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}'
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
newline",c
Antwort2
Ihre Datei enthält möglicherweise LFs in der Mitte mit CRLF-Zeilenenden, z. B. wenn sie aus MS-Excel exportiert wurde. In diesem Fall benötigen Sie mit gawk nur:
awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file
Zum Beispiel ( cat -v
nur verwenden, um die CRs als ^M
s sichtbar zu machen):
$ cat -v file
head1,head2,head3^M
a,b,c^M
b,no a in first field,c^M
a,"with quotes",c^M
a,"with ,",c^M
b,a,1^M
a,"with
newline",c^M
b,1,a^M
$ awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file | cat -v
a,b,c^M
a,"with quotes",c^M
a,"with ,",c^M
a,"with
newline",c^M
Wenn es einen Grund gibt, warum das oben genannte für Sie nicht funktioniert, dann sehen Siehttps://stackoverflow.com/questions/45420535/was ist der robusteste Weg, um CSV mithilfe von Awk effizient zu analysieren?oder laden Sie die CSV-Parser-Erweiterung von gawks in gawkextlib herunter/verwenden Sie sie.
Antwort3
gemischter Sed-AWK-Ansatz:
$ < file \
sed -e '
s/$/\x00/
s/\(,"[^,"]*\)\x00/\1/
H;1h;$!d;g
s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -
Kommentare: das gemischte sed+awk Ich habe Ihren Code genommen und ihn leicht angepasst, um die gewünschten Ergebnisse zu erzielen. Die Hauptidee besteht darin, die Zeilenumbrüche zu entfernen, die sed immer einfügt. So halten wir sed davon ab, nach der Verarbeitung jedes Datensatzes zu drucken. Dann entfernen wir am Ende die Zeilenumbrüche und übergeben diese durch NUL getrennten Daten an awk mit NUL als Datensatztrennzeichen. Dann suchen wir einfach nach Datensätzen, die mit einem beginnen,
Ausgabe:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
newline",c
Nachfolgend sind nur awk- und nur sed-Methoden aufgeführt. Sie basieren auf Anführungszeichen innerhalb eines in Anführungszeichen gesetzten Felds, die verdoppelt werden sollen.
reiner Sed-Ansatz:
$ sed -Ee ':a
/^(([^"]*"){2})*[^"]*$/!{
$d;N;ba
}
/^a,/!d
' file
reiner Awk-Ansatz
$ awk -F\" '
!(NF%2){
t = $0; n = NF
while (getline a > 0) {
t = t ORS a
n = n + split(a, _x, FS)
if (!(nf%2)) break
}
$0 = t
}/^a,/
' file