Confronto tra i contenuti di due directory


93

Ho due directory che dovrebbero contenere gli stessi file e avere la stessa struttura di directory.

Penso che manchi qualcosa in una di queste directory.

Usando la shell bash, c'è un modo per confrontare le mie directory e vedere se a uno di essi mancano i file presenti nell'altro?


1
Qual è l'output di bash --version?
Jobin

Risposte:


64

Un buon modo per fare questo confronto è usare findcon md5sum, quindi a diff.

Esempio

Usa trova per elencare tutti i file nella directory, quindi calcola l'hash md5 per ogni file e esegui il pipe in ordine di nome file in un file:

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

Esegui la stessa procedura in un'altra directory:

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

Quindi confrontare il risultato due file con diff:

diff -u dir1.txt dir2.txt

O come singolo comando usando la sostituzione del processo:

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

Se vuoi vedere solo le modifiche:

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

Il comando cut stampa solo l'hash (primo campo) da confrontare con diff. Altrimenti diff stamperà ogni riga poiché i percorsi della directory differiscono anche quando l'hash è lo stesso.

Ma non saprai quale file è cambiato ...

Per questo, puoi provare qualcosa del genere

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

Questa strategia è molto utile quando le due directory da confrontare non sono nella stessa macchina ed è necessario assicurarsi che i file siano uguali in entrambe le directory.

Un altro buon modo per fare il lavoro è usare il diffcomando di Git (può causare problemi quando i file hanno autorizzazioni diverse -> ogni file è elencato nell'output quindi):

git diff --no-index dir1/ dir2/

1
Ciò non funziona senza una fase di ordinamento aggiuntiva, poiché l'ordine in cui findverranno elencati i file differirà in generale tra le due directory.
Faheem Mitha,

1
Si può usare il metodo descritto in askubuntu.com/a/662383/15729 per ordinare i file.
Faheem Mitha,

1
Ottengo l'errore `` find: md5sum: nessun file o directory del genere
Houman,

1
@Houman Non so quale Linux Distro stai usando, ma forse devi installare un pacchetto che fornirà de md5sum. In Fedora 26 puoi installarlo con: #dnf install coreutils
Adail Junior

Usa invece md5 ()
boj

81

Puoi usare il diffcomando così come lo useresti per i file:

diff <directory1> <directory2>

Se vuoi vedere anche le sottocartelle e i file, puoi usare l' -ropzione:

diff -r <directory1> <directory2>

2
Non sapevo che difffunzionasse anche per le directory (man diff lo ha confermato), ma questo non controlla ricorsivamente i cambiamenti nelle sottodirectory all'interno delle sottodirectory.
Jobin

1
@Jobin È strano ... Per me funziona.
Alex R.

1
Ho qualcosa del genere: a/b/c/d/a, x/b/c/d/b. Vedi cosa diff a xti dà.
Jobin

2
Devi usare l' -ropzione. Questo ( diff -r a x) mi dà:Only in a/b/c/d: a. only in x/b/c/d: b.
Alex R.

3
diff mi mostra la differenza nei file INTO ma non se una directory contiene un file che l'altro non contiene !!! Non ho bisogno di conoscere le differenze nel file ma anche se esiste un file in una directory e non nell'altra
AndreaNobili,

25

Attraverso non stai usando bash, puoi farlo usando diff con --briefe --recursive:

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

Il man diffinclude entrambe le opzioni:

-q, --brief
segnala solo quando i file differiscono

-r, --recursive
confronta ricorsivamente tutte le sottodirectory trovate


13

Ecco un'alternativa, per confrontare solo i nomi di file e non il loro contenuto:

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

Questo è un modo semplice per elencare i file mancanti, ma ovviamente non rileverà file con lo stesso nome ma con contenuti diversi!

(Personalmente uso il mio diffdirsscript, ma fa parte di una libreria più grande .)


3
Faresti meglio a usare la sostituzione di processo, non i file temporanei ...
mniip,

3
Nota che questo non supporta i nomi di file con determinati caratteri speciali, in tal caso potresti voler usare delimitatori zero che AFAIK diffnon supporta al momento. Ma c'è commciò che lo supporta dal momento che git.savannah.gnu.org/cgit/coreutils.git/commit/… quindi una volta che si tratta di un coreutils vicino a te, puoi fare comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(di cui potresti dover convertire ulteriormente l'output nel formato è necessario utilizzare il --output-delimiterparametro e strumenti aggiuntivi).
phk,

8

Forse un'opzione è eseguire rsync due volte:

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

Con la riga precedente, otterrai file che sono in dir1 e sono diversi (o mancanti) in dir2.

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

Lo stesso per 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

È possibile eliminare l' -nopzione per subire le modifiche. Quello sta copiando l'elenco dei file nella seconda cartella.

In tal caso, forse una buona opzione è quella di utilizzare -u, per evitare di sovrascrivere i file più recenti.

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

Una fodera:

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

3

Se vuoi rendere ogni file espandibile e pieghevole, puoi reindirizzare l'output di diff -rVim.

Per prima cosa diamo a Vim una regola pieghevole:

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

Adesso solo:

diff -r dir1 dir2 | vim -

Puoi colpire zoe zcaprire e chiudere le pieghe. Per uscire da Vim, colpisci:q<Enter>


3

Compito abbastanza facile da realizzare in 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

Sostituire i valori effettivi per DIR1e DIR2.

Ecco un esempio:

$ 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

Per leggibilità, ecco una sceneggiatura reale invece di una riga:

#!/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
Si noti che il os.listdirnon dà alcun ordine specifico. Quindi le liste potrebbero avere le stesse cose in ordine diverso e il confronto fallirebbe.
muru,

1
@muru buon punto, includerò l'ordinamento per quello
Sergiy Kolodyazhnyy

3

Ispirato dalla risposta di Sergiy, ho scritto il mio script Python per confrontare due directory.

A differenza di molte altre soluzioni, non confronta i contenuti dei file. Inoltre non rientra nelle sottodirectory mancanti in una delle directory. Quindi l'output è abbastanza conciso e lo script funziona velocemente con directory di grandi dimensioni.

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

Se lo salvi in ​​un file chiamato compare_dirs.py, puoi eseguirlo con Python3.x:

python3 compare_dirs.py dir1 dir2

Uscita campione:

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 Se devi confrontare le dimensioni dei file e gli hash dei file per potenziali modifiche, ho pubblicato uno script aggiornato qui: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779


1
Grazie, ho aggiunto un terzo parametro regexp opzionale per saltare / ignorare gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684 per creare esattamente ciò di cui avevo bisogno:cmpdirs dir1 dir2 '/\.git/'
Mike

0

Aggiungerò a questa lista un'alternativa a NodeJs che ho scritto qualche tempo fa.

dir-confrontare

npm install dir-compare -g
dircompare dir1 dir2

0

Vorrei suggerire un ottimo strumento che ho appena scoperto: MELD .

Funziona correttamente e tutto ciò che puoi fare con il comando diffsu un sistema basato su Linux, può essere lì replicato con una bella interfaccia grafica! Godere

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.