Da alle Eingabedateien bereits sortiert sind, können wir den eigentlichen Sortierschritt umgehen und nur sort -m
zum Zusammenführen der Dateien verwenden.
Auf einigen Unix-Systemen (meines Wissens nur unter Linux) kann dies ausreichen
sort -m *.words | uniq -d >dupes.txt
um die duplizierten Zeilen in die Datei zu schreiben dupes.txt
.
Um herauszufinden, aus welchen Dateien diese Zeilen stammen, können Sie dies tun
grep -Fx -f dupes.txt *.words
Dadurch wird angewiesen grep
, die Zeilen in dupes.txt
( -f dupes.txt
) als feste Zeichenfolgenmuster ( -F
) zu behandeln. grep
erfordert auch, dass die gesamte Linie von Anfang bis Ende perfekt übereinstimmt ( -x
). Der Dateiname und die Zeile zum Terminal werden gedruckt.
Nicht-Linux-Unices (oder noch mehr Dateien)
Auf einigen Unix-Systemen werden 30000 Dateinamen zu einer Zeichenfolge erweitert, die zu lang ist, um an ein einzelnes Dienstprogramm übergeben zu werden (was bedeutet , dass dies bei meinem OpenBSD-System sort -m *.words
fehlschlägt Argument list too long
). Sogar Linux wird sich darüber beschweren, wenn die Anzahl der Dateien viel größer ist.
Die Dupes finden
Dies bedeutet, dass im allgemeinen Fall (dies funktioniert auch mit viel mehr als nur 30000 Dateien) die Sortierung "aufgeteilt" werden muss:
rm -f tmpfile
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
Alternativ können Sie erstellen tmpfile
ohne xargs
:
rm -f tmpfile
find . -type f -name '*.words' -exec sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh {} +
Dadurch werden alle Dateien im aktuellen Verzeichnis (oder darunter) gefunden, deren Namen übereinstimmen *.words
. Für einen Teil dieser Namen mit angemessener Größe, dessen Größe durch xargs
/ bestimmt wird find
, werden sie in der sortierten tmpfile
Datei zusammengeführt. Wenn tmpfile
bereits vorhanden (für alle außer dem ersten Block), wird diese Datei auch mit den anderen Dateien im aktuellen Block zusammengeführt. Abhängig von der Länge Ihrer Dateinamen und der maximal zulässigen Länge einer Befehlszeile sind möglicherweise mehr oder mehr als 10 einzelne Ausführungen des internen Skripts erforderlich ( find
/ xargs
wird dies automatisch tun).
Das "interne" sh
Skript,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
wird verwendet, sort -o tmpfile
um auszugeben tmpfile
(dies wird nicht überschrieben, tmpfile
auch wenn dies auch eine Eingabe ist sort
) und -m
um die Zusammenführung durchzuführen. "$@"
Wird in beiden Zweigen zu einer Liste von einzeln zitierten Dateinamen erweitert, die von find
oder an das Skript übergeben werden xargs
.
Führen Sie dann einfach uniq -d
weiter tmpfile
, um alle Zeilen zu erhalten, die dupliziert wurden:
uniq -d tmpfile >dupes.txt
Wenn Ihnen das "DRY" -Prinzip ("Don't Repeat Yourself") gefällt, können Sie das interne Skript als schreiben
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
oder
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
Wo kommst du her?
Aus den gleichen Gründen wie oben können wir nicht grep -Fx -f dupes.txt *.words
ermitteln, woher diese Duplikate stammen. Stattdessen verwenden wir find
erneut:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Da keine "komplizierte" Verarbeitung erforderlich ist, können wir grep
direkt von aufrufen -exec
. Die -exec
Option verwendet einen Dienstprogrammbefehl und platziert die gefundenen Namen in {}
. Mit +
am Ende find
werden {}
bei jedem Aufruf des Dienstprogramms so viele Argumente anstelle der aktuellen Shell platziert.
Um ganz richtig zu sein, kann man beides verwenden
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
oder
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
um sicherzugehen, dass Dateinamen immer in der Ausgabe von enthalten sind grep
.
Die erste Variante verwendet, grep -H
um immer übereinstimmende Dateinamen auszugeben. Die letzte Variante verwendet die Tatsache, dass grep
der Name der übereinstimmenden Datei enthalten ist, wenn mehr als eine Datei in der Befehlszeile angegeben ist.
Dies ist wichtig, da der letzte Teil der Dateinamen, an die grep
von gesendet wird, find
möglicherweise nur einen einzigen Dateinamen enthält. In diesem Fall wird grep
er in den Ergebnissen nicht erwähnt.
Bonusmaterial:
Zerlegen des find
+ xargs
+ sh
Befehls:
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
find . -type f -name '*.words'
generiert einfach eine Liste von Pfadnamen aus dem aktuellen Verzeichnis (oder darunter), wobei jeder Pfadname der einer regulären Datei ( -type f
) ist und am Ende eine passende Dateinamenkomponente vorhanden ist *.words
. Wenn nur das aktuelle Verzeichnis durchsucht werden soll, kann man -maxdepth 1
nach dem .
, vor hinzufügen -type f
.
-print0
stellt sicher, dass alle gefundenen Pfadnamen mit einem \0
( nul
) Zeichen als Trennzeichen ausgegeben werden . Dies ist ein Zeichen, das in einem Unix-Pfad nicht gültig ist, und es ermöglicht uns, Pfadnamen zu verarbeiten, selbst wenn sie Zeilenumbruchzeichen (oder andere seltsame Dinge) enthalten.
find
leitet seine Ausgabe an xargs
.
xargs -0
liest die durch \0
-begrenzte Liste von Pfadnamen und führt das angegebene Dienstprogramm wiederholt mit Teilen davon aus, um sicherzustellen, dass das Dienstprogramm mit gerade genug Argumenten ausgeführt wird, damit sich die Shell nicht über eine zu lange Argumentliste beschwert, bis keine Eingabe mehr erfolgt von find
.
Das Dienstprogramm aufgerufen , indem xargs
ist sh
mit einem Skript in der Befehlszeile als String gegeben unter Verwendung seiner -c
Flagge.
Beim Aufrufen sh -c '...some script...'
mit folgenden Argumenten stehen die Argumente dem Skript in zur Verfügung $@
, mit Ausnahme des ersten Arguments , in das eingefügt wird $0
(dies ist der "Befehlsname", den Sie möglicherweise finden, z. B. top
wenn Sie schnell genug sind). Aus diesem Grund fügen wir die Zeichenfolge sh
als erstes Argument nach dem Ende des eigentlichen Skripts ein. Die Zeichenfolge sh
ist ein Dummy-Argument und kann ein einzelnes Wort sein (einige scheinen dies zu bevorzugen _
oder sh-find
).
fi' sh
?fi
ist das Ende derif
Anweisung im "internen"sh
Shell-Skript. Das'
Ende dieses Shell-Skripts (das gesamte Skript ist eine einfach zitierte Zeichenfolge). Dassh
wird an das interne Skript in übergeben$0
(nicht Teil von$@
, das die Dateinamen enthält). In diesem Fall kann diesesh
Zeichenfolge tatsächlich ein beliebiges Wort sein. Wennsh
am Ende weggelassen wird, wird der erste Dateiname übergeben$0
und ist nicht Teil der Verarbeitung, die das interne Shell-Skript ausführt.Was bedeutet, dass Sie wahrscheinlich eine Verwendung finden für
sort -m
:Die andere offensichtliche Alternative dazu wäre
awk
, die Zeilen in einem Array einfach zu sammeln und zu zählen. Aber wie @ dave_thompson_085 kommentierte, würden diese 3 000 Millionen Zeilen (oder wie viele eindeutige es auch gibt) wahrscheinlich eine beträchtliche Menge an Speicher zum Speichern benötigen , so dass dies möglicherweise nicht sehr gut funktioniert.quelle
Mit awk können Sie alle wiederholten Zeilen in allen Dateien mit einem kurzen Befehl abrufen:
Es werden jedoch Zeilen wiederholt, wenn eine Zeile dreimal oder öfter vorhanden ist.
Es gibt eine Lösung, um nur das erste Duplikat zu erhalten:
Es sollte ziemlich schnell sein (wenn es nur wenige Wiederholungen gibt), aber es wird viel Speicher verbrauchen, um alle Zeilen im Speicher zu halten. Abhängig von Ihren tatsächlichen Dateien und Wiederholungen versuchen Sie es möglicherweise zuerst mit drei oder vier Dateien.
Andernfalls können Sie Folgendes tun:
Dadurch werden einzelne wiederholte Zeilen gedruckt.
quelle
sort -m * | uniq -d
'x[$0]++==1'
benötigt aber tatsächlich viel Speicher; Wenn die 3G-Zeilen unterschiedliche 1G-Werte haben und Ihr awk beispielsweise 50 Byte für einen Hasharray-Eintrag benötigt, der eine (vermutlich kürzere) Zeichenfolge dem uninit-Wert zuordnet, sind das 50 GB. Für sortierte Eingaben können Sieuniq -d
manuell damit arbeiten,awk '$0==p&&n++==1;$0!=p{p=$0;n=1}'
aber warum sich die Mühe machen?==1
, tolle Idee.awk
2,4E11 Bytes (223 GiB) gespeichert werden.sort -m *.words | uniq -d
funktioniert super! Nach dem Vorgang suche ichgrep
nach Dateien, die einen doppelten Eintrag enthalten. Sehen Sie eine Möglichkeit, den mindestens einen Dateinamen zu drucken, der einen doppelten Eintrag enthält?Optimierte
sort
+uniq
Lösung:--parallel=N
- Ändern Sie die Anzahl der gleichzeitig ausgeführten Sortierungen inN
-d, --repeated
- Drucken Sie nur doppelte Zeilen, eine für jede Gruppequelle