วิธีใช้ 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. หากบรรทัดมีแท็กชื่อเดียวกัน แต่มีค่าต่างกันให้นำค่าจากวินาที

ฉันต้องการใช้งานนี้ในสคริปต์ทุบตีดังนั้นจึงไม่จำเป็นต้องทำโดยใช้ diff และ patch ถ้าโปรแกรมอื่นเหมาะสมกว่า


diffสามารถบอกคุณได้ว่าบรรทัดใดอยู่ในไฟล์เดียว แต่ไม่สามารถบอกได้ว่าเป็นไฟล์ใด แต่จะอยู่ในกลุ่มย่อยของทั้งบรรทัด patchเหมาะสำหรับการเปลี่ยนแปลงไฟล์เดียวกันในลักษณะเดียวกัน (อาจเป็นไฟล์รุ่นเดียวกันหรือเป็นไฟล์ที่แตกต่างกันโดยสิ้นเชิงซึ่งอย่างไรก็ตามหมายเลขบรรทัดและบรรทัดโดยรอบสำหรับการเปลี่ยนแปลงแต่ละไฟล์จะเหมือนกับไฟล์ต้นฉบับของคุณ) ไม่เลยพวกมันไม่เหมาะกับงานนี้เป็นพิเศษ คุณอาจต้องการดูwdiffแต่วิธีแก้ไขอาจต้องใช้สคริปต์ที่กำหนดเอง เนื่องจากข้อมูลของคุณดูเหมือน XML คุณอาจต้องการค้นหาเครื่องมือ XSL บางตัว
tripleee

1
ทำไมคำตอบทั้งหมดกับสคริปต์ที่กำหนดเอง การผสานเป็นปัญหามาตรฐานและซับซ้อนและมีเครื่องมือที่ดีสำหรับมัน อย่าประดิษฐ์ล้อใหม่
alexis

คำตอบ:


23

คุณไม่ต้องการpatchสิ่งนี้ ใช้สำหรับแยกการเปลี่ยนแปลงและส่งโดยไม่มีส่วนของไฟล์ที่ไม่เปลี่ยนแปลง

เครื่องมือสำหรับการผสานไฟล์สองเวอร์ชันคือmergeแต่ตามที่@vonbrandเขียนไว้คุณต้องใช้ไฟล์ "base" ซึ่งทั้งสองเวอร์ชันแยกจากกัน หากต้องการผสานโดยไม่มีให้ใช้diffดังนี้:

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

มันจะแนบการเปลี่ยนแปลงแต่ละชุดในคำสั่งC-style #ifdef/ #ifndef"preprocessor" ดังนี้:

#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. Smash ผลลัพธ์ด้วยกัน
  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 ฯลฯ เป็นเพียงแฮ็กที่เหมาะกับ "ตัวอย่างเฉพาะ" สำหรับการแก้ปัญหาทั่วไปพวกเขาเป็นเพียงแค่เครื่องมือที่ผิด
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>
'

โปรดทราบ arg ที่มาก่อนมีความสำคัญดังนั้นคุณต้องโทร:

script b.xml a.xml

ที่จะได้รับค่านิยมร่วมกันเก็บไว้จากมากกว่าb.xmla.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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.