seenist ein assoziatives Array, an das Awk jede Zeile der Datei weitergibt. Wenn sich keine Zeile im Array befindet, seen[$0]wird false ausgewertet. Das !ist ein logischer NICHT-Operator und invertiert das Falsche in Wahr. Awk druckt die Zeilen, in denen der Ausdruck true ergibt. Die ++Inkremente, seenso dass seen[$0] == 1nach dem ersten Mal eine Zeile gefunden wird und dann seen[$0] == 2und so weiter.
Awk wertet alles außer 0und ""(leere Zeichenfolge) als wahr aus. Wenn eine doppelte Linie in platziert seendann !seen[$0]wird falsch bewerten und die Linie nicht in die Ausgabe geschrieben werden.
Um es in einer Datei zu speichern, können wir dies tunawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal
5
Eine wichtige Einschränkung hier: Wenn Sie dies für mehrere Dateien tun müssen und am Ende des Befehls weitere Dateien anheften oder einen Platzhalter verwenden, wird das Array "gesehen" mit doppelten Zeilen aus ALLEN Dateien gefüllt. Wenn Sie stattdessen jede Datei unabhängig behandeln möchten, müssen Sie etwas tun wiefor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9
@ NickK9, dass das kumulative De-Duping über mehrere Dateien hinweg an sich schon fantastisch ist. Netter Tipp
# 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'
Geekery ;-) +1, aber Ressourcenverbrauch ist unvermeidlich.
Michael Krelin - Hacker
3
'$! N; /^(.*)\n\1$/!P; D 'bedeutet "Wenn Sie nicht in der letzten Zeile sind, lesen Sie in einer anderen Zeile. Schauen Sie sich jetzt an, was Sie haben, und wenn es NICHT Zeug ist, gefolgt von einer neuen Zeile und dann wieder dasselbe Zeug, drucken Sie das Zeug aus. Löschen Sie es jetzt." das Zeug (bis zur Newline). "
Beta
2
'G; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'bedeutet ungefähr: "Hänge den gesamten Haltebereich an diese Zeile an. Wenn du dann eine doppelte Zeile siehst, wirf das Ganze raus, andernfalls kopiere das ganze Durcheinander zurück in den Haltebereich und drucke den ersten Teil (das ist die Zeile, die du gerade hast) lesen. "
Beta
Ist das $!Teil notwendig? Tut nicht sed 'N; /^\(.*\)\n\1$/!P; D'das Gleiche? Ich kann mir kein Beispiel ausdenken, bei dem die beiden auf meinem Computer unterschiedlich sind (fwiw, ich habe am Ende mit beiden Versionen eine leere Zeile ausprobiert und beide waren in Ordnung).
Eddi
1
Fast 7 Jahre später und niemand antwortete @amichair ... <sniff> macht mich traurig. ;) Stellt jedenfalls [ -~]einen Bereich von ASCII-Zeichen von 0x20 (Leerzeichen) bis 0x7E (Tilde) dar. Diese gelten als die druckbaren ASCII - Zeichen (gelinkten Seite hat auch 0x7F / löschen , aber das scheint nicht richtig). Das macht die Lösung für jeden kaputt, der kein ASCII verwendet oder beispielsweise Tabulatorzeichen verwendet. Je portabler es ist, [^\n]desto mehr Zeichen sind enthalten ... alle außer einem.
B-Schicht
14
Perl-Einzeiler ähnlich der awk-Lösung von @ jonas:
perl -ne 'print if ! $x{$_}++' file
Diese Variante entfernt nach dem Vergleich nachgestellte Leerzeichen:
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 Sicherung file.bak
Der Einzeiler, den Andre Miller oben gepostet hat, funktioniert mit Ausnahme der neuesten Versionen von sed, wenn die Eingabedatei mit einer Leerzeile und ohne 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 :
Das GNU sed Maintainer das Gefühl , dass trotz der Portabilität Probleme
würde dies dazu führen, den N - Befehl Ändern drucken (anstatt
der Musterraum konsistentere war löschen) mit einem Intuition
darüber , wie ein Befehl „fügen Sie die nächste Zeile“ soll verhalten.
Eine weitere Tatsache, die die Änderung begünstigte, war, dass "{N; Befehl;}"
die letzte Zeile löscht, wenn die Datei eine ungerade Anzahl von Zeilen enthält, aber
die letzte Zeile druckt, wenn die Datei eine gerade Anzahl von Zeilen enthält.
Ändern Sie ein einzelnes "N", um Skripte, die das frühere Verhalten von N (Löschen
des Musterbereichs beim Erreichen des EOF) verwendeten, in Skripte zu konvertieren, die mit
allen Versionen von sed kompatibel sind. zu "$ d; N;" .
print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.
Erklärt:
$!N;: Wenn die aktuelle Zeile NICHT die letzte Zeile ist, Nlesen Sie mit dem Befehl die nächste Zeile ein pattern space.
/^(.*)\n\1$/!P: Wenn der Inhalt des Stroms durch pattern spacezwei duplicate stringgetrennt ist \n, was bedeutet, dass die nächste Zeile die samemit 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 PBefehl verwenden, um die Zeichen im aktuellen pattern spaceutil \n( \nauch gedruckt) zu drucken.
D: Wir verwenden den DBefehl, um die Zeichen im aktuellen pattern spaceUtil zu löschen \n( \nebenfalls gelöscht). Der Inhalt von pattern spaceist dann die nächste Zeile.
und DBefehl erzwingt sed, zu seinem FIRSTBefehl zu springen, $!Nliest jedoch NICHT die nächste Zeile aus der Datei oder dem Standardeingabestream.
Die zweite Lösung ist leicht zu verstehen (von mir selbst):
print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.
Erklärt:
Lesen Sie eine neue Zeile aus dem Eingabestream oder der Eingabedatei und drucken Sie sie einmal aus.
Verwenden Sie den :loopBefehlssatz a labelnamed loop.
Verwenden Sie N, um die nächste Zeile in die zu lesen pattern space.
Verwenden Sie s/^(.*)\n\1$/\1/diese Option, um die aktuelle Zeile zu löschen. Wenn die nächste Zeile mit der aktuellen Zeile übereinstimmt, verwenden Sie den sBefehl, um die deleteAktion auszuführen.
Wenn der sBefehl erfolgreich ausgeführt wird, springen Sie mit der tloopBefehlskraft sedzum labelNamen loop, wodurch dieselbe Schleife zu den nächsten Zeilen ausgeführt wird, wenn keine doppelten aufeinander folgenden Zeilen der Zeile vorhanden sind latest printed. Andernfalls verwenden Sie den DBefehl für deletedie Zeile, die mit der identisch ist latest-printed line, und erzwingen Sie sedden Sprung zum ersten Befehl, bei dem es sich um den pBefehl handelt. Der Inhalt der aktuellen pattern spaceZeile ist die nächste neue Zeile.
uniq
reicht es aus.awk
größeren Dateien möglich ist , aber bei größeren Dateien ziemlich ressourcenintensiv sein wird.Antworten:
seen
ist ein assoziatives Array, an das Awk jede Zeile der Datei weitergibt. Wenn sich keine Zeile im Array befindet,seen[$0]
wird false ausgewertet. Das!
ist ein logischer NICHT-Operator und invertiert das Falsche in Wahr. Awk druckt die Zeilen, in denen der Ausdruck true ergibt. Die++
Inkremente,seen
so dassseen[$0] == 1
nach dem ersten Mal eine Zeile gefunden wird und dannseen[$0] == 2
und so weiter.Awk wertet alles außer
0
und""
(leere Zeichenfolge) als wahr aus. Wenn eine doppelte Linie in platziertseen
dann!seen[$0]
wird falsch bewerten und die Linie nicht in die Ausgabe geschrieben werden.quelle
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Von http://sed.sourceforge.net/sed1line.txt : (Bitte frag mich nicht, wie das funktioniert ;-))
quelle
$!
Teil notwendig? Tut nichtsed 'N; /^\(.*\)\n\1$/!P; D'
das Gleiche? Ich kann mir kein Beispiel ausdenken, bei dem die beiden auf meinem Computer unterschiedlich sind (fwiw, ich habe am Ende mit beiden Versionen eine leere Zeile ausprobiert und beide waren in Ordnung).[ -~]
einen Bereich von ASCII-Zeichen von 0x20 (Leerzeichen) bis 0x7E (Tilde) dar. Diese gelten als die druckbaren ASCII - Zeichen (gelinkten Seite hat auch 0x7F / löschen , aber das scheint nicht richtig). Das macht die Lösung für jeden kaputt, der kein ASCII verwendet oder beispielsweise Tabulatorzeichen verwendet. Je portabler es ist,[^\n]
desto mehr Zeichen sind enthalten ... alle außer einem.Perl-Einzeiler ähnlich der awk-Lösung von @ jonas:
Diese Variante entfernt nach dem Vergleich nachgestellte Leerzeichen:
Diese Variante bearbeitet die Datei direkt:
Diese Variante bearbeitet die Datei direkt und erstellt eine Sicherung
file.bak
quelle
Der Einzeiler, den Andre Miller oben gepostet hat, funktioniert mit Ausnahme der neuesten Versionen von sed, wenn die Eingabedatei mit einer Leerzeile und ohne 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 :
quelle
Eine alternative Möglichkeit 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
quelle
Die erste Lösung stammt ebenfalls von http://sed.sourceforge.net/sed1line.txt
Die Kernidee ist:
Erklärt:
$!N;
: Wenn die aktuelle Zeile NICHT die letzte Zeile ist,N
lesen Sie mit dem Befehl die nächste Zeile einpattern space
./^(.*)\n\1$/!P
: Wenn der Inhalt des Stroms durchpattern space
zweiduplicate string
getrennt ist\n
, was bedeutet, dass die nächste Zeile diesame
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 denP
Befehl verwenden, um die Zeichen im aktuellenpattern space
util\n
(\n
auch gedruckt) zu drucken.D
: Wir verwenden denD
Befehl, um die Zeichen im aktuellenpattern space
Util zu löschen\n
(\n
ebenfalls gelöscht). Der Inhalt vonpattern space
ist dann die nächste Zeile.D
Befehl erzwingtsed
, zu seinemFIRST
Befehl zu springen,$!N
liest jedoch NICHT die nächste Zeile aus der Datei oder dem Standardeingabestream.Die zweite Lösung ist leicht zu verstehen (von mir selbst):
Die Kernidee ist:
Erklärt:
:loop
Befehlssatz alabel
namedloop
.N
, um die nächste Zeile in die zu lesenpattern space
.s/^(.*)\n\1$/\1/
diese Option, um die aktuelle Zeile zu löschen. Wenn die nächste Zeile mit der aktuellen Zeile übereinstimmt, verwenden Sie dens
Befehl, um diedelete
Aktion auszuführen.s
Befehl erfolgreich ausgeführt wird, springen Sie mit dertloop
Befehlskraftsed
zumlabel
Namenloop
, wodurch dieselbe Schleife zu den nächsten Zeilen ausgeführt wird, wenn keine doppelten aufeinander folgenden Zeilen der Zeile vorhanden sindlatest printed
. Andernfalls verwenden Sie denD
Befehl fürdelete
die Zeile, die mit der identisch istlatest-printed line
, und erzwingen Siesed
den Sprung zum ersten Befehl, bei dem es sich um denp
Befehl handelt. Der Inhalt der aktuellenpattern space
Zeile ist die nächste neue Zeile.quelle
busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
Dies kann erreicht werden, indem awk
Below Line eindeutige Werte anzeigt
Sie können diese eindeutigen Werte in eine neue Datei ausgeben
Die neue Datei uniq_file_name enthält nur eindeutige Werte, keine Duplikate
quelle
Löscht die doppelten Zeilen mit awk.
quelle
cat
nutzlos. Jedenfallsuniq
erledigt dies bereits von selbst und erfordert nicht, dass die Eingabe genau ein Wort pro Zeile ist.