patchとdiffを使用して2つのファイルをマージし、競合を自動的に解決する方法


19

diffとパッチについて読んだことがありますが、必要なものを適用する方法がわかりません。私はかなり簡単だと思うので、私の問題を示すためにこれらの2つのファイルを取ります:

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. ファイルの1つにのみある任意の行
  2. 行の名前タグが同じで値が異なる場合、2番目の値を取得します

私はこのタスクをbashスクリプト内に適用したいので、別のプログラムがより適している場合は、diffとパッチを完全に行う必要はありません


diffどの行が一方のファイルにあり、もう一方のファイルにはないかを知ることができますが、行全体の粒度でのみです。patchは、同様のファイル(同じファイルの異なるバージョン、または各変更の行番号と周囲の行が元のファイルと同じである完全に異なるファイル)に同じ変更を加える場合にのみ適しています。いいえ、このタスクには特に適していません。ご覧になりたいかもしれませんwdiffが、ソリューションにはおそらくカスタムスクリプトが必要です。データはXMLのように見えるので、XSLツールを探したいかもしれません。
トリプリー

1
カスタムスクリプトですべての回答が必要なのはなぜですか?マージは標準的で複雑な問題であり、それに適したツールがあります。車輪を再発明しないでください。
アレクシス

回答:


23

patchこれは必要ありません。変更を抽出し、ファイルの変更されていない部分なしでそれらを送信するためです。

ファイルの2つのバージョンをマージするツールはですがmerge@vonbrand書いたように、2つのバージョンが分岐した「ベース」ファイルが必要です。それなしでマージを行うには、次の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

2つのファイル間で行または領域が異なる場合、次のような「競合」が発生します。

#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競合解決中にエディターで行を手動で削除する必要があります。
アレクシス

6

merge(1) おそらくあなたが望むものに近いですが、それはあなたの2つのファイルの共通の祖先を必要とします。

それを行う(汚い!)方法は次のとおりです:

  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の並べ替えが上にそれを置くと、代わりに最初の1の第2の値を消去
ラファエルT

この場合の最適なソリューションを得るには、上記のハッキングではなく実際のXMLパーサーを使用してXMLを解析し、そこから新しいマージされたXML出力を生成する必要があります。差分/パッチ/ソートなどの「特定の例」に合わせただけで、すべてのハックは、一般的な解決策のために彼らは単に間違ったツールですされている
frostschutz

@alzheimer、鞭アップ何か簡単な私たちを表示する...
vonbrand

どうやらdiff3同じように動作します。共通の祖先ファイルが必要です。diff表示内容に基づいて2つのファイルをマージするだけの簡単なCLIツールがないのはなぜですか。
CMCDragonkai

5

sdiff (1)-ファイルの違いの並列マージ

--outputオプションを使用します。これにより、2つのファイルが対話的にマージされます。単純なコマンドを使用して、変更を選択するか、変更を編集します。

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

OK、今はPerlでの2回目の試行、(ない生産品質、ノーチェック!):

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

もう1つは、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' '|'とにかくあなたはどうしてですか?
トリプリー

良い点-それは簡単なハックです。編集します。
frostschutz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.