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
?