jak używać patch i diff do łączenia dwóch plików i automatycznego rozwiązywania konfliktów

19

Czytałem o diff i łatce, ale nie mogę wymyślić, jak zastosować to, czego potrzebuję. Myślę, że to dość proste, więc aby pokazać mój problem, weź te dwa pliki:

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>

Chcę mieć wynik, który wygląda następująco (kolejność nie ma znaczenia):

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

Scalanie powinno zawierać wszystkie wiersze według tych prostych zasad:

  1. dowolny wiersz, który znajduje się tylko w jednym z plików
  2. jeśli wiersz ma tę samą plakietkę, ale inną wartość, należy pobrać wartość z drugiego

Chcę zastosować to zadanie w skrypcie bash, więc nie trzeba go koniecznie wykonywać przy użyciu diff i łatki, jeśli inny program lepiej pasuje

Rafael T.
źródło
diffmoże powiedzieć, które linie są w jednym pliku, ale nie w drugim, ale tylko na ziarnistości całych linii. patchnadaje się tylko do dokonywania tych samych zmian w podobnym pliku (być może innej wersji tego samego pliku lub zupełnie innym pliku, w którym jednak numery linii i otaczające linie dla każdej zmiany są identyczne z plikiem oryginalnym). Więc nie, nie są szczególnie odpowiednie do tego zadania. Możesz rzucić okiem, wdiffale rozwiązanie prawdopodobnie wymaga niestandardowego skryptu. Ponieważ twoje dane wyglądają jak XML, możesz poszukać jakiegoś narzędzia XSL.
tripleee
1
Dlaczego wszystkie odpowiedzi z niestandardowymi skryptami? Scalanie jest standardowym i złożonym problemem i istnieją na to dobre narzędzia. Nie wymyślaj koła na nowo.
Alexis

Odpowiedzi:

23

Nie potrzebujesz patchtego; służy do wyodrębniania zmian i wysyłania ich bez niezmienionej części pliku.

Narzędziem do scalania dwóch wersji pliku jest merge, ale jak @vonbrandnapisano, potrzebujesz pliku „podstawowego”, z którego rozdzieliły się twoje dwie wersje. Aby wykonać scalanie bez niego, użyj diffnastępującego:

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

Będzie zawierać każdy zestaw zmian w poleceniach w stylu C #ifdef/ #ifndef„preprocesorze”, takich jak:

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

Jeśli linia lub region różnią się między dwoma plikami, pojawi się „konflikt”, który wygląda następująco:

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

Zapisz wynik w pliku i otwórz go w edytorze. Wyszukaj wszystkie znalezione miejsca #elsei rozwiąż je ręcznie. Następnie zapisz plik i uruchom go, grep -vaby pozbyć się pozostałych #if(n)defi #endifwierszy:

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

W przyszłości zapisz oryginalną wersję pliku. mergemoże dać ci znacznie lepsze wyniki za pomocą dodatkowych informacji. (Ale bądź ostrożny: mergeedytuje jeden z plików w miejscu, chyba że używasz -p. Przeczytaj instrukcję).

Alexis
źródło
Dodałem coś, jeśli miałem konfliktsed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr
1
Nie sądzę, że to dobry pomysł. Jak napisałem w mojej odpowiedzi, powinieneś usuwać #elselinie ręcznie w edytorze podczas rozwiązywania konfliktu.
Alexis
6

merge(1) jest prawdopodobnie bliżej tego, czego chcesz, ale wymaga to wspólnego przodka dla twoich dwóch plików.

(Brudnym!) Sposobem na zrobienie tego jest:

  1. Pozbądź się pierwszej i ostatniej linii, użyj, grep(1)aby je wykluczyć
  2. Zniszcz wyniki razem
  3. sort -u pozostawia posortowaną listę, eliminuje duplikaty
  4. Zamień pierwszą / ostatnią linię

Humm ... coś w stylu:

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

może zrobić.

vonbrand
źródło
działa w tym konkretnym przykładzie, ale NIE ogólnie: Jeśli wartość name in_b_but_different_valma #00AABBsort, umieści tę wartość na górze i usunie drugą wartość zamiast pierwszej
Rafael T
dla optymalnego rozwiązania w tym przypadku trzeba przeanalizować XML, używając prawdziwego parsera XML, a nie powyższych hacków, i wygenerować z niego nowy scalony wynik XML. diff / patch / sort itp. to tylko hacki dostosowane do „konkretnych przykładów”, dla ogólnego rozwiązania są po prostu niewłaściwymi narzędziami
frostschutz
@alzheimer, przygotuj coś prostego, aby nam pokazać ...
vonbrand
Najwyraźniej diff3działa w ten sam sposób. Wymagający wspólnego pliku przodka. Dlaczego nie ma prostego narzędzia CLI, które po prostu łączy 2 pliki w zależności od tego diff, co pokazuje.
CMCDragonkai
5

sdiff (1) - łączenie różnic w plikach obok siebie

Użyj --outputopcji, spowoduje to interaktywne scalenie dowolnych dwóch plików. Korzystasz z prostych poleceń, aby wybrać zmianę lub edytować zmianę.

Należy upewnić się, że EDITORzmienna środowiskowa jest ustawiona. Domyślnym edytorem poleceń takich jak „eb” jest zazwyczaj ededytor liniowy .

EDITOR=nano sdiff -o merged.txt file1.txt file2.txt
Cody Allan Taylor
źródło
1
Uważam, że używanie vimjako EDYTORA jest lepsze. Ale to najlepsze rozwiązanie, ma też diffpolecenie!
CMCDragonkai
1

Oto proste rozwiązanie, które działa scalając do 10 plików :

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

pamiętaj, że arg, który jest pierwszy, ma pierwszeństwo, więc musisz zadzwonić:

script b.xml a.xml

aby uzyskać wspólne wartości, b.xmla nie 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
źródło
1

Kolejny okropny hack - można uprościć, ale: 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
źródło
0

OK, druga próba, teraz w Perlu ( nie jakość produkcji, brak sprawdzania!):

#!/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
źródło
0

Kolejny, wykorzystujący cut i grep ... (przyjmuje argumenty a.xml b.xml)

#!/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
źródło
echojest działaniem domyślnym, więc xargs echojest zbyteczne. Dlaczego tak po prostu nie jesteś tr '\n' '|'?
tripleee
Dobra uwaga - to tylko szybki hack. Zmienię to.
frostschutz