Verwendung von Patch und Diff zum Zusammenführen von zwei Dateien und zum automatischen Lösen von Konflikten

19

Ich habe über Diff und Patch gelesen, kann aber nicht herausfinden, wie ich das, was ich brauche, anwenden soll. Ich denke, es ist ziemlich einfach, also nimm diese zwei Dateien, um mein Problem zu zeigen:

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>

Ich möchte eine Ausgabe haben, die so aussieht (Reihenfolge spielt keine Rolle):

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

Die Zusammenführung sollte alle Zeilen nach diesen einfachen Regeln enthalten:

  1. Jede Zeile, die sich nur in einer der Dateien befindet
  2. Wenn eine Zeile den gleichen Namen, aber einen anderen Wert hat, wird der Wert aus der zweiten übernommen

Ich möchte diese Aufgabe in einem Bash-Skript anwenden, damit es nicht unbedingt mit diff und patch fertig werden muss, wenn ein anderes Programm besser passt

Rafael T
quelle
diffkann Ihnen sagen, welche Zeilen sich in einer Datei befinden, aber nicht in der anderen, sondern nur in der Granularität ganzer Zeilen. patcheignet sich nur, um dieselben Änderungen an einer ähnlichen Datei vorzunehmen (möglicherweise eine andere Version derselben Datei oder eine völlig andere Datei, bei der jedoch die Zeilennummern und die umgebenden Zeilen für jede Änderung mit Ihrer Originaldatei identisch sind). Nein, sie sind für diese Aufgabe nicht besonders geeignet. Vielleicht möchten Sie einen Blick darauf werfen, wdiffaber für die Lösung ist wahrscheinlich ein benutzerdefiniertes Skript erforderlich. Da Ihre Daten wie XML aussehen, sollten Sie nach einem XSL-Tool suchen.
Tripleee
1
Warum alle Antworten mit benutzerdefinierten Skripten? Das Zusammenführen ist ein standardmäßiges und komplexes Problem, für das es gute Tools gibt. Das Rad nicht neu erfinden.
Alexis

Antworten:

23

Das brauchst du nicht patch; Es dient zum Extrahieren und Weiterleiten von Änderungen ohne den unveränderten Teil der Datei.

Das Tool zum Zusammenführen von zwei Versionen einer Datei ist merge, aber wie @vonbrandgeschrieben, benötigen Sie die "Basis" -Datei, von der Ihre beiden Versionen abweichen. Um eine Zusammenführung ohne sie durchzuführen, verwenden Sie diffFolgendes:

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

Es wird jede Reihe von Änderungen in C-Stil #ifdef/ #ifndef"Präprozessor" -Befehlen enthalten, wie folgt :

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

Wenn sich eine Linie oder ein Bereich zwischen den beiden Dateien unterscheidet, tritt ein "Konflikt" auf, der folgendermaßen aussieht:

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

Speichern Sie die Ausgabe in einer Datei und öffnen Sie sie in einem Editor. Suchen Sie nach Orten, an denen sie #elseauftauchen, und beheben Sie sie manuell. Speichern Sie dann die Datei und führen Sie sie aus grep -v, um die verbleibenden #if(n)defund #endifZeilen zu entfernen:

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

Speichern Sie in Zukunft die ursprüngliche Version der Datei. mergeMit Hilfe der zusätzlichen Informationen können Sie viel bessere Ergebnisse erzielen. (Aber seien Sie vorsichtig: mergeBearbeiten Sie eine der Dateien an Ort und Stelle, es sei denn, Sie verwenden sie -p. Lesen Sie das Handbuch.)

alexis
quelle
Ich fügte etwas für, wenn ich einen Konflikt hattesed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr
1
Das halte ich nicht für eine gute Idee. Wie ich in meiner Antwort schrieb, sollten Sie die #elseZeilen während der Konfliktlösung im Editor manuell entfernen .
Alexis
6

merge(1) ist wahrscheinlich näher an dem, was Sie wollen, aber das erfordert einen gemeinsamen Vorfahren für Ihre beiden Dateien.

Eine (schmutzige!) Art, es zu tun, ist:

  1. Werde die ersten und letzten Zeilen los und grep(1)schließe sie mit aus
  2. Zerstöre die Ergebnisse
  3. sort -u hinterlässt eine sortierte Liste, entfernt Duplikate
  4. Ersetzen Sie die erste / letzte Zeile

Humm ... etwas in der Art:

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

könnte tun.

vonbrand
quelle
funktioniert in diesem speziellen Beispiel, aber NICHT im Allgemeinen: Wenn der name in_b_but_different_valWert #00AABBsort lautet, wird der Wert darüber gesetzt und der zweite Wert anstelle des ersten gelöscht
Rafael T
Für die optimale Lösung in diesem Fall müssten Sie das XML mit einem echten XML-Parser analysieren, nicht mit den oben genannten Hacks, und daraus eine neue zusammengeführte XML-Ausgabe erstellen. diff / patch / sort usw. sind nur alle hacks, die auf "bestimmte beispiele" zugeschnitten sind, für eine allgemeine lösung sind sie einfach die falschen werkzeuge
frostschutz
@alzheimer, mach was einfaches, um es uns zu zeigen ...
vonbrand
Anscheinend diff3funktioniert das genauso. Gemeinsame Vorfahrendatei erforderlich. Warum gibt es kein einfaches CLI - Tool , das nur verschmilzt 2 Dateien zusammen auf , was diffzeigt.
CMCDragonkai
5

sdiff (1) - Nebeneinander Zusammenführen von Dateidifferenzen

Verwenden Sie die --outputOption, um zwei beliebige Dateien interaktiv zusammenzuführen. Sie verwenden einfache Befehle , um eine Änderung auszuwählen oder zu bearbeiten.

Sie sollten sicherstellen, dass die EDITORUmgebungsvariable festgelegt ist. Der Standardeditor für Befehle wie "eb" ist normalerweise edein Zeileneditor .

EDITOR=nano sdiff -o merged.txt file1.txt file2.txt
Cody Allan Taylor
quelle
1
Ich finde es vimals EDITOR besser zu gebrauchen. Dies ist jedoch die beste Lösung, die auch mit dem diffBefehl geliefert wird!
CMCDragonkai
1

Hier eine einfache Lösung, die das Zusammenführen von bis zu 10 Dateien ermöglicht :

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

Bitte beachten Sie, dass das zuerst kommende Argument Vorrang hat. Sie müssen also Folgendes anrufen:

script b.xml a.xml

um gemeinsame Werte zu vermeiden, b.xmlanstatt a.xml.

script b.xml a.xml outs:

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

Ein weiterer schrecklicher Hack - könnte vereinfacht werden, aber: 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
quelle
0

OK, zweiter Versuch, jetzt in Perl ( keine Produktionsqualität, keine Prüfung!):

#!/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
quelle
0

Ein anderes, mit cut und grep ... (nimmt a.xml b.xml als Argumente)

#!/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
quelle
echoist die Standardaktion, xargs echoist also überflüssig. Warum gehst du nicht einfach tr '\n' '|'sowieso?
Tripleee
Guter Punkt - es ist nur ein kurzer Hack. Ich werde es bearbeiten.
Frostschutz