cara menggunakan patch dan diff untuk menggabungkan dua file dan secara otomatis menyelesaikan konflik


19

Saya telah membaca tentang diff dan patch tetapi saya tidak tahu bagaimana menerapkan apa yang saya butuhkan. Saya kira ini cukup sederhana, jadi untuk menunjukkan masalah saya ambil dua file ini:

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>

Saya ingin memiliki output, yang terlihat seperti ini (pesanan tidak masalah):

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

Penggabungan harus berisi semua baris di sepanjang aturan sederhana ini:

  1. setiap baris yang hanya ada di salah satu file
  2. jika suatu baris memiliki tag nama yang sama tetapi memiliki nilai yang berbeda, ambil nilainya dari yang kedua

Saya ingin menerapkan tugas ini di dalam skrip bash, jadi tidak perlu harus dilakukan dengan diff dan patch, jika program lain lebih cocok


diffdapat memberi tahu Anda baris mana dalam satu file tetapi tidak pada yang lain, tetapi hanya pada rincian seluruh baris. patchhanya cocok untuk membuat perubahan yang sama untuk file yang sama (mungkin versi berbeda dari file yang sama, atau file yang sama sekali berbeda di mana namun nomor baris dan garis sekitarnya untuk setiap perubahan identik dengan file asli Anda). Jadi tidak, mereka tidak cocok untuk tugas ini. Anda mungkin ingin melihatnya wdifftetapi solusinya mungkin memerlukan skrip khusus. Karena data Anda terlihat seperti XML, Anda mungkin ingin mencari beberapa alat XSL.
tripleee

1
Mengapa semua jawaban dengan skrip khusus? Penggabungan adalah masalah standar dan kompleks, dan ada alat yang bagus untuk itu. Jangan menemukan kembali roda.
alexis

Jawaban:


23

Anda tidak perlu patchuntuk ini; itu untuk mengekstraksi perubahan dan mengirimkannya tanpa bagian file yang tidak berubah.

Alat untuk menggabungkan dua versi file adalah merge, tetapi seperti yang @vonbrandditulis, Anda memerlukan file "basis" dari mana dua versi Anda berbeda. Untuk melakukan penggabungan tanpa itu, gunakan diffseperti ini:

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

Ini akan menyertakan setiap set perubahan dalam perintah C-style #ifdef/ #ifndef"preprocessor", seperti ini:

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

Jika garis atau wilayah berbeda antara dua file, Anda akan mendapatkan "konflik", yang terlihat seperti ini:

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

Jadi simpan output dalam file, dan buka di editor. Cari tempat mana pun yang #elsemuncul, dan atasi secara manual. Kemudian simpan file dan jalankan grep -vuntuk menyingkirkan yang tersisa #if(n)defdan #endifbaris:

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

Di masa depan, simpan versi file asli. mergedapat memberi Anda hasil yang lebih baik dengan bantuan informasi tambahan. (Tapi hati-hati: mergeedit salah satu file di tempat, kecuali Anda menggunakan -p. Baca manual).


Saya menambahkan sesuatu karena jika saya memiliki konfliksed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr

1
Saya pikir itu bukan ide yang bagus. Seperti yang saya tulis dalam jawaban saya, Anda harus menghapus #elsebaris secara manual, di editor selama resolusi konflik.
alexis

6

merge(1) mungkin lebih dekat dengan apa yang Anda inginkan, tetapi itu membutuhkan leluhur yang sama untuk dua file Anda.

Cara (kotor!) Untuk melakukannya adalah:

  1. Singkirkan baris pertama dan terakhir, gunakan grep(1)untuk mengecualikan mereka
  2. Hancurkan hasilnya bersama-sama
  3. sort -u meninggalkan daftar yang diurutkan, menghilangkan duplikat
  4. Ganti baris pertama / terakhir

Humm ... sesuatu seperti itu:

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

mungkin lakukan.


tidak bekerja dalam contoh khusus ini, tetapi BUKAN secara umum: Jika name in_b_but_different_valmemiliki nilai #00AABBsemacam akan menempatkan itu di atas dan menghapus nilai kedua alih-alih yang pertama
Rafael T

untuk solusi optimal dalam hal ini Anda harus mengurai XML, dengan parser XML nyata bukan peretasan di atas, dan menghasilkan output XML baru yang digabungkan dari itu. diff / patch / sort dll hanyalah semua retasan yang dirancang untuk "contoh-contoh tertentu", untuk solusi umum mereka hanyalah alat yang salah
frostschutz

@alzheimer, menyiapkan sesuatu yang sederhana untuk ditunjukkan kepada kita ...
vonbrand

Tampaknya diff3bekerja dengan cara yang sama. Membutuhkan file leluhur yang umum. Mengapa tidak ada alat CLI sederhana yang hanya menggabungkan 2 file bersama berdasarkan apa yang diffditampilkan.
CMCDragonkai

5

sdiff (1) - gabungan dari perbedaan file

Gunakan --outputopsi, ini akan secara interaktif menggabungkan dua file. Anda menggunakan perintah sederhana untuk memilih perubahan atau mengedit perubahan.

Anda harus memastikan bahwa EDITORvariabel lingkungan diatur. Editor default untuk perintah seperti "eb" biasanya ed, editor baris .

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

1
Saya menemukan menggunakan vimsebagai editor lebih baik. Tapi ini solusi terbaik, ia datang dengan diffperintah juga!
CMCDragonkai

1

Di sini solusi sederhana yang berfungsi menggabungkan hingga 10 file :

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

harap perhatikan arg yang didahulukan memiliki prioritas sehingga Anda harus menelepon:

script b.xml a.xml

untuk mendapatkan nilai-nilai umum b.xmldari a.xml.

script b.xml a.xml beluk:

<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

Peretasan mengerikan lainnya - bisa disederhanakan, tetapi: 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, coba kedua, sekarang di Perl ( bukan kualitas produksi, tidak ada pengecekan!):

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

Yang lain, menggunakan cut and grep ... (mengambil a.xml b.xml sebagai argumen)

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

echoadalah tindakan default, jadi xargs echotidak perlu. Kenapa kau tidak melakukannya tr '\n' '|'saja?
tripleee

Poin bagus - ini hanya hack cepat. Saya akan mengeditnya.
frostschutz
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.