it-swarm.com.de

Wie kann ich doppelte Zeilen in einer Datei in Unix löschen?

Gibt es eine Möglichkeit, doppelte Zeilen in einer Datei in Unix zu löschen?

Ich schaffe das mit sort -u und uniq Befehle, aber ich möchte sed oder awk verwenden. Ist das möglich?

114
Vijay
awk '!seen[$0]++' file.txt

seen ist ein assoziatives Array, an das Awk jede Zeile der Datei übergibt. Wenn eine Zeile nicht im Array enthalten ist, wird seen[$0] wird mit false bewertet. Das ! ist ein logischer NOT-Operator und invertiert den Wert false in true. Awk gibt die Zeilen aus, in denen der Ausdruck true ergibt. Das ++ erhöht seen, so dass seen[$0] == 1 nach dem ersten Mal wird eine Zeile gefunden und dann seen[$0] == 2, und so weiter.
Awk bewertet alles außer 0 und "" (leere Zeichenfolge) auf true. Wenn eine doppelte Zeile in seen steht, dann !seen[$0] wird mit false ausgewertet und die Zeile wird nicht in die Ausgabe geschrieben.

251
Jonas Elfström

Von http://sed.sourceforge.net/sed1line.txt : (Bitte frag mich nicht, wie das funktioniert ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
28
Andre Miller

Perl-Einzeiler ähnlich der awk-Lösung von @ jonas:

Perl -ne 'print if ! $x{$_}++' file

Diese Variante entfernt nachfolgende Leerzeichen vor dem Vergleich:

Perl -lne 's/\s*$//; print if ! $x{$_}++' file

Diese Variante bearbeitet die Datei direkt:

Perl -i -ne 'print if ! $x{$_}++' file

Diese Variante bearbeitet die Datei direkt und erstellt eine Sicherungskopie file.bak

Perl -i.bak -ne 'print if ! $x{$_}++' file
12
Chris Koknat

Der Einzeiler, den Andre Miller oben gepostet hat, funktioniert mit Ausnahme der neuesten Versionen von sed, wenn die Eingabedatei mit einer Leerzeile und keinen Zeichen endet. Auf meinem Mac dreht sich meine CPU nur.

Endlosschleife, wenn die letzte Zeile leer ist und keine Zeichen enthält :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Hängt nicht, aber Sie verlieren die letzte Zeile

sed '$d;N; /^\(.*\)\n\1$/!P; D'

Die Erklärung befindet sich ganz am Ende der sed FAQ :

Der GNU sed Betreuer meinte das trotz der Portabilitätsprobleme
Dies würde dazu führen, dass der Befehl N nicht gedruckt wird, sondern gedruckt wird
löschen) Der Musterraum entsprach mehr den eigenen Intuitionen
darüber, wie sich ein Befehl zum "Anhängen der nächsten Zeile" soll verhalten soll.
Eine weitere Tatsache, die die Änderung begünstigte, war die, dass "{N; command;}"
löscht die letzte Zeile, wenn die Datei eine ungerade Anzahl von Zeilen hat, aber
Gibt die letzte Zeile aus, wenn die Datei eine gerade Anzahl von Zeilen enthält.

Zum Konvertieren von Skripten, die das frühere Verhalten von N verwendeten (Löschen
den Musterraum bei Erreichen der EOF) zu Skripten kompatibel mit
alle versionen von sed, ändern ein einsames "N;" zu "$ d; N;" .

7
Bradley Kreider

Ein alternativer Weg mit Vim (Vi kompatibel):

Löschen Sie doppelte, aufeinanderfolgende Zeilen aus einer Datei:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Löschen Sie doppelte, nicht aufeinanderfolgende und nicht leere Zeilen aus einer Datei:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

4
Bohr

Die erste Lösung stammt ebenfalls von http://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

die Kernidee ist:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Erklärt:

  1. $!N;: Wenn die aktuelle Zeile NICHT die letzte Zeile ist, verwenden Sie den Befehl N, um die nächste Zeile in pattern space einzulesen.
  2. /^(.*)\n\1$/!P: Wenn der Inhalt des aktuellen pattern space zwei durch duplicate string getrennte \n enthält, was bedeutet, dass die nächste Zeile die same mit der aktuellen Zeile ist, können wir sie NICHT gemäß unserer Kernidee drucken. Andernfalls, was bedeutet, dass die aktuelle Zeile das LETZTE Erscheinungsbild aller doppelten aufeinanderfolgenden Zeilen ist, können wir jetzt den Befehl P verwenden, um die Zeichen im aktuellen pattern space mit \n zu drucken (\n wird auch gedruckt).
  3. D: Wir verwenden den Befehl D, um die Zeichen im aktuellen pattern space mithilfe von \n (\n ebenfalls gelöscht) zu löschen. Dann ist der Inhalt von pattern space die nächste Zeile.
  4. und der Befehl D zwingt sed, zu seinem Befehl FIRST$!N zu springen, aber NICHT die nächste Zeile aus der Datei oder dem Standardeingabestream zu lesen.

Die zweite Lösung ist leicht zu verstehen (von mir):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

die Kernidee ist:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Erklärt:

  1. lesen Sie eine neue Zeile aus dem Eingabestream oder der Datei und drucken Sie sie einmal aus.
  2. verwenden Sie den Befehl :loop, um einen label mit dem Namen loop festzulegen.
  3. verwenden Sie N, um die nächste Zeile in den pattern space einzulesen.
  4. verwenden Sie s/^(.*)\n\1$/\1/, um die aktuelle Zeile zu löschen. Wenn die nächste Zeile mit der aktuellen Zeile identisch ist, verwenden Sie den Befehl s, um die Aktion delete auszuführen.
  5. wenn der Befehl s erfolgreich ausgeführt wird, verwenden Sie den Befehl tloop force sed, um zu dem label mit dem Namen loop zu springen In den nächsten Zeilen gibt es keine doppelten aufeinanderfolgenden Zeilen der Zeile latest printed; Andernfalls verwenden Sie den Befehl D, um delete in der Zeile anzuzeigen, die mit latest-printed line identisch ist, und zwingen Sie sed, zum ersten Befehl zu springen, der der Inhalt von p ist current pattern space ist die nächste neue Zeile.
3
Weike