Comparer le contenu de deux répertoires

93

J'ai deux répertoires qui devraient contenir les mêmes fichiers et avoir la même structure de répertoires.

Je pense qu'il manque quelque chose dans l'un de ces répertoires.

À l'aide du shell bash, existe-t-il un moyen de comparer mes répertoires et de voir si l'un d'entre eux manque des fichiers présents dans l'autre?

AndreaNobili
la source
1
Quelle est la sortie de bash --version?
jobin
1
Similaire , mais plus précis: stackoverflow.com/questions/16787916/...
Ciro Santilli新疆改造中心法轮功六四事件

Réponses:

64

Un bon moyen de faire cette comparaison est d'utiliser findavec md5sum, alors a diff.

Exemple

Utilisez find pour répertorier tous les fichiers du répertoire, puis calculez le hachage md5 pour chaque fichier et dirigez-le trié par nom de fichier vers un fichier:

find /dir1/ -type f -exec md5sum {} + | sort -k 2 > dir1.txt

Effectuez la même procédure dans un autre répertoire:

find /dir2/ -type f -exec md5sum {} + | sort -k 2 > dir2.txt

Comparez ensuite le résultat avec deux fichiers diff:

diff -u dir1.txt dir2.txt

Ou en tant que commande unique utilisant la substitution de processus:

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2) <(find /dir2/ -type f -exec md5sum {} + | sort -k 2)

Si vous voulez voir uniquement les modifications:

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2 | cut -f1 -d" ") <(find /dir2/ -type f -exec md5sum {} + | sort -k 2 | cut -f1 -d" ")

La commande cut n'imprime que le hachage (premier champ) à comparer par diff. Dans le cas contraire, diff imprimera chaque ligne car les chemins de répertoire diffèrent même lorsque le hachage est identique.

Mais vous ne saurez pas quel fichier a changé ...

Pour cela, vous pouvez essayer quelque chose comme

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2 | sed 's/ .*\// /') <(find /dir2/ -type f -exec md5sum {} + | sort -k 2 | sed 's/ .*\// /')

Cette stratégie est très utile lorsque les deux répertoires à comparer ne se trouvent pas sur le même ordinateur et que vous devez vous assurer que les fichiers sont égaux dans les deux répertoires.

Un autre bon moyen de faire le travail est d'utiliser la diffcommande de Git (peut causer des problèmes lorsque les fichiers ont des autorisations différentes -> chaque fichier est alors répertorié dans la sortie):

git diff --no-index dir1/ dir2/
Adail Junior
la source
1
Cela ne fonctionne pas sans une étape de tri supplémentaire, car l'ordre dans lequel findles fichiers seront listés sera généralement différent entre les deux répertoires.
Faheem Mitha
1
Vous pouvez utiliser la méthode décrite dans askubuntu.com/a/662383/15729 pour trier les fichiers.
Faheem Mitha
1
Je reçois le message d'erreur `find: md5sum: Aucun fichier ou répertoire de ce type
Houman,
1
@Houman Je ne sais pas quel Linux Distro vous utilisez, mais vous devez peut-être installer un paquet qui fournira de md5sum. Dans Fedora 26, vous pouvez l'installer avec: #dnf install coreutils
Adail Junior
Utilisez md5 () à la place
boj
81

Vous pouvez utiliser la diffcommande comme vous le feriez pour des fichiers:

diff <directory1> <directory2>

Si vous souhaitez également voir les sous-dossiers et les fichiers, vous pouvez utiliser l' -roption suivante:

diff -r <directory1> <directory2>
Alex R.
la source
2
Did not know difffonctionne également pour les répertoires (man diff a confirmé cela), mais cela ne vérifie pas de manière récursive les modifications apportées aux sous-répertoires dans les sous-répertoires.
jobin
1
@Jobin C'est étrange ... Pour moi, ça marche.
Alex R.
1
J'ai quelque chose comme ça: a/b/c/d/a, x/b/c/d/b. Voyez ce que diff a xvous donne.
travail le
2
Vous devez utiliser l' -roption. Cela ( diff -r a x) me donne:Only in a/b/c/d: a. only in x/b/c/d: b.
Alex R.
3
diff me montre la différence dans les fichiers mais pas si un répertoire contient un fichier que l'autre ne contient pas !!! Je n'ai pas besoin de connaître les différences entre les fichiers, mais également si un fichier existe dans un répertoire et non dans l'autre.
AndreaNobili
25

Grâce à vous n'utilisez pas bash, vous pouvez le faire en utilisant diff avec --briefet --recursive:

$ diff -rq dir1 dir2 
Only in dir2: file2
Only in dir1: file1

Le man diffcomprend les deux options:

-q, --brief
signaler uniquement lorsque les fichiers diffèrent

-r, --recursive
comparez récursivement les sous-répertoires trouvés

Braiam
la source
13

Voici une alternative, pour comparer uniquement les noms de fichiers, et non leur contenu:

diff <(cd folder1 && find . | sort) <(cd folder2 && find . | sort)

C'est un moyen facile de lister les fichiers manquants, mais bien sûr, il ne détectera pas les fichiers portant le même nom mais un contenu différent!

(Personnellement, j'utilise mon propre diffdirsscript, mais cela fait partie d'une plus grande bibliothèque .)

Joeytwiddle
la source
3
Vous feriez mieux d'utiliser la substitution de processus, pas les fichiers temporaires ...
mniip le
3
Notez que cela ne prend pas en charge les noms de fichiers avec certains caractères spéciaux. Dans ce cas, vous pouvez utiliser des délimiteurs zéro que AFAIK diffne prend pas en charge pour le moment. Mais il y en a commqui le soutiennent depuis git.savannah.gnu.org/cgit/coreutils.git/commit/… donc une fois que cela arrive à un coreutils près de chez vous, vous pouvez le faire comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(dont vous pourriez avoir à convertir le format au format vous devez utiliser le --output-delimiterparamètre et les outils supplémentaires).
phk
8

Peut-être qu'une option consiste à exécuter rsync deux fois:

rsync -r -n -t -v -O --progress -c -s /dir1/ /dir2/

Avec la ligne précédente, vous obtiendrez des fichiers qui sont dans dir1 et qui sont différents (ou manquants) dans dir2.

rsync -r -n -t -v -O --progress -c -s /dir2/ /dir1/

Même chose pour dir2

#from the rsync --help :
-r, --recursive             recurse into directories
-n, --dry-run               perform a trial run with no changes made
-t, --times                 preserve modification times
-v, --verbose               increase verbosity
    --progress              show progress during transfer
-c, --checksum              skip based on checksum, not mod-time & size
-s, --protect-args          no space-splitting; only wildcard special-chars
-O, --omit-dir-times        omit directories from --times

Vous pouvez supprimer l' -noption permettant de subir les modifications. C'est copier la liste des fichiers dans le deuxième dossier.

Dans ce cas, une bonne option est peut-être d'utiliser -u, pour éviter d'écraser les fichiers les plus récents.

-u, --update                skip files that are newer on the receiver

Un one-liner:

rsync -rtvcsOu -n --progress /dir1/ /dir2/ && rsync -rtvcsOu -n --progress /dir2/ /dir1/
Ferroao
la source
3

Si vous voulez rendre chaque fichier extensible et pliable, vous pouvez diriger la sortie de diff -rdans Vim.

D'abord, donnons à Vim une règle de pliage:

mkdir -p ~/.vim/ftplugin
echo "set foldexpr=getline(v:lnum)=~'^diff.*'?'>1':1 foldmethod=expr fdc=2" >> ~/.vim/ftplugin/diff.vim

Maintenant juste:

diff -r dir1 dir2 | vim -

Vous pouvez frapper zoet zcpour ouvrir et fermer les plis. Pour sortir de Vim, appuyez sur:q<Enter>

Joeytwiddle
la source
3

Tâche assez facile à réaliser en python:

python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' DIR1 DIR2

Remplacez les valeurs réelles par DIR1et DIR2.

Voici un exemple de parcours:

$ python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' Desktop/ Desktop
SAME
$ python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' Desktop/ Pictures/
DIFF

Pour la lisibilité, voici un script réel au lieu de one-liner:

#!/usr/bin/env python
import os, sys

d1 = os.listdir(sys.argv[1])
d2 = os.listdir(sys.argv[2])
d1.sort()
d2.sort()

if d1 == d2:
    print("SAME")
else:
    print("DIFF")
Sergiy Kolodyazhnyy
la source
2
Notez que le os.listdirne donne aucun ordre spécifique. Les listes peuvent donc avoir les mêmes choses dans un ordre différent et la comparaison échouera.
Muru
1
@ bon point, je vais inclure le tri à cela
Sergiy Kolodyazhnyy
3

Inspiré par la réponse de Sergiy, j'ai écrit mon propre script Python pour comparer deux répertoires.

Contrairement à beaucoup d'autres solutions, il ne compare pas le contenu des fichiers. De plus, il n’entre pas dans les sous-répertoires qui manquent dans l’un des répertoires. La sortie est donc assez concise et le script fonctionne rapidement avec les grands répertoires.

#!/usr/bin/env python3

import os, sys

def compare_dirs(d1: "old directory name", d2: "new directory name"):
    def print_local(a, msg):
        print('DIR ' if a[2] else 'FILE', a[1], msg)
    # ensure validity
    for d in [d1,d2]:
        if not os.path.isdir(d):
            raise ValueError("not a directory: " + d)
    # get relative path
    l1 = [(x,os.path.join(d1,x)) for x in os.listdir(d1)]
    l2 = [(x,os.path.join(d2,x)) for x in os.listdir(d2)]
    # determine type: directory or file?
    l1 = sorted([(x,y,os.path.isdir(y)) for x,y in l1])
    l2 = sorted([(x,y,os.path.isdir(y)) for x,y in l2])
    i1 = i2 = 0
    common_dirs = []
    while i1<len(l1) and i2<len(l2):
        if l1[i1][0] == l2[i2][0]:      # same name
            if l1[i1][2] == l2[i2][2]:  # same type
                if l1[i1][2]:           # remember this folder for recursion
                    common_dirs.append((l1[i1][1], l2[i2][1]))
            else:
                print_local(l1[i1],'type changed')
            i1 += 1
            i2 += 1
        elif l1[i1][0]<l2[i2][0]:
            print_local(l1[i1],'removed')
            i1 += 1
        elif l1[i1][0]>l2[i2][0]:
            print_local(l2[i2],'added')
            i2 += 1
    while i1<len(l1):
        print_local(l1[i1],'removed')
        i1 += 1
    while i2<len(l2):
        print_local(l2[i2],'added')
        i2 += 1
    # compare subfolders recursively
    for sd1,sd2 in common_dirs:
        compare_dirs(sd1, sd2)

if __name__=="__main__":
    compare_dirs(sys.argv[1], sys.argv[2])

Si vous enregistrez dans un fichier nommé compare_dirs.py, vous pouvez l'exécuter avec Python3.x:

python3 compare_dirs.py dir1 dir2

Exemple de sortie:

user@laptop:~$ python3 compare_dirs.py old/ new/
DIR  old/out/flavor-domino removed
DIR  new/out/flavor-maxim2 added
DIR  old/target/vendor/flavor-domino removed
DIR  new/target/vendor/flavor-maxim2 added
FILE old/tmp/.kconfig-flavor_domino removed
FILE new/tmp/.kconfig-flavor_maxim2 added
DIR  new/tools/tools/LiveSuit_For_Linux64 added

PS Si vous avez besoin de comparer la taille des fichiers et les hachages de fichiers pour les modifications éventuelles, j’ai publié un script mis à jour ici: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779

Andriy Makukha
la source
1
Merci, j'ai ajouté un troisième paramètre optionnel regexp pour ignorer / ignorer gist.github.com/mscalora/e86e2bbff3c24a7c1784f3d692b1c684 afin de créer ce dont j'avais besoin, comme par exemple:cmpdirs dir1 dir2 '/\.git/'
Mike
0

J'ajouterai à cette liste une alternative à NodeJs que j'ai écrite il y a quelque temps.

dir-compare

npm install dir-compare -g
dircompare dir1 dir2
Gliviu
la source
0

Je voudrais suggérer un excellent outil que je viens de découvrir: MELD .

Cela fonctionne correctement et tout ce que vous pouvez faire avec la commande diffsur un système basé sur Linux peut être reproduit avec une belle interface graphique! Prendre plaisir

Léos313
la source