Poiché tutti i file di input sono già ordinati, è possibile ignorare l'effettiva fase di ordinamento e utilizzarli solo sort -m
per unire i file.
Su alcuni sistemi Unix (per quanto ne so solo Linux), potrebbe essere sufficiente farlo
sort -m *.words | uniq -d >dupes.txt
per ottenere le righe duplicate scritte nel file dupes.txt
.
Per trovare i file da cui provengono queste linee, puoi farlo
grep -Fx -f dupes.txt *.words
Questo indicherà grep
di trattare le linee in dupes.txt
( -f dupes.txt
) come schemi di stringa fissi ( -F
). grep
richiederà inoltre che l'intera riga corrisponda perfettamente dall'inizio alla fine ( -x
). Stampa il nome del file e la linea sul terminale.
Unices non Linux (o anche più file)
Su alcuni sistemi Unix, i nomi di file 30000 si espandono in una stringa che è troppo lunga per passare a una singola utility (il che significa sort -m *.words
che fallirà Argument list too long
, cosa che fa sul mio sistema OpenBSD). Anche Linux se ne lamenterà se il numero di file è molto più grande.
Trovare i duplicati
Ciò significa che nel caso generale (funzionerà anche con molti più di soli 30000 file), si dovrà "tagliare" l'ordinamento:
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
In alternativa, creare tmpfile
senza 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 {} +
Questo troverà tutti i file nella directory corrente (o sotto) i cui nomi corrispondono *.words
. Per un pezzo di dimensioni appropriate di questi nomi alla volta, la cui dimensione è determinata da xargs
/ find
, li unisce nel tmpfile
file ordinato . Se tmpfile
esiste già (per tutti tranne il primo blocco), questo file viene anche unito agli altri file nel blocco corrente. A seconda della lunghezza dei nomi dei file e della lunghezza massima consentita di una riga di comando, ciò potrebbe richiedere più o più di 10 singole esecuzioni dello script interno ( find
/ xargs
lo farà automaticamente).
La sh
sceneggiatura "interna" ,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
usa sort -o tmpfile
per l'output in tmpfile
(questo non sovrascriverà tmpfile
anche se anche questo è un input per sort
) e -m
per fare l'unione. In entrambi i rami, "$@"
verrà espanso in un elenco di nomi di file citati singolarmente passati allo script da find
o xargs
.
Poi, basta eseguire uniq -d
su tmpfile
di ottenere tutte le linee che vengono duplicati:
uniq -d tmpfile >dupes.txt
Se ti piace il principio "DRY" ("Non ripetere te stesso"), puoi scrivere lo script interno come
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
o
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
Da dove provengono?
Per gli stessi motivi di cui sopra, non possiamo usare grep -Fx -f dupes.txt *.words
per trovare da dove provengono queste duplicazioni, quindi invece find
riutilizziamo:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Poiché non è necessario eseguire elaborazioni "complicate", è possibile invocare grep
direttamente -exec
. L' -exec
opzione accetta un comando di utilità e inserirà i nomi trovati {}
. Con +
alla fine, find
inserirà tanti argomenti al posto di {}
quelli che la shell corrente supporta in ogni invocazione dell'utilità.
Per essere del tutto corretti, si potrebbe voler usare entrambi
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
o
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
per essere sicuri che i nomi dei file siano sempre inclusi nell'output di grep
.
La prima variante utilizza grep -H
per generare sempre nomi di file corrispondenti. L'ultima variante utilizza il fatto che grep
includerà il nome del file corrispondente se nella riga di comando viene fornito più di un file .
Ciò è importante dal momento che l'ultimo blocco di nomi di file inviato grep
da find
può effettivamente contenere solo un singolo nome file, nel qual caso grep
non lo menzionerebbe nei suoi risultati.
Materiale bonus:
Dissezione del comando find
+ xargs
+ sh
:
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'
genererà semplicemente un elenco di nomi di percorso dalla directory corrente (o inferiore) in cui ciascun percorso è quello di un normale file ( -type f
) e che ha un componente nome file alla fine corrispondente *.words
. Se si deve cercare solo la directory corrente , si può aggiungere -maxdepth 1
dopo il .
, prima -type f
.
-print0
farà in modo che tutti i percorsi trovati vengano emessi con un carattere \0
( nul
) come delimitatore. Questo è un personaggio che non è valido in un percorso Unix e ci consente di elaborare i nomi dei percorsi anche se contengono caratteri di nuova riga (o altre cose strane).
find
convoglia il suo output a xargs
.
xargs -0
leggerà l' \0
elenco -delimitato di nomi di percorso ed eseguirà ripetutamente l'utilità data con blocchi di questi, assicurandosi che l'utilità sia eseguita con argomenti sufficienti per non far lamentare alla shell un elenco di argomenti troppo lungo, fino a quando non ci sono più input da find
.
L'utilità invocata da xargs
è sh
con uno script fornito nella riga di comando come stringa usando il suo -c
flag.
Quando si invocano gli sh -c '...some script...'
argomenti seguenti, gli argomenti saranno disponibili per lo script $@
, ad eccezione del primo argomento , che verrà inserito $0
(questo è il "nome comando" che è possibile individuare, ad esempio top
se si è abbastanza veloci). Questo è il motivo per cui inseriamo la stringa sh
come primo argomento dopo la fine dello script reale. La stringa sh
è un argomento fittizio e potrebbe essere qualsiasi parola singola (alcuni sembrano preferire _
o sh-find
).
fi' sh
?