如何使用patch和diff合并两个文件并自动解决冲突


19

我已经阅读了有关diff和patch的内容,但我不知道如何应用所需的内容。我想这很简单,因此为了显示我的问题,请使用以下两个文件:

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
为什么所有答案都使用自定义脚本?合并是一个标准且复杂的问题,有很好的工具可以解决。不要重新发明轮子。
亚历克西斯

Answers:


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

1
我认为这不是一个好主意。如我在回答中所写,您应该#else在冲突解决期间在编辑器中手动删除这些行。
亚历克西斯

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具有#00AABB排序的值会将其放在最前面,并擦除第二个值而不是第一个值
Rafael T

对于这种情况下的最佳解决方案,您必须使用真正的XML解析器(而不是上面的技巧)来解析XML,并从中生成新的合并XML输出。diff / patch / sort等只是针对“特定示例”的所有hack,对于一般解决方案而言,它们只是错误的工具
frostschutz

@alzheimer,用简单的方法向我们展示...
vonbrand

显然,diff3工作方式相同。需要一个共同的祖先文件。为什么没有简单的CLI工具仅根据diff显示的内容将2个文件合并在一起。
CMCDragonkai

5

sdiff (1)-文件差异的并排合并

使用该--output选项,它将以交互方式合并任何两个文件。您可以使用简单的命令来选择更改或编辑更改。

您应该确保EDITOR设置了环境变量。像“ eb”这样的命令的默认编辑器通常ed是行编辑器

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

1
我发现使用vimEDITOR更好。但这是最好的解决方案,它也附带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>
'

请注意,第一个出现的arg具有优先权,因此您必须调用:

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' '|'呢?
2013年

好点-这只是一个快速的技巧。我将对其进行编辑。
frostschutz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.