как использовать patch и diff для объединения двух файлов и автоматического разрешения конфликтов


19

Я читал о diff и patch, но не могу понять, как применить то, что мне нужно. Я думаю, это довольно просто, поэтому, чтобы показать мою проблему, возьмите эти два файла:

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>

Я хочу получить вывод, который выглядит следующим образом (порядок не имеет значения):

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

Слияние должно содержать все строки по этим простым правилам:

  1. любая строка, которая есть только в одном из файлов
  2. если строка имеет тот же тег имени, но другое значение, возьмите значение из второго

Я хочу применить эту задачу в скрипте bash, поэтому она не обязательно должна быть завершена с помощью diff и patch, если другая программа лучше подходит


diffможет сказать вам, какие строки находятся в одном файле, но не в другом, но только по степени детализации целых строк. patchподходит только для внесения одинаковых изменений в аналогичный файл (возможно, в другую версию того же файла или в совершенно другой файл, где, однако, номера строк и окружающие строки для каждого изменения идентичны вашему исходному файлу). Так что нет, они не особенно подходят для этой задачи. Возможно, вы захотите взглянуть, wdiffно решение, вероятно, требует специального сценария. Поскольку ваши данные выглядят как XML, вам может понадобиться какой-нибудь инструмент XSL.
tripleee

1
Почему все ответы с нестандартными сценариями? Слияние является стандартной и сложной проблемой, и для этого есть хорошие инструменты. Не изобретай велосипед.
Алекс

Ответы:


23

Вам не нужно patchдля этого; это для извлечения изменений и отправки их без неизменной части файла.

Инструмент для слияния двух версий файла есть merge, но, как @vonbrandнаписали, вам нужен «базовый» файл, из которого ваши две версии расходятся. Чтобы сделать слияние без него, используйте diffтак:

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

Он будет включать в себя каждый набор изменений в командах в стиле C #ifdef/ #ifndef«препроцессор», например:

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

Если линия или область отличаются между двумя файлами, вы получите «конфликт», который выглядит следующим образом:

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

Поэтому сохраните вывод в файл и откройте его в редакторе. Ищите любые места, где #elseвыплывает, и разрешайте их вручную. Затем сохраните файл и запустите его, grep -vчтобы избавиться от оставшихся строк #if(n)defи #endifстрок:

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

В дальнейшем сохраните оригинальную версию файла. mergeможет дать вам гораздо лучшие результаты с помощью дополнительной информации. (Но будьте осторожны: mergeредактируйте один из файлов на месте, если вы не используете -p. Прочтите руководство).


Я добавил кое-что, если у меня возник конфликтsed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr

1
Я не думаю, что это хорошая идея. Как я написал в своем ответе, вы должны удалять #elseстроки вручную в редакторе во время разрешения конфликта.
Alexis

6

merge(1) вероятно, ближе к тому, что вы хотите, но это требует общего предка ваших двух файлов.

(Грязный!) Способ сделать это:

  1. Избавьтесь от первой и последней строчек, используйте grep(1)для их исключения
  2. Разбейте результаты вместе
  3. sort -u оставляет отсортированный список, удаляет дубликаты
  4. Заменить первую / последнюю строку

Хм ... что-то вроде:

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

может сделать.


работает в этом конкретном примере, но НЕ в общем: если значение name in_b_but_different_valимеет значение #00AABBsort, оно помещает его сверху и удаляет второе значение вместо первого
Rafael T

для оптимального решения в этом случае вам нужно будет проанализировать XML с реальным парсером XML, а не взломами, описанными выше, и создать новый объединенный вывод XML из этого. diff / patch / sort и т. д. - это просто хаки, приспособленные к «конкретным примерам», для общего решения они просто неправильные инструменты
frostschutz

@alzheimer, напиши что-нибудь простое, чтобы показать нам ...
vonbrand

Видимо diff3работает так же. Требуется файл общего предка. Почему нет простого инструмента CLI, который просто объединяет 2 файла в зависимости от того, что diffпоказывает.
CMCDragonkai

5

sdiff (1) - параллельное объединение различий в файлах

Используйте --outputопцию, это интерактивно объединит любые два файла. Вы используете простые команды, чтобы выбрать изменение или отредактировать изменение.

Вы должны убедиться, что EDITORпеременная окружения установлена. Редактором по умолчанию для таких команд, как «eb», обычно edявляется редактор строк .

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

1
Я считаю использование vimв качестве редактора как лучше. Но это лучшее решение, оно приходит и с diffкомандой!
CMCDragonkai

1

Вот простое решение, которое работает, объединяя до 10 файлов :

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

обратите внимание, что аргумент, который стоит первым, имеет приоритет, поэтому вам нужно позвонить:

script b.xml a.xml

чтобы получить общие ценности, b.xmlа не a.xml.

script b.xml a.xml выходы:

<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

Еще один ужасный взлом - можно упростить, но: 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

Хорошо, вторая попытка, теперь в Perl ( не качество продукции, не проверка!):

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

Еще один, используя cut и grep ... (принимает 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>"

echoявляется действием по умолчанию, поэтому xargs echoявляется излишним. Почему бы тебе просто не tr '\n' '|'пойти?
tripleee

Хороший вопрос - это просто быстрый взлом. Я отредактирую это.
frostschutz
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.