Étant donné que tous les fichiers d'entrée sont déjà triés, nous pouvons contourner l'étape de tri réelle et simplement utiliser sort -m
pour fusionner les fichiers ensemble.
Sur certains systèmes Unix (à ma connaissance uniquement Linux), cela peut suffire
sort -m *.words | uniq -d >dupes.txt
pour obtenir les lignes dupliquées écrites dans le fichier dupes.txt
.
Pour trouver de quels fichiers proviennent ces lignes, vous pouvez alors faire
grep -Fx -f dupes.txt *.words
Cela demandera grep
de traiter les lignes entre dupes.txt
( -f dupes.txt
) comme des modèles de chaînes fixes ( -F
). grep
exigera également que la ligne entière corresponde parfaitement du début à la fin ( -x
). Il imprimera le nom du fichier et la ligne sur le terminal.
Unices non Linux (ou même plus de fichiers)
Sur certains systèmes Unix, 30000 noms de fichiers se développeront en une chaîne trop longue pour passer à un seul utilitaire (la signification sort -m *.words
échouera avec Argument list too long
, ce qui se produit sur mon système OpenBSD). Même Linux s'en plaindra si le nombre de fichiers est beaucoup plus important.
Trouver les dupes
Cela signifie que dans le cas général (cela fonctionnera également avec bien plus que 30000 fichiers), il faut "découper" le tri:
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
Alternativement, créer tmpfile
sans 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 {} +
Cela trouvera tous les fichiers dans le répertoire courant (ou ci-dessous) dont les noms correspondent *.words
. Pour un morceau de taille appropriée de ces noms à la fois, dont la taille est déterminée par xargs
/ find
, il les fusionne dans le tmpfile
fichier trié . S'il tmpfile
existe déjà (pour tous sauf le premier bloc), ce fichier est également fusionné avec les autres fichiers du bloc actuel. Selon la longueur de vos noms de fichiers et la longueur maximale autorisée d'une ligne de commande, cela peut nécessiter plus ou beaucoup plus de 10 exécutions individuelles du script interne ( find
/ le xargs
fera automatiquement).
Le sh
script "interne" ,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
utilise sort -o tmpfile
pour sortir vers tmpfile
(cela n'écrasera pas tmpfile
même si c'est aussi une entrée pour sort
) et -m
pour faire la fusion. Dans les deux branches, "$@"
s'étendra à une liste de noms de fichiers entre guillemets individuels transmis au script à partir de find
ou xargs
.
Ensuite, il suffit d' exécuter uniq -d
sur tmpfile
pour obtenir toutes les lignes qui sont dupliquées:
uniq -d tmpfile >dupes.txt
Si vous aimez le principe "DRY" ("Ne vous répétez pas"), vous pouvez écrire le script interne comme
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
ou
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
D'où viennent-ils?
Pour les mêmes raisons que ci-dessus, nous ne pouvons pas utiliser grep -Fx -f dupes.txt *.words
pour trouver d'où proviennent ces duplications, nous utilisons donc à find
nouveau:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Puisqu'il n'y a pas de traitement "compliqué" à faire, nous pouvons invoquer grep
directement depuis -exec
. L' -exec
option prend une commande utilitaire et placera les noms trouvés dans {}
. Avec +
à la fin, find
placera autant d'arguments à la place {}
que le shell actuel prend en charge dans chaque appel de l'utilitaire.
Pour être totalement correct, on peut vouloir utiliser soit
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
ou
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
pour être sûr que les noms de fichiers sont toujours inclus dans la sortie de grep
.
La première variante utilise grep -H
pour toujours afficher les noms de fichiers correspondants. La dernière variante utilise le fait qui grep
inclura le nom du fichier correspondant si plus d'un fichier est donné sur la ligne de commande.
Cela est important car le dernier morceau de noms de fichiers envoyé grep
depuis ne find
peut en fait contenir qu'un seul nom de fichier, auquel cas grep
il ne le mentionnerait pas dans ses résultats.
Matériel bonus:
Dissection de la commande 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'
générera simplement une liste de chemins à partir du répertoire courant (ou ci-dessous) où chaque chemin est celui d'un fichier normal ( -type f
) et qui a un composant de nom de fichier à la fin qui correspond *.words
. Si seul le répertoire courant doit être recherché, on peut ajouter -maxdepth 1
après le .
, avant -type f
.
-print0
s'assurera que tous les chemins trouvés sont sortis avec un caractère \0
( nul
) comme délimiteur. Il s'agit d'un caractère qui n'est pas valide dans un chemin Unix et il nous permet de traiter les chemins d'accès même s'ils contiennent des caractères de nouvelle ligne (ou d'autres choses étranges).
find
dirige sa sortie vers xargs
.
xargs -0
lira la \0
liste de chemins d'accès délimitée et exécutera l'utilitaire donné à plusieurs reprises avec des morceaux de ceux-ci, en s'assurant que l'utilitaire est exécuté avec juste assez d'arguments pour ne pas faire se plaindre le shell d'une liste d'arguments trop longue, jusqu'à ce qu'il n'y ait plus d'entrée de find
.
L'utilitaire invoqué par xargs
est sh
avec un script donné sur la ligne de commande sous forme de chaîne utilisant son -c
indicateur.
Lors de l'appel sh -c '...some script...'
avec les arguments suivants, les arguments seront disponibles pour le script dans $@
, à l' exception du premier argument , qui sera placé $0
(c'est le "nom de la commande" que vous pouvez repérer, par exemple top
si vous êtes assez rapide). C'est pourquoi nous insérons la chaîne sh
comme premier argument après la fin du script réel. La chaîne sh
est un argument factice et peut être n'importe quel mot (certains semblent préférer _
ou sh-find
).
fi' sh
?