Como comparar dois arquivos

82

Então, basicamente, o que eu quero fazer é comparar dois arquivos por linha pela coluna 2. Como eu poderia fazer isso?

Arquivo_1.txt:

User1 US
User2 US
User3 US

Arquivo_2.txt:

User1 US
User2 US
User3 NG

Arquivo de saída:

User3 has changed
Roboman1723
fonte
11
Usediff "File_1.txt" "File_2.txt"
Pandya
Também visite: askubuntu.com/q/12473
Pandya

Respostas:

91

Olhe para o diffcomando. É uma boa ferramenta, e você pode ler tudo sobre isso digitando man diffno seu terminal.

O comando que você deseja executar é o diff File_1.txt File_2.txtque produzirá a diferença entre os dois e deve ser algo como isto:

insira a descrição da imagem aqui

Uma observação rápida sobre a leitura da saída do terceiro comando: As 'setas' ( <e >) referem-se ao valor da linha no arquivo esquerdo ( <) versus o arquivo direito ( >), sendo o arquivo esquerdo o que você digitou primeiro na linha de comando, neste casoFile_1.txt

Além disso, você pode notar que o quarto comando é diff ... | tee Output_Filecanalizar os resultados de diffpara em um tee, que coloca a saída em um arquivo, para que você possa salvá-lo para mais tarde, se não quiser ver tudo no console naquele segundo.

Mitch
fonte
Isso pode fazer outros arquivos (como imagens)? Ou é limitado apenas a documentos?
Gregory Opera
2
Tanto quanto eu sei, é limitado a arquivos de texto. O código funcionará, pois é essencialmente texto, mas todos os arquivos binários (quais são as imagens) serão apenas descartados. Você pode comparar para ver se eles são idênticos fazendo: diff file1 file2 -s. Aqui está um exemplo: imgur.com/ShrQx9x
Mitch
Existe uma maneira de colorir a saída? Gostaria de mantê-lo somente para CLI, mas com um pouco mais de ... toque humano.
Lazar Ljubenović
36

Ou você pode usar Meld Diff

O Meld ajuda a comparar arquivos, diretórios e projetos controlados por versão. Ele fornece uma comparação bidirecional e tripla de arquivos e diretórios, e suporta muitos sistemas populares de controle de versão.

Instale executando:

sudo apt-get install meld

Seu exemplo:

insira a descrição da imagem aqui

Comparar diretório:

insira a descrição da imagem aqui

Exemplo com texto completo:

insira a descrição da imagem aqui

Achu
fonte
18

Você pode usar o vimdiff .

Exemplo:

vimdiff  file1  file2
Sra
fonte
1
este tem cores
Jake Toronto
Isso me ajudou, pois mostrava o final da linha do meu primeiro arquivo dose o segundo unix.
LoMaPh
13

FWIW, eu gosto bastante do que recebo com a saída lado a lado do diff

diff -y -W 120 File_1.txt File_2.txt

daria algo como:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
Mike Reardon
fonte
10

Você pode usar o comando cmp:

cmp -b "File_1.txt" "File_2.txt"

saída seria

a b differ: byte 25, line 3 is 125 U 116 N
Maythux
fonte
cmpé muito mais rápido do que diffse tudo o que você deseja é o código de retorno.
stevesliva 03/10
8

Meldé realmente uma ótima ferramenta. Mas você também pode usar diffusepara comparar visualmente dois arquivos:

diffuse file1.txt file2.txt

insira a descrição da imagem aqui

Meysam
fonte
7

Permanecendo fielmente à pergunta (arquivo1, arquivo2, arquivo de saída com a mensagem "mudou"), o script abaixo funciona.

Copie o script em um arquivo vazio, salve-o como compare.py, torne-o executável, execute-o pelo comando:

/path/to/compare.py <file1> <file2> <outputfile>

O script:

#!/usr/bin/env python

import sys
file1 = sys.argv[1]; file2 = sys.argv[2]; outfile = sys.argv[3]

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

with open(outfile, "wt") as out:
    for line in mismatch:
        out.write(line+" has changed"+"\n")

Com algumas linhas extras, você pode imprimir em um arquivo de saída ou no terminal, dependendo se o arquivo de saída estiver definido:

Para imprimir em um arquivo:

/path/to/compare.py <file1> <file2> <outputfile>

Para imprimir na janela do terminal:

/path/to/compare.py <file1> <file2> 

O script:

#!/usr/bin/env python

import sys

file1 = sys.argv[1]; file2 = sys.argv[2]
try:
    outfile = sys.argv[3]
except IndexError:
    outfile = None

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

if outfile != None:
        with open(outfile, "wt") as out:
            for line in mismatch:
                out.write(line+" has changed"+"\n")
else:
    for line in mismatch:
        print line+" has changed"
Jacob Vlijm
fonte
4

Uma maneira fácil é usar colordiff, que se comporta como, diffmas coloriza sua saída. Isso é muito útil para a leitura de diferenças. Usando seu exemplo,

$ colordiff -u File_1.txt File_2.txt
--- File_1.txt  2016-12-24 17:59:17.409490554 -0500
+++ File_2.txt  2016-12-24 18:00:06.666719659 -0500
@@ -1,3 +1,3 @@
 User1 US
 User2 US
-User3 US
+User3 NG

onde a uopção fornece um diff unificado. É assim que o diff colorido se parece:

insira a descrição da imagem aqui

Instale colordiffexecutando sudo apt-get install colordiff.

edwinksl
fonte
1
Se você quiser cores que eu encontrar o diff incorporado vim para ser realmente fácil de usar, como na resposta por Mr.S
thomasrutter
2

Resposta adicional

Se não for necessário saber quais partes dos arquivos diferem, você pode usar a soma de verificação do arquivo. Há muitas maneiras de fazer isso, usando md5sumou sha256sum. Basicamente, cada um deles gera uma sequência na qual o arquivo contém um hash. Se os dois arquivos forem iguais, o hash também será o mesmo. Isso geralmente é usado quando você baixa software, como imagens iso de instalação do Ubuntu. Eles geralmente são usados ​​para verificar a integridade de um conteúdo baixado.

Considere o script abaixo, onde você pode fornecer dois arquivos como argumentos, e o arquivo informará se eles são iguais ou não.

#!/bin/bash

# Check if both files exist  
if ! [ -e "$1"  ];
then
    printf "%s doesn't exist\n" "$1"
    exit 2
elif ! [ -e "$2" ]
then
    printf "%s doesn't exist\n" "$2"
    exit 2
fi

# Get checksums of eithe file
file1_sha=$( sha256sum "$1" | awk '{print $1}')
file2_sha=$( sha256sum "$2" | awk '{print $1}')

# Compare the checksums
if [ "x$file1_sha" = "x$file2_sha" ]
then
    printf "Files %s and %s are the same\n" "$1" "$2"
    exit 0
else
    printf "Files %s and %s are different\n" "$1" "$2"
    exit 1
fi

Exemplo de execução:

$ ./compare_files.sh /etc/passwd ./passwd_copy.txt                                                                
Files /etc/passwd and ./passwd_copy.txt are the same
$ echo $?
0
$ ./compare_files.sh /etc/passwd /etc/default/grub                                                                
Files /etc/passwd and /etc/default/grub are different
$ echo $?
1

Resposta mais antiga

Além disso, existe um commcomando que compara dois arquivos classificados e fornece saída em três colunas: coluna 1 para itens exclusivos para o arquivo nº 1, coluna 2 para itens exclusivos para o arquivo nº 2 e coluna 3 para itens presentes nos dois arquivos.

Para suprimir qualquer coluna, você pode usar as opções -1, -2 e -3. Usar -3 mostrará as linhas que diferem.

Abaixo, você pode ver a captura de tela do comando em ação.

insira a descrição da imagem aqui

Há apenas um requisito - os arquivos devem ser classificados para serem comparados corretamente. sortcomando pode ser usado para esse fim. Abaixo está outra captura de tela, na qual os arquivos são classificados e depois comparados. As linhas começando à esquerda entre o arquivo_1 e as linhas iniciando na coluna 2 pertencem apenas ao arquivo_2

insira a descrição da imagem aqui

Sergiy Kolodyazhnyy
fonte
@DavidFoerster, é meio difícil editar no celular :) Feito agora, apesar de tudo
Sergiy Kolodyazhnyy
2

Instale o git e use

$ git diff filename1 filename2

E você obterá saída em bom formato colorido

Instalação do Git

$ apt-get update
$ apt-get install git-core
Eric Korolev
fonte
2

colcmp.sh

Compara pares de nome / valor em 2 arquivos no formato name value\n. Grava o namepara Output_filese alterado. Requer bash v4 + para matrizes associativas .

Uso

$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2

Arquivo de saída

$ cat Output_File
User3 has changed

Origem (colcmp.sh)

cmp -s "$1" "$2"
case "$?" in
    0)
        echo "" > Output_File
        echo "files are identical"
        ;;
    1)
        echo "" > Output_File
        cp "$1" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.array1.tmp.sh
        chmod 755 ~/.colcmp.array1.tmp.sh
        declare -A A1
        source ~/.colcmp.array1.tmp.sh

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

        USERSWHODIDNOTCHANGE=
        for i in "${!A1[@]}"; do
            if [ "${A2[$i]+x}" = "" ]; then
                echo "$i was removed"
                echo "$i has changed" > Output_File
            fi
        done
        for i in "${!A2[@]}"; do
            if [ "${A1[$i]+x}" = "" ]; then
                echo "$i was added as '${A2[$i]}'"
                echo "$i has changed" > Output_File
            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
                echo "$i changed from '${A1[$i]}' to '${A2[$i]}'"
                echo "$i has changed" > Output_File
            else
                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
            fi
        done
        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
        ;;
    *)
        echo "error: file not found, access denied, etc..."
        echo "usage: ./colcmp.sh File_1.txt File_2.txt"
        ;;
esac

Explicação

Repartição do código e o que isso significa, da melhor forma possível. Congratulo-me com edições e sugestões.

Comparação básica de arquivos

cmp -s "$1" "$2"
case "$?" in
    0)
        # match
        ;;
    1)
        # compare
        ;;
    *)
        # error
        ;;
esac

O cmp definirá o valor de $? da seguinte maneira :

  • 0 = correspondência de arquivos
  • 1 = arquivos diferem
  • 2 = erro

Eu escolhi usar um caso .. instrução esac para avaliar $? porque o valor de $? muda após cada comando, incluindo test ([).

Alternativamente, eu poderia ter usado uma variável para armazenar o valor de $? :

cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
    # match
elif [ $CMPRESULT -eq 1 ]; then
    # compare
else
    # error
fi

Acima faz o mesmo que a declaração do caso. IDK que eu gosto mais.

Limpe a saída

        echo "" > Output_File

Acima limpa o arquivo de saída, portanto, se nenhum usuário for alterado, o arquivo de saída estará vazio.

Eu faço isso dentro das instruções case para que o Output_file permaneça inalterado por erro.

Copiar arquivo do usuário para o shell script

        cp "$1" ~/.colcmp.arrays.tmp.sh

Acima, copia o arquivo_1.txt para o diretório inicial do usuário atual.

Por exemplo, se o usuário atual for john, o item acima seria o mesmo que cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Escapar caracteres especiais

Basicamente, sou paranóico. Eu sei que esses caracteres podem ter um significado especial ou executar um programa externo quando executados em um script como parte da atribuição de variável:

  • `- back-tick - executa um programa e a saída como se a saída fizesse parte do seu script
  • $ - cifrão - geralmente prefixa uma variável
  • $ {} - permite uma substituição de variável mais complexa
  • $ () - idk o que isso faz, mas acho que ele pode executar código

O que não sei é o quanto não sei sobre o bash. Não sei que outros personagens podem ter um significado especial, mas quero escapar de todos eles com uma barra invertida:

        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh

O sed pode fazer muito mais do que a correspondência de padrões de expressão regular . O padrão de script "s / (localizar) / (substituir) /" executa especificamente a correspondência de padrões.

"s / (localizar) / (substituir) / (modificadores)"

em inglês: capture qualquer pontuação ou caractere especial como grupo de captura 1 (\\ 1)

  • (substituir) = \\\\\\ 1
    • \\\\ = caractere literal (\\) ou seja, uma barra invertida
    • \\ 1 = grupo de captura 1

em inglês: prefixe todos os caracteres especiais com uma barra invertida

  • (modificadores) = g
    • g = substituir globalmente

em inglês: se mais de uma correspondência for encontrada na mesma linha, substitua-as todas

Comentar o script inteiro

        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh

Acima usa uma expressão regular para prefixar todas as linhas de ~ / .colcmp.arrays.tmp.sh com um caractere de comentário básico ( # ). Faço isso porque mais tarde pretendo executar ~ / .colcmp.arrays.tmp.sh usando o comando source e porque não sei ao certo todo o formato do arquivo_1.txt .

Não quero executar acidentalmente código arbitrário. Eu acho que ninguém faz.

"s / (localizar) / (substituir) /"

em inglês: capture cada linha como grupo de captura 1 (\\ 1)

  • (substituir) = # \\ 1
    • # = caractere literal (#) ou seja, um símbolo de libra ou hash
    • \\ 1 = grupo de captura 1

em inglês: substitua cada linha por um símbolo de libra seguido pela linha que foi substituída

Converter valor do usuário em A1 [Usuário] = "valor"

        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh

Acima está o núcleo deste script.

  • converta isso: #User1 US
    • para isso: A1[User1]="US"
    • ou este: A2[User1]="US"(para o 2º arquivo)

"s / (localizar) / (substituir) /"

em inglês:

  • requer, mas ignora, os principais caracteres de comentário (#)
  • ignorar espaços em branco à esquerda
  • capturar a primeira palavra como grupo de captura 1 (\\ 1)
  • requer um espaço (ou tabulação ou espaço em branco)
    • que será substituído por um sinal de igual porque
    • não faz parte de nenhum grupo de captura e porque
    • o padrão (substituir) coloca um sinal de igual entre o grupo de captura 1 e o grupo de captura 2
  • capture o restante da linha como grupo de captura 2

  • (substituir) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"

    • A1 \\ [- caracteres literais A1[para iniciar a atribuição da matriz em uma matriz chamadaA1
    • \\ 1 = grupo de captura 1 - que não inclui o hash inicial (#) e não inclui o espaço em branco inicial - nesse caso, o grupo de captura 1 está sendo usado para definir o nome do par nome / valor na matriz associativa do bash.
    • \\] = \ "= caracteres literais ]="
      • ]= atribuição de matriz fechada, por exemplo, A1[Usuário1 ]="US"
      • = = operador de atribuição, por exemplo, variável = valor
      • " = quote quote para capturar espaços ... embora agora que eu pense sobre isso, teria sido mais fácil deixar o código acima que inverte tudo para também inverter caracteres de espaço.
    • \\ 1 = grupo de captura 2 - nesse caso, o valor do par nome / valor
    • "= valor de cotação de fechamento para capturar espaços

em inglês: substitua cada linha no formato #name valuepor um operador de atribuição de matriz no formatoA1[name]="value"

Tornar executável

        chmod 755 ~/.colcmp.arrays.tmp.sh

Acima, usa chmod para tornar o arquivo de script da matriz executável.

Não tenho certeza se isso é necessário.

Declarar matriz associativa (bash v4 +)

        declare -A A1

O capital -A indica que as variáveis ​​declaradas serão matrizes associativas .

É por isso que o script requer o bash v4 ou superior.

Executar nosso script de atribuição de variável de matriz

        source ~/.colcmp.arrays.tmp.sh

Nós já temos:

  • convertemos nosso arquivo de linhas de User valuepara linhas de A1[User]="value",
  • tornou executável (talvez) e
  • declarou A1 como uma matriz associativa ...

Acima, fornecemos o script para executá-lo no shell atual. Fazemos isso para manter os valores das variáveis ​​que são definidos pelo script. Se você executar o script diretamente, ele gera um novo shell, e os valores das variáveis ​​são perdidos quando o novo shell sai, ou pelo menos esse é o meu entendimento.

Isso deve ser uma função

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

Fazemos a mesma coisa por US $ 1 e A1 que fazemos por US $ 2 e A2 . Realmente deveria ser uma função. Eu acho que neste momento esse script é bastante confuso e funciona, então não vou corrigi-lo.

Detectar usuários removidos

        for i in "${!A1[@]}"; do
            # check for users removed
        done

Loops acima através de chaves de matriz associativas

            if [ "${A2[$i]+x}" = "" ]; then

Acima, usa a substituição de variável para detectar a diferença entre um valor não definido e uma variável que foi explicitamente definida como uma cadeia de comprimento zero.

Aparentemente, existem várias maneiras de verificar se uma variável foi definida . Eu escolhi aquele com mais votos.

                echo "$i has changed" > Output_File

Acima adiciona o usuário $ i ao Output_File

Detectar usuários adicionados ou alterados

        USERSWHODIDNOTCHANGE=

Acima limpa uma variável para que possamos rastrear os usuários que não foram alterados.

        for i in "${!A2[@]}"; do
            # detect users added, changed and not changed
        done

Loops acima através de chaves de matriz associativas

            if ! [ "${A1[$i]+x}" != "" ]; then

Acima usa a substituição de variável para verificar se uma variável foi configurada .

                echo "$i was added as '${A2[$i]}'"

Como $ i é a chave da matriz (nome do usuário), $ A2 [$ i] deve retornar o valor associado ao usuário atual de File_2.txt .

Por exemplo, se $ i for Usuário1 , o texto acima será lido como $ {A2 [Usuário1]}

                echo "$i has changed" > Output_File

Acima adiciona o usuário $ i ao Output_File

            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then

Como $ i é a chave da matriz (nome do usuário), $ A1 [$ i] deve retornar o valor associado ao usuário atual do File_1.txt e $ A2 [$ i] deve retornar o valor do File_2.txt .

Acima, compara os valores associados ao usuário $ i dos dois arquivos.

                echo "$i has changed" > Output_File

Acima adiciona o usuário $ i ao Output_File

                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"

Acima cria uma lista separada por vírgula de usuários que não foram alterados. Observe que não há espaços na lista; caso contrário, a próxima verificação precisará ser citada.

        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi

Acima, o relatório informa o valor de $ USERSWHODIDNOTCHANGE, mas apenas se houver um valor em $ USERSWHODIDNOTCHANGE . Da maneira como está escrito, $ USERSWHODIDNOTCHANGE não pode conter espaços. Se precisar de espaços, acima pode ser reescrito da seguinte maneira:

        if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
Jonathan
fonte