Gibt es eine Möglichkeit, nach Spalten "uniq" zu machen?


195

Ich habe eine CSV-Datei wie diese:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Ich muss doppelte E-Mails (die gesamte Zeile) aus der Datei entfernen (dh eine der Zeilen overflow@example.comim obigen Beispiel). Wie verwende ich uniqnur Feld 1 (durch Kommas getrennt)? Laut man, uniqkeine Optionen für die Spalten.

Ich habe etwas mit versucht, sort | uniqaber es funktioniert nicht.

Antworten:


325
sort -u -t, -k1,1 file
  • -u für einzigartig
  • -t, Komma ist also das Trennzeichen
  • -k1,1 für das Schlüsselfeld 1

Testergebnis:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
Dies funktioniert nicht, wenn die Spalte Komma selbst enthält (mit Anführungszeichen)
user775187

13
warum brauchst du die, 1 in -k1,1? warum nicht einfach -k1?
hallo_there_andy

18
@hello_there_andy: Dies wird im Handbuch ( man sort) erklärt. Es steht für die Start- und Stoppposition.
Serrano

3
@CarlSmotricz: Ich habe es getestet und bestätigt , was sort‚s Manpage sagt:‚ mit , die Prüfung für strenge Ordnung, ohne , Ausgang nur die erste einer gleichen Lauf .‘ Es ist also in der Tat "das erste Auftreten des Duplikats vor dem Sortieren". -u--unique-c-c
Geremia

2
das ändert auch die Reihenfolge der Zeilen, nicht wahr?
Rkachach

102
awk -F"," '!_[$1]++' file
  • -F Legt das Feldtrennzeichen fest.
  • $1 ist das erste Feld.
  • _[val]sucht valim Hash _(eine reguläre Variable).
  • ++ Inkrementieren und alten Wert zurückgeben.
  • ! gibt logisch nicht zurück.
  • Am Ende befindet sich ein impliziter Druck.

4
Dieser Ansatz ist zweimal schneller als sort
Bitek

9
Dies hat auch den zusätzlichen Vorteil, dass die Linien in der ursprünglichen Reihenfolge bleiben!
AffluentOwl

8
Wenn Sie die letzte Uniq anstelle der ersten benötigen, hilft Ihnen dieses awk-Skript:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima

3
@eshwar füge einfach weitere Felder zum Wörterbuchindex hinzu! Zum Beispiel !_[$1][$2]++kann durch die ersten beiden Felder zu sortieren verwendet werden. Meine awk-fu ist jedoch nicht stark genug, um auf einer Reihe von Feldern einzigartig zu sein. :(
Soham Chowdhury

1
Brillant! Diese Option ist besser als die Antwort, weil sie die Reihenfolge der Zeilen
beibehält

16

Mehrere Spalten berücksichtigen.

Sortieren und geben Sie eine eindeutige Liste basierend auf Spalte 1 und Spalte 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : Doppelpunkt ist Trennzeichen
  • -k 1,1 -k 3,3 basierend auf Spalte 1 und Spalte 3

8

oder wenn du uniq verwenden willst:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

gibt:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Ich möchte auf eine mögliche Vereinfachung hinweisen: Sie können die cat! Anstatt in tr zu leiten, lassen Sie tr einfach die Datei mit lesen <. Das Durchleiten catist eine häufige unnötige Komplikation, die von Anfängern verwendet wird. Bei großen Datenmengen ist ein Leistungseffekt zu verzeichnen.
Carl Smotricz

4
Gut zu wissen. Vielen Dank! (Natürlich macht dies Sinn, wenn man an "Katze" und "Faulheit" denkt;))
Carsten C.

Das Umkehren von Feldern kann mit vereinfacht werden rev.
Hielke Walinga

5

Wenn Sie das letzte Duplikat behalten möchten, das Sie verwenden können

 tac a.csv | sort -u -t, -r -k1,1 |tac

Welches war meine Anforderung

Hier

tac kehrt die Datei Zeile für Zeile um


1

Hier ist ein sehr geschickter Weg.

