como usar patch e diff para mesclar dois arquivos e resolver conflitos automaticamente

19

Eu li sobre diff e patch, mas não consigo descobrir como aplicar o que preciso. Eu acho que é bem simples, então, para mostrar meu problema, pegue estes dois arquivos:

a.xml

<resources>
   <color name="same_in_b">#AAABBB</color>
   <color name="not_in_b">#AAAAAA</color>
   <color name="in_b_but_different_val">#AAAAAA</color>
   <color name="not_in_b_too">#AAAAAA</color>
</resources>

b.xml

<resources>
   <color name="same_in_b">#AAABBB</color>
   <color name="in_b_but_different_val">#BBBBBB</color>
   <color name="not_in_a">#AAAAAA</color>
</resources>

Eu quero ter uma saída, que se parece com isso (ordem não importa):

<resources>
   <color name="same_in_b">#AAABBB</color>
   <color name="not_in_b">#AAAAAA</color>
   <color name="in_b_but_different_val">#BBBBBB</color>
   <color name="not_in_b_too">#AAAAAA</color>
   <color name="not_in_a">#AAAAAA</color>
</resources>

A mesclagem deve conter todas as linhas ao longo destas regras simples:

  1. qualquer linha que esteja apenas em um dos arquivos
  2. se uma linha tiver o mesmo nome, mas um valor diferente, use o valor do segundo

Eu quero aplicar esta tarefa dentro de um script bash, para que não seja necessário fazer nessesariamente o diff e o patch, se outro programa for mais adequado

Rafael T
fonte
diffpode dizer quais linhas estão em um arquivo, mas não no outro, mas apenas na granularidade de linhas inteiras. patché adequado apenas para fazer as mesmas alterações em um arquivo semelhante (talvez uma versão diferente do mesmo arquivo ou um arquivo totalmente diferente, no entanto, os números das linhas e as linhas adjacentes de cada alteração são idênticas ao seu arquivo original). Portanto, não, eles não são particularmente adequados para esta tarefa. Você pode querer dar uma olhada, wdiffmas a solução provavelmente requer um script personalizado. Como seus dados se parecem com XML, convém procurar alguma ferramenta XSL.
Tripleee
1
Por que todas as respostas com scripts personalizados? A fusão é um problema padrão e complexo, e existem boas ferramentas para isso. Não reinvente a roda.
Alexis

Respostas:

23

Você não precisa patchdisso; é para extrair alterações e enviá-las sem a parte inalterada do arquivo.

A ferramenta para mesclar duas versões de um arquivo é merge, mas conforme foi @vonbrandescrito, você precisa do arquivo "base" do qual suas duas versões divergiram. Para fazer uma mesclagem sem ela, use o diffseguinte:

diff -DVERSION1 file1.xml file2.xml > merged.xml

Ele incluirá cada conjunto de alterações nos comandos de estilo C #ifdef/ #ifndef"pré-processador", assim:

#ifdef VERSION1
<stuff added to file1.xml>
#endif
...
#ifndef VERSION1
<stuff added to file2.xml>
#endif

Se uma linha ou região diferir entre os dois arquivos, você receberá um "conflito", que se parece com isso:

#ifndef VERSION1
<version 1>
#else /* VERSION1 */
<version 2>
#endif /* VERSION1 */

Portanto, salve a saída em um arquivo e abra-a em um editor. Pesquise os locais que #elseaparecerem e resolva-os manualmente. Em seguida, salve o arquivo e execute-o grep -vpara se livrar do restante #if(n)defe das #endiflinhas:

grep -v '^#if' merged.xml | grep -v '^#endif' > clean.xml

No futuro, salve a versão original do arquivo. mergepode fornecer resultados muito melhores com a ajuda de informações extras. (Mas tenha cuidado: mergeedita um dos arquivos no local, a menos que você o utilize -p. Leia o manual).

alexis
fonte
Acrescentei algo para se eu tivesse um conflitosed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr
1
Não acho que seja uma boa ideia. Como escrevi na minha resposta, você deve remover as #elselinhas manualmente, no editor durante a resolução de conflitos.
Alexis
6

merge(1) provavelmente está mais próximo do que você deseja, mas isso requer um ancestral comum para seus dois arquivos.

Uma maneira (suja!) De fazer isso é:

  1. Livre-se da primeira e da última linha, use-as grep(1)para excluí-las
  2. Esmagar os resultados juntos
  3. sort -u sai de uma lista classificada, elimina duplicatas
  4. Substituir primeira / última linha

Humm ... algo do tipo:

echo '<resources>'; grep -v resources file1 file2 | sort -u; echo '</resources>'

pode fazer.

