come usare patch e diff per unire due file e risolvere automaticamente i conflitti


19

Ho letto di diff e patch ma non riesco a capire come applicare ciò di cui ho bisogno. Immagino sia piuttosto semplice, quindi per mostrare il mio problema prendi questi due file:

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>

Voglio avere un output, che assomigli a questo (l'ordine non ha importanza):

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

L'unione dovrebbe contenere tutte le righe lungo queste semplici regole:

  1. qualsiasi riga che si trova solo in uno dei file
  2. se una riga ha lo stesso tag di nome ma un valore diverso, prendi il valore dal secondo

Voglio applicare questo compito all'interno di uno script bash, quindi non deve necessariamente essere fatto con diff e patch, se un altro programma è più adatto


diffpuò dirti quali righe si trovano in un file ma non nell'altro, ma solo sulla granularità di intere righe. patchè adatto solo per apportare le stesse modifiche a un file simile (forse una versione diversa dello stesso file o un file completamente diverso in cui, tuttavia, i numeri di riga e le righe circostanti per ogni modifica sono identici al file originale). Quindi no, non sono particolarmente adatti per questo compito. Potresti dare un'occhiata wdiffma la soluzione probabilmente richiede uno script personalizzato. Dato che i tuoi dati sembrano XML, potresti voler cercare alcuni strumenti XSL.
Tripleee

1
Perché tutte le risposte con script personalizzati? La fusione è un problema standard e complesso e ci sono buoni strumenti per farlo. Non reinventare la ruota.
alexis,

Risposte:


23

Non è necessario patchper questo; serve per estrarre le modifiche e inviarle senza la parte invariata del file.

Lo strumento per unire due versioni di un file è merge, ma come @vonbrandscritto, è necessario il file "base" da cui le due versioni differivano. Per eseguire un'unione senza di essa, utilizzare in diffquesto modo:

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

Includerà ogni serie di modifiche nei comandi in stile C #ifdef/ #ifndef"preprocessore", in questo modo:

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

Se una linea o una regione differisce tra i due file, otterrai un "conflitto", che assomiglia a questo:

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

Quindi salva l'output in un file e aprilo in un editor. Cerca i luoghi in cui #elsesi presenta e risolvili manualmente. Quindi salva il file ed eseguilo grep -vper eliminare le righe #if(n)defe le #endifrighe rimanenti :

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

In futuro, salva la versione originale del file. mergepuò darti risultati molto migliori con l'aiuto delle informazioni extra. (Ma fai attenzione: mergemodifica uno dei file sul posto, a meno che tu non lo usi -p. Leggi il manuale).


Ho aggiunto qualcosa per se avessi avuto un conflittosed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr il

1
Non penso sia una buona idea. Come ho scritto nella mia risposta, dovresti rimuovere le #elserighe manualmente, nell'editor durante la risoluzione dei conflitti.
alexis,

6

merge(1) è probabilmente più vicino a quello che vuoi, ma ciò richiede un antenato comune ai tuoi due file.

Un modo (sporco!) Di farlo è:

  1. Sbarazzati della prima e dell'ultima riga, usa grep(1)per escluderle
  2. Distruggi i risultati insieme
  3. sort -u lascia un elenco ordinato, elimina i duplicati
  4. Sostituisci prima / ultima riga

Humm ... qualcosa del genere:

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

potrebbe fare.


funziona in questo esempio particolare, ma NON in generale: se il name in_b_but_different_valvalore ha un #00AABBtipo di ordinamento, questo verrà messo in primo piano e cancella il secondo valore anziché il primo
Rafael T

per la soluzione ottimale in questo caso dovresti analizzare l'XML, con un vero parser XML non gli hack di cui sopra, e produrre un nuovo output XML unito da quello. diff / patch / sort ecc. sono solo tutti gli hack su misura per "esempi particolari", per una soluzione generale sono semplicemente gli strumenti sbagliati
frostschutz,

@alzheimer, prepara qualcosa di semplice per mostrarci ...
vonbrand,

Apparentemente diff3funziona allo stesso modo. Richiede un file antenato comune. Perché non esiste un semplice strumento CLI che unisce solo 2 file in base a ciò che diffmostra.
CMCDragonkai,

5

sdiff (1) - unione fianco a fianco delle differenze tra i file

Utilizzare l' --outputopzione, questo unirà in modo interattivo due file qualsiasi. Utilizzare semplici comandi per selezionare una modifica o modificare una modifica.

È necessario assicurarsi che la EDITORvariabile di ambiente sia impostata. L'editor predefinito per comandi come "eb" è in genere edun editor di linee .

EDITOR=nano sdiff -o merged.txt file1.txt file2.txt

1
Trovo che utilizzare vimcome EDITOR meglio. Ma questa è la soluzione migliore, viene fornita anche con il diffcomando!
CMCDragonkai,

1

Ecco una semplice soluzione che funziona unendo fino a 10 file :

#!/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>
'

si prega di notare che l'arg che viene prima ha la precedenza, quindi è necessario chiamare:

script b.xml a.xml

per ottenere valori comuni da cui b.xmlanziché a.xml.

script b.xml a.xml out:

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

1

Un altro hack orribile - potrebbe essere semplificato, ma: 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>"

0

OK, secondo tentativo, ora in Perl ( non qualità di produzione, nessun controllo!):

#!/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";

0

Un altro, usando cut e grep ... (prende a.xml b.xml come argomenti)

#!/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>"

echoè l'azione predefinita, quindi xargs echoè superflua. Perché non lo fai semplicemente tr '\n' '|'?
Tripleee

Un buon punto: è solo un trucco rapido. Lo modificherò.
frostschutz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.