Formatieren Sie zuerst den Inhalt so, dass die Spalte, deren Eindeutigkeit verglichen werden soll, eine feste Breite hat. Eine Möglichkeit hierfür ist die Verwendung von awk printf mit einem Feld- / Spaltenbreitenspezifizierer ("% 15s").

Jetzt können die Optionen -f und -w von uniq verwendet werden, um vorhergehende Felder / Spalten zu überspringen und die Vergleichsbreite (Spaltenbreite) anzugeben.

Hier sind drei Beispiele.

Im ersten Beispiel ...

1) Stellen Sie die interessierende Spalte vorübergehend auf eine feste Breite ein, die größer oder gleich der maximalen Breite des Feldes ist.

2) Verwenden Sie die Option -f uniq, um die vorherigen Spalten zu überspringen, und verwenden Sie die Option -w uniq, um die Breite auf tmp_fixed_width zu beschränken.

3) Entfernen Sie nachgestellte Leerzeichen aus der Spalte, um die Breite wiederherzustellen (vorausgesetzt, es gab zuvor keine nachgestellten Leerzeichen).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

Im zweiten Beispiel ...

Erstellen einer neuen Uniq-Spalte 1. Entfernen Sie diese, nachdem der Uniq-Filter angewendet wurde.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Das dritte Beispiel ist das gleiche wie das zweite, jedoch für mehrere Spalten.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

Nun, einfacher als das Isolieren der Spalte mit awk. Wenn Sie alles mit einem bestimmten Wert für eine bestimmte Datei entfernen müssen, warum nicht einfach grep -v:

zB um alles mit dem Wert "col2" in der zweiten Zeile zu löschen: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Wenn dies nicht gut genug ist, weil einige Zeilen möglicherweise nicht ordnungsgemäß entfernt werden, weil möglicherweise der übereinstimmende Wert in einer anderen Spalte angezeigt wird, können Sie Folgendes tun:

awk, um die beleidigende Spalte zu isolieren: z

awk -F, '{print $2 "|" $line}'

Das -F setzt das durch "," getrennte Feld, $ 2 bedeutet Spalte 2, gefolgt von einem benutzerdefinierten Trennzeichen und dann der gesamten Zeile. Sie können dann filtern, indem Sie Zeilen entfernen, die mit dem fehlerhaften Wert beginnen:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

und dann das Zeug vor dem Trennzeichen ausziehen:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(Beachten Sie, dass der Befehl sed schlampig ist, da er keine Escape-Werte enthält. Außerdem sollte das sed-Muster wirklich so etwas wie "[^ |] +" sein (dh alles, was nicht das Trennzeichen ist). Aber hoffentlich ist dies klar genug.


3
Er möchte keine Zeilen löschen, sondern eine einzelne Kopie einer Zeile mit einer bestimmten Zeichenfolge behalten. Uniq ist der richtige Anwendungsfall.
Ingyhere

-3

Wenn Sie die Datei sortzuerst mit sortieren , können Sie sie dann anwenden uniq.

Es scheint die Datei ganz gut zu sortieren:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Sie können auch AWK-Magie anwenden:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Dies ist nicht nach Spalten eindeutig , wie in der Frage gefordert. Dies ist nur für die gesamte Linie einzigartig. Außerdem müssen Sie keine Sortierung durchführen, um eine Uniq zu erstellen. Die beiden schließen sich gegenseitig aus.
Javid Jamae

1
Ja, du hast recht. Das letzte Beispiel macht das, worum es in der Frage geht, obwohl die akzeptierte Antwort viel sauberer ist. In Bezug auf sort, dann uniq, sortmuss getan werden , bevor Sie uniqes sonst nicht funktioniert (aber Sie können den zweiten Befehl und nur Gebrauch überspringen sort -u). Von uniq(1): "Filtern Sie benachbarte übereinstimmende Zeilen von INPUT (oder Standardeingabe) und schreiben Sie in OUTPUT (oder Standardausgabe)."
Mikael S

Ah, Sie haben Recht mit dem Sortieren vor Uniq. Ich habe nie bemerkt, dass uniq nur auf benachbarten Linien funktioniert. Ich denke, ich benutze immer nur sort -u.
Javid Jamae
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.