Wie manipuliert man eine CSV-Datei mit sed oder awk?

23

Wie kann ich mit sedoder eine CSV-Datei bearbeiten awk?

  • Löschen Sie eine Spalte
  • Duplizieren Sie eine Spalte
  • Eine Spalte verschieben

Ich habe einen großen Tisch mit über 200 Zeilen und bin damit nicht so vertraut sed.

Binoy Babu
quelle
1
Cross posted on AskUbuntu
enzotib 16.12.11
@enzotib kannst du den Link posten?
Nr.
@MaxMackie askubuntu.com/questions/88142/… . Ich kann dort in dieser Stunde keinen Mod finden, also habe ich ihn markiert und sie gebeten, zu migrieren, wenn sie wollen. Es hat bereits eine akzeptierte Antwort, daher bin ich mir nicht sicher, ob sie es werden
Michael Mrozek
@MichaelMrozek, hmmm was passiert normalerweise in diesen Situationen? Bewahren wir die Duplikate einfach auf?
Nr.
1
Sofern Sie nicht auf einem System ausgeführt werden müssen, auf dem nur grundlegende Tools verfügbar sind, lesen Sie den Abschnitt Gibt es ein robustes Befehlszeilentool für die Verarbeitung von CSV-Dateien ?.
Gilles 'SO- hör auf böse zu sein'

Antworten:

7

Abgesehen davon, wie Sie die Felder ausschneiden und neu anordnen (siehe die anderen Antworten), gibt es auch das Problem der skurrilen CSV-Felder.

Wenn Sie Ihre Daten in dieser „quirky“ Kategorie fällt, ein bisschen vor und Post - Filterung kann sich darum kümmern. Die Filter unten erfordern die Zeichen \x01, \x02, \x03, \x04nicht überall in Ihren Daten erscheinen.

Hier sind die Filter, die um einen einfachen Feldspeicherauszug gewickelt sind awk.

Hinweis: Feld 5 hat ein ungültiges / unvollständiges Layout für Felder in Anführungszeichen, ist jedoch am Ende einer Zeile harmlos (abhängig vom CSV-Parser). Aber natürlich wäre es verursacht problematisch unexpedted Ergebnisse , wenn sie von ihrem aktuellen werden sollten getauscht weg End-of-Row - Position.

Aktualisieren; user121196 hat auf einen Fehler hingewiesen, wenn vor einem nachgestellten Anführungszeichen ein Komma steht. Hier ist die Lösung.

Die Daten

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

Der Code

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

Die Ausgabe:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Hier ist der Vorfilter , erweitert mit Kommentaren.
Der Nachfilter ist nur eine Umkehrung von \x01. \x02, \x03,\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
Peter.O
quelle
Wie würden Sie die n-te Spalte basierend auf diesem Filter löschen?
user121196
@ user121196 - Wie im ersten Satz erwähnt, zeigt diese Antwort eine Möglichkeit, die CSV-Daten konsistenter zu machen. indem Sie ein in Anführungszeichen eingebettetes Komma vorübergehend durch ein neutrales Token-Zeichen ersetzen ... und es nach dem Verschieben / Ausschneiden / Löschen wieder in ein Komma umwandeln. Wie bereits erwähnt, wird der Schritt Verschieben / Ausschneiden / Löschen durch einen einfachen awk-Field-Dump ersetzt .
Peter.O
1
In diesem Fall schlägt dies fehl: "15111 N. Hayden Rd., Ste 160,", ""
user121196
@ user121196: Danke für den Hinweis. Ich habe die Antwort mit einem Update aktualisiert.
Peter.O
15

Dies hängt davon ab, ob Ihre CSV-Datei Kommas nur für Trennzeichen verwendet oder ob Sie den Wahnsinn haben:

Feld eins, "Feld zwei", Feld drei

Dies setzt voraus, dass Sie eine einfache CSV-Datei verwenden:

Eine Spalte entfernen

Sie können eine einzelne Spalte auf viele Arten loswerden. Ich habe als Beispiel Spalte 2 verwendet. Am einfachsten ist wahrscheinlich die Verwendung cut, mit der Sie ein Trennzeichen angeben -dund welche Felder Sie drucken möchten -f. Dies teilt es in Kommas und Ausgabefeld 1 und die Felder 3 bis zum Ende auf:

$ cut -d, -f1,3- /path/to/your/file

Wenn Sie tatsächlich verwenden müssen sed, können Sie einen regulären Ausdruck schreiben, der mit den ersten n-1Feldern, dem nth-Feld und dem Rest übereinstimmt , und die Ausgabe des nth -Felds überspringen (hier nist 2, damit die erste Gruppe nach 1Zeit abgeglichen wird:) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Dafür gibt es eine Reihe von Möglichkeiten awk, von denen keine besonders elegant ist. Sie können eine forSchleife verwenden, aber mit dem nachgestellten Komma umzugehen ist ein Schmerz; ignorieren, dass es so etwas wie wäre:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Ich finde es einfacher, Feld 1 auszugeben und dann substralles nach Feld 2 abzurufen:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Dies ist jedoch für weiter entfernte Kolumnen ärgerlich

Eine Spalte duplizieren

In seddieser ist im Wesentlichen der gleiche Ausdruck wie zuvor, aber Sie auch die Zielspalt erfassen und umfassen die Gruppe mehrfach in dem Ersatz:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

In awkder for-Schleife wäre es so etwas wie (wieder ohne das nachstehende Komma):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

Der substrWeg:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl hat in seiner Antwort eine bessere Methode gefunden )

Eine Spalte verschieben

Ich denke, die sedLösung folgt natürlich aus den anderen, aber es wird langsam lächerlich lang

Michael Mrozek
quelle
Das ist eine geladene Antwort! +1 :)
Jaypal Singh
Lächerlich lang? Pah !
Gilles 'SO- hör auf böse zu sein'
12

awkist Ihre beste Wette. awkdruckt Felder nach Nummer, also ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

So entfernen Sie eine Spalte, ohne sie zu drucken:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

So ändern Sie die Reihenfolge:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Umleiten in eine Ausgabedatei.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk kann auch die Ausgabe formatieren.

Ausgabe im Awk-Format

Panther
quelle
Da es sich um CSV handelt, benötigen Sie auch BEGIN { FS=","; OFS=","; }.
1
Ich denke sogar FS = OFS = "," wird funktionieren.
5

Gegeben sei eine durch Leerzeichen getrennte Datei im folgenden Format:

1 2 3 4 5

Sie können Feld 2 mit awk wie folgt entfernen:

awk '{ sub($2,""); print}' file

was zurückkehrt

1  3 4 5

Ersetzen Sie Spalte 2 gegebenenfalls durch Spalte n.

So duplizieren Sie Spalte 2:

awk '{ col = $2 " " $2; $2 = col; print }' file

was zurückkehrt

1 2 2 3 4 5

So wechseln Sie zwischen Spalte 2 und 3:

awk '{temp = $2; $2 = $3; $3 = temp; print}'

was zurückkehrt

1 3 2 4 5

awk ist generell sehr gut im Umgang mit dem Feldbegriff . Wenn Sie mit einer CSV-Datei und nicht mit einer durch Leerzeichen getrennten Datei arbeiten, können Sie sie einfach verwenden

awk -F,

Definieren Sie Ihr Feld als Komma anstelle eines Leerzeichens (dies ist die Standardeinstellung). Es gibt eine Reihe guter awk-Ressourcen online, von denen ich eine unten als Quelle aufführe.

Quelle für # 3

tcdyl
quelle
Ich weiß nicht viel darüber awk, aber es scheint eine durch Leerzeichen getrennte Ausgabe zu geben, auch wenn das Feldtrennzeichen ,(das Feldtrennzeichen steuert nur, wie es die Eingabe behandelt)
Michael Mrozek
@MichaelMrozek: Ja, es ist die OFS awk-Variable, die das Ausgabefeldtrennzeichen steuert.
Enzotib
Ja, und wie ich in meiner Antwort erwähne, können Sie die Option -F an awk übergeben, um das Trennzeichen (z. B. -F,)
tcdyl
0

Dies funktioniert zum Löschen

awk '{$2="";$0=$0;$1=$1}1'

Eingang

a b c d

Ausgabe

a c d
Steven Penny
quelle