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
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
Respostas:
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:

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.
diff file1 file2 -s. Aqui está um exemplo: imgur.com/ShrQx9x
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:

Comparar diretório:

Exemplo com texto completo:

dose o segundo unix.
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
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
cmpé muito mais rápido do que diffse tudo o que você deseja é o código de retorno.
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"
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:
Instale colordiffexecutando sudo apt-get install colordiff.
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
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.

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

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
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 .
$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2
$ cat Output_File
User3 has changed
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
Repartição do código e o que isso significa, da melhor forma possível. Congratulo-me com edições e sugestões.
cmp -s "$1" "$2"
case "$?" in
0)
# match
;;
1)
# compare
;;
*)
# error
;;
esac
O cmp definirá o valor de $? da seguinte maneira :
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.
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.
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
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:
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)
em inglês: prefixe todos os caracteres especiais com uma barra invertida
em inglês: se mais de uma correspondência for encontrada na mesma linha, substitua-as todas
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)
em inglês: substitua cada linha por um símbolo de libra seguido pela linha que foi substituída
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh
Acima está o núcleo deste script.
#User1 US
A1[User1]="US"A2[User1]="US"(para o 2º arquivo)"s / (localizar) / (substituir) /"
em inglês:
capture o restante da linha como grupo de captura 2
(substituir) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"
A1[para iniciar a atribuição da matriz em uma matriz chamadaA1]="
]= 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.em inglês: substitua cada linha no formato #name valuepor um operador de atribuição de matriz no formatoA1[name]="value"
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.
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.
source ~/.colcmp.arrays.tmp.sh
Nós já temos:
User valuepara linhas de A1[User]="value",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.
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.
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
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
diff "File_1.txt" "File_2.txt"