colcmp.sh
Compara pares de nome / valor em 2 arquivos no formato name value\n
. Grava o name
para Output_file
se 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)"
- (encontrar) = ([^ A-Za-z0-9])
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) /"
- (encontrar) = ^ # \\ s * (\\ S +) \\ s + (\\ S. ?) \\ s \ $
em inglês:
em inglês: substitua cada linha no formato #name value
por 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 value
para 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
diff "File_1.txt" "File_2.txt"