vonbrand
fonte
funciona neste exemplo particular, mas não em geral: Se o name in_b_but_different_valtem um valor de #00AABBtipo vai colocar isso em cima e apaga o segundo valor, em vez do primeiro
Rafael T
para a solução ideal nesse caso, você teria que analisar o XML, com um analisador de XML real, não os hacks acima, e produzir uma nova saída XML mesclada a partir disso. diff / patch / tipo etc. são apenas todos os hacks sob medida para "exemplos particulares", para uma solução geral eles são simplesmente as ferramentas erradas
frostschutz
@alzheimer, chicote simples algo para nos mostrar ...
vonbrand
Aparentemente, diff3funciona da mesma maneira. Exigindo um arquivo ancestral comum. Por que não existe uma ferramenta CLI simples que apenas mescla 2 arquivos com base no que é diffmostrado?
CMCDragonkai
5

sdiff (1) - mesclagem lado a lado das diferenças de arquivo

Use a --outputopção, isso mesclará interativamente quaisquer dois arquivos. Você usa comandos simples para selecionar uma alteração ou editar uma alteração.

Você deve se certificar de que a EDITORvariável de ambiente esteja definida. O editor padrão para comandos como "eb" é geralmente edum editor de linha .

EDITOR=nano sdiff -o merged.txt file1.txt file2.txt
Cody Allan Taylor
fonte
1
Acho que usar vimcomo o EDITOR é melhor. Mas esta é a melhor solução, também vem com o diffcomando!
CMCDragonkai
1

Aqui está uma solução simples que funciona mesclando até 10 arquivos :

#!/bin/bash

strip(){
    i=0
    for f; do
        sed -r '
            /<\/?resources>/ d
            s/>/>'$((i++))'/
        ' "$f"
    done
}

strip "$@" | sort -u -k1,1 -t'>' | sed '
    1 s|^|<resources>\n|
    s/>[0-9]/>/
    $ a </resources>
'

observe que o argumento que vem primeiro tem precedência, então você deve ligar para:

script b.xml a.xml

para obter valores comuns mantidos em b.xmlvez de a.xml.

script b.xml a.xml saídas:

<resources>
   <color name="in_b_but_different_val">#BBBBBB</color>
   <color name="not_in_a">#AAAAAA</color>
   <color name="not_in_b">#AAAAAA</color>
   <color name="not_in_b_too">#AAAAAA</color>
   <color name="same_in_b">#AAABBB</color>
</resources>
neurino
fonte
1

Outro truque horrível - poderia ser simplificado, mas: P

#!/bin/bash

i=0

while read line
do
    if [ "${line:0:13}" == '<color name="' ]
    then
        a_keys[$i]="${line:13}"
        a_keys[$i]="${a_keys[$i]%%\"*}"
        a_values[$i]="$line"
        i=$((i+1))
    fi
done < a.xml

i=0

while read line
do
    if [ "${line:0:13}" == '<color name="' ]
    then
        b_keys[$i]="${line:13}"
        b_keys[$i]="${b_keys[$i]%%\"*}"
        b_values[$i]="$line"
        i=$((i+1))
    fi
done < b.xml

echo "<resources>"

i=0

for akey in "${a_keys[@]}"
do
    print=1

    for bkey in "${b_keys[@]}"
    do
        if [ "$akey" == "$bkey" ]
        then
            print=0
            break
        fi
    done

    if [ $print == 1 ]
    then
        echo "  ${a_values[$i]}"
    fi

    i=$(($i+1))
done

for value in "${b_values[@]}"
do
    echo "  $value"
done

echo "</resources>"
frostschutz
fonte
0

OK, segunda tentativa, agora em Perl ( sem qualidade de produção, sem verificação!):

#!/usr/bin/perl

open(A, "a.xml");

while(<A>) {
  next if(m;^\<resource\>$;);
  next if(m;^\<\/resource\>$;);
  ($name, $value) = m;^\s*\<color\s+name\s*\=\s*\"([^"]+)\"\>([^<]+)\<\/color\>$;;
  $nv{$name} = $value if $name;
}

close(A);

open(B, "b.xml");

while(<B>) {
  next if(m;^\<resource\>$;);
  next if(m;^\<\/resource\>$;);
  ($name, $value) = m;^\s*\<color\s+name\s*\=\*\"([^"]+)\"\>([^<]+)\<\/color\>$;;
  $nv{$name} = $value if $name;
}

close(B);

print "<resource>\n";
foreach (keys(%nv)) {
    print "   <color name=\"$_\">$nv{$_}</color>\n";
}
print "</resource>\n";
vonbrand
fonte
0

Outro, usando cut e grep ... (usa a.xml b.xml como argumentos)

#!/bin/bash

zap='"('"`grep '<color' "$2" | cut -d '"' -f 2 | tr '\n' '|'`"'")'
echo "<resources>"
grep '<color' "$1" | grep -E -v "$zap"
grep '<color' "$2"
echo "</resources>"
frostschutz
fonte
echoé a ação padrão, portanto xargs echoé supérflua. Por que você simplesmente não é tr '\n' '|'assim?
Tripleee
Bom ponto - é apenas um truque rápido. Eu vou editar.
Frostschutz