Comparando o conteúdo de dois diretórios

93

Eu tenho dois diretórios que devem conter os mesmos arquivos e ter a mesma estrutura de diretórios.

Eu acho que algo está faltando em um desses diretórios.

Usando o shell bash, existe uma maneira de comparar meus diretórios e ver se um deles está faltando arquivos que estão presentes no outro?

AndreaNobili
fonte
1
Qual é a saída de bash --version?
Jobin
1
Semelhante, porém mais específico: stackoverflow.com/questions/16787916/…
Ciro Santilli escreveu:

Respostas:

64

Uma boa maneira de fazer essa comparação é usar findcom md5sum, então a diff.

Exemplo

Use find para listar todos os arquivos no diretório e calcule o hash md5 para cada arquivo e canalize-o por nome de arquivo para um arquivo:

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

Execute o mesmo procedimento para o outro diretório:

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

Em seguida, compare o resultado dois arquivos com diff:

diff -u dir1.txt dir2.txt

Ou como um único comando usando substituição de processo:

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

Se você deseja ver apenas as alterações:

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

O comando recortar imprime apenas o hash (primeiro campo) a ser comparado pelo diff. Caso contrário, o diff imprimirá todas as linhas, pois os caminhos do diretório diferem mesmo quando o hash é o mesmo.

Mas você não saberá qual arquivo foi alterado ...

Para isso, você pode tentar 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/ .*\// /')

Essa estratégia é muito útil quando os dois diretórios a serem comparados não estão na mesma máquina e você precisa garantir que os arquivos sejam iguais nos dois diretórios.

Outra boa maneira de fazer o trabalho é usar o diffcomando Git (pode causar problemas quando os arquivos têm permissões diferentes -> todos os arquivos são listados na saída):

git diff --no-index dir1/ dir2/
Adail Junior
fonte
1
Isso não funciona sem uma etapa extra de classificação, porque a ordem na qual findos arquivos serão listados diferirá em geral entre os dois diretórios.
Faheem Mitha
1
Pode-se usar o método descrito em askubuntu.com/a/662383/15729 para classificar os arquivos.
Faheem Mitha
1
Eu recebo o erro `` find: md5sum: Não
existe
1
@Houman Eu não sei o que Linux Distro você está usando, mas talvez você precise instalar um pacote que forneça o md5sum. No Fedora 26 você pode instalá-lo com: #dnf install coreutils #
Adail Junior #
Em vez disso, use md5 () #
22418
81

Você pode usar o diffcomando da mesma maneira que usaria para arquivos:

diff <directory1> <directory2>

Se você deseja ver também as subpastas e -files, pode usar a -ropção:

diff -r <directory1> <directory2>
Alex R.
fonte
2
diffTambém não sabia que funciona para diretórios (o man diff confirmou isso), mas isso não verifica recursivamente se há alterações nos subdiretórios dentro dos subdiretórios.
Jobin
1
@Jobin Isso é estranho ... Para mim, funciona.
Alex R.
1
Eu tenho algo parecido com isto: a/b/c/d/a, x/b/c/d/b. Veja o que diff a xlhe dá.
Jobin
2
Você tem que usar a -ropção Isso ( diff -r a x) me dá:Only in a/b/c/d: a. only in x/b/c/d: b.
Alex R.
3
diff mostra-me a diferença nos arquivos, mas não se um diretório contiver um arquivo que o outro não contenha !!! Eu não preciso saber as diferenças em arquivo, mas também se um arquivo existe em um diretório e não no outro
AndreaNobili
25

Como você não está usando o bash, você pode fazê-lo usando o diff com --briefe --recursive:

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

O man diffinclui as duas opções:

-q, --brief
relate apenas quando os arquivos diferirem

-r, --recursive
compare recursivamente todos os subdiretórios encontrados

Braiam
fonte
13

Aqui está uma alternativa, para comparar apenas nomes de arquivos, e não seu conteúdo:

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

Essa é uma maneira fácil de listar arquivos ausentes, mas é claro que não detectará arquivos com o mesmo nome, mas com conteúdos diferentes!

(Pessoalmente, uso meu próprio diffdirsscript, mas isso faz parte de uma biblioteca maior .)

joeytwiddle
fonte
3
É melhor você usar a substituição do processo, não os arquivos temporários ...
mniip
3
Observe que isso não suporta nomes de arquivos com certos caracteres especiais; nesse caso, convém usar delimitadores zero aos quais o AFAIK diffnão oferece suporte a partir de agora. Mas há commsuporte para isso desde git.savannah.gnu.org/cgit/coreutils.git/commit/… então, quando se trata de um coreutils perto de você, você pode fazer comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(cuja saída você pode precisar converter ainda mais no formato você precisa usar o --output-delimiterparâmetro e ferramentas adicionais).
Php5
8

Talvez uma opção seja executar o rsync duas vezes:

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

Com a linha anterior, você obterá arquivos que estão no dir1 e são diferentes (ou ausentes) no dir2.

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

O mesmo 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

Você pode excluir a -nopção de sofrer as alterações. Isso está copiando a lista de arquivos para a segunda pasta.

Caso você faça isso, talvez seja uma boa opção -u, para evitar a substituição de arquivos mais recentes.

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

Uma linha:

rsync -rtvcsOu -n --progress /dir1/ /dir2/ && rsync -rtvcsOu -n --progress /dir2/ /dir1/
Ferroao
fonte
3

Se você deseja tornar cada arquivo expansível e recolhível, você pode canalizar a saída diff -rpara o Vim.

Primeiro vamos dar ao Vim uma regra de dobragem:

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

Agora apenas:

diff -r dir1 dir2 | vim -

Você pode pressionar zoe zcabrir e fechar dobras. Para sair do Vim, pressione:q<Enter>

joeytwiddle
fonte
3

Tarefa bastante fácil de realizar em 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

Substitua valores reais por DIR1e DIR2.

Aqui está um exemplo de execução:

$ 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 a leitura, aqui está um script real em vez de uma linha:

#!/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
fonte
2
Observe que o os.listdirnão fornece nenhuma ordem específica. Portanto, as listas podem ter as mesmas coisas em ordem diferente e a comparação falhará.
Muru
1
@muru bom ponto, vou incluir a classificação para isso #
Sergiy Kolodyazhnyy 14/11/16
3

Inspirado pela resposta de Sergiy, escrevi meu próprio script Python para comparar dois diretórios.

Ao contrário de muitas outras soluções, ele não compara o conteúdo dos arquivos. Também não entra em subdiretórios ausentes em um dos diretórios. Portanto, a saída é bastante concisa e o script funciona rapidamente com diretórios 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])

Se você o salvar em um arquivo chamado compare_dirs.py, poderá executá-lo com o Python3.x:

python3 compare_dirs.py dir1 dir2

Saída de amostra:

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 você precisar comparar tamanhos de arquivo e hashes de arquivo para possíveis alterações, publiquei um script atualizado aqui: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779

Andriy Makukha
fonte
1
Obrigado, eu adicionei um regexp terceiro param opcional para pular / ignore gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684 para fazer apenas o que eu precisava, como:cmpdirs dir1 dir2 '/\.git/'
Mike
0

Vou adicionar a esta lista uma alternativa dos NodeJs que escrevi há algum tempo.

dir-compare

npm install dir-compare -g
dircompare dir1 dir2
gliviu
fonte
0

Gostaria de sugerir uma ótima ferramenta que acabei de descobrir: MELD .

Funciona corretamente e tudo o que você pode fazer com o comando diffno sistema baseado em Linux pode ser replicado com uma boa interface gráfica! Desfrutar

Leos313
fonte