Comparar los contenidos de dos directorios


92

Tengo dos directorios que deben contener los mismos archivos y tener la misma estructura de directorios.

Creo que falta algo en uno de estos directorios.

Usando el shell bash, ¿hay alguna forma de comparar mis directorios y ver si a uno de ellos le faltan archivos que están presentes en el otro?


1
¿Cuál es la salida de bash --version?
jobin

Respuestas:


63

Una buena manera de hacer esta comparación es usar findcon md5sum, luego a diff.

Ejemplo

Use find para enumerar todos los archivos en el directorio, luego calcule el hash md5 para cada archivo y canalícelo por nombre de archivo a un archivo:

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

Realice el mismo procedimiento para el otro directorio:

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

Luego compare el resultado con dos archivos diff:

diff -u dir1.txt dir2.txt

O como un solo comando usando la sustitución de procesos:

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

Si solo quieres ver los cambios:

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" ")

El comando de corte imprime solo el hash (primer campo) para ser comparado por diff. De lo contrario, diff imprimirá cada línea, ya que las rutas del directorio difieren incluso cuando el hash es el mismo.

Pero no sabrás qué archivo cambió ...

Para eso, puedes probar algo como

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

Esta estrategia es muy útil cuando los dos directorios a comparar no están en la misma máquina y debe asegurarse de que los archivos sean iguales en ambos directorios.

Otra buena manera de hacer el trabajo es usar el diffcomando de Git (puede causar problemas cuando los archivos tienen permisos diferentes -> cada archivo aparece en la salida):

git diff --no-index dir1/ dir2/

1
Esto no funciona sin un paso de clasificación adicional, porque el orden en que findse enumerarán los archivos diferirá en general entre los dos directorios.
Faheem Mitha

1
Se puede usar el método descrito en askubuntu.com/a/662383/15729 para ordenar los archivos.
Faheem Mitha

1
Me sale el error `` find: md5sum: No existe tal archivo o directorio
Houman

1
@Houman No sé qué Linux Distro estás usando, pero quizás necesites instalar un paquete que proporcione de md5sum. En Fedora 26 puede instalarlo con: #dnf install coreutils
Adail Junior

Utilice md5 () en su lugar
boj

81

Puede usar el diffcomando tal como lo usaría para los archivos:

diff <directory1> <directory2>

Si desea ver subcarpetas y archivos también, puede usar la -ropción:

diff -r <directory1> <directory2>

2
No sabía que también difffunciona para directorios (man diff lo confirmó), pero esto no verifica recursivamente los cambios en los subdirectorios dentro de los subdirectorios.
jobin

1
@Jobin Eso es extraño ... Para mí, funciona.
Alex R.

1
Tengo algo como esto: a/b/c/d/a, x/b/c/d/b. Mira lo diff a xque te da.
jobin

2
Tienes que usar la -ropción. Eso ( diff -r a x) me da:Only in a/b/c/d: a. only in x/b/c/d: b.
Alex R.

3
¡¡¡diff, muéstrame la diferencia en los archivos, pero no si un directorio contiene un archivo que el otro no contiene !!! No necesito saber las diferencias en el archivo, pero también si existe un archivo en un directorio y no en el otro
AndreaNobili

25

Si no está utilizando bash, puede hacerlo utilizando diff con --briefy --recursive:

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

La man diffincluye las dos opciones:

-q, --brief
informe solo cuando los archivos difieran

-r, --recursive
compare recursivamente cualquier subdirectorio encontrado


13

Aquí hay una alternativa, para comparar solo nombres de archivos, y no sus contenidos:

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

Esta es una manera fácil de enumerar los archivos que faltan, pero, por supuesto , ¡no detectará archivos con el mismo nombre pero con contenidos diferentes!

(Personalmente uso mi propio diffdirsscript, pero eso es parte de una biblioteca más grande ).


3
Será mejor que use la sustitución de procesos, no los archivos temporales ...
mniip

3
Tenga en cuenta que esto no admite nombres de archivo con ciertos caracteres especiales, en ese caso es posible que desee utilizar delimitadores cero que AFAIK diffno admite a partir de ahora. Pero hay algo commque lo admite desde git.savannah.gnu.org/cgit/coreutils.git/commit/…, así que una vez que se trata de un núcleoutils cerca de usted, puede hacerlo comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(cuya salida puede tener que convertir aún más en el formato necesita usar el --output-delimiterparámetro y las herramientas adicionales).
phk

7

Tal vez una opción es ejecutar rsync dos veces:

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

Con la línea anterior, obtendrá los archivos que están en dir1 y son diferentes (o que faltan) en dir2.

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

Lo mismo para 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

Puede eliminar la -nopción de someterse a los cambios. Eso es copiar la lista de archivos a la segunda carpeta.

En caso de que lo haga, tal vez sea una buena opción usar -u, para evitar sobrescribir archivos más nuevos.

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

Una frase:

rsync -rtvcsOu -n --progress /dir1/ /dir2/ && rsync -rtvcsOu -n --progress /dir2/ /dir1/

3

Si desea hacer que cada archivo sea expandible y contraíble, puede canalizar la salida diff -ra Vim.

Primero demos a Vim una regla de plegado:

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

Ahora sólo:

diff -r dir1 dir2 | vim -

Puede golpear zoy zcabrir y cerrar pliegues. Para salir de Vim, presiona:q<Enter>


3

Tarea bastante fácil de lograr 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

Sustituya los valores reales por DIR1y DIR2.

Aquí hay una muestra de ejecución:

$ 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

Para facilitar la lectura, aquí hay un script real en lugar de una línea:

#!/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")

2
Tenga en cuenta que os.listdirno da ningún orden específico. Por lo tanto, las listas pueden tener las mismas cosas en un orden diferente y la comparación podría fallar.
muru

1
@muru buen punto, incluiré la clasificación para eso
Sergiy Kolodyazhnyy

3

Inspirado por la respuesta de Sergiy, escribí mi propio script de Python para comparar dos directorios.

A diferencia de muchas otras soluciones, no compara el contenido de los archivos. Además, no va dentro de subdirectorios que faltan en uno de los directorios. Por lo tanto, el resultado es bastante conciso y el script funciona rápido con directorios grandes.

#!/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 lo guarda en un archivo llamado compare_dirs.py, puede ejecutarlo con Python3.x:

python3 compare_dirs.py dir1 dir2

Salida de muestra:

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

PD: si necesita comparar tamaños de archivos y hashes de archivos para posibles cambios, publiqué un script actualizado aquí: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779


1
Gracias, agregué un tercer parámetro opcional regexp para omitir / ignorar gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684 para hacer justo lo que necesitaba:cmpdirs dir1 dir2 '/\.git/'
Mike

0

Agregaré a esta lista una alternativa de NodeJs que escribí hace algún tiempo.

dir-compare

npm install dir-compare -g
dircompare dir1 dir2

0

Me gustaría sugerir una gran herramienta que acabo de descubrir: MELD .

Funciona correctamente y todo lo que puede hacer con el comando diffen un sistema basado en Linux, ¡puede replicarse allí con una agradable interfaz gráfica! Disfrutar

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.