Cách sử dụng patch và diff để hợp nhất hai tập tin và tự động giải quyết xung đột


19

Tôi đã đọc về diff và patch nhưng tôi không thể tìm ra cách áp dụng những gì tôi cần. Tôi đoán nó khá đơn giản, vì vậy để hiển thị vấn đề của tôi, hãy lấy hai tệp này:

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>

bDB

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

Tôi muốn có một đầu ra, trông như thế này (thứ tự không thành vấn đề):

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

Việc hợp nhất phải chứa tất cả các dòng dọc theo quy tắc đơn giản này:

  1. bất kỳ dòng nào chỉ có trong một trong các tệp
  2. nếu một dòng có cùng tên thẻ nhưng có giá trị khác, hãy lấy giá trị từ dòng thứ hai

Tôi muốn áp dụng tác vụ này trong tập lệnh bash, vì vậy nó không nhất thiết phải được thực hiện với diff và patch, nếu một chương trình khác phù hợp hơn


diffcó thể cho bạn biết những dòng nào trong một tệp nhưng không phải là dòng khác, mà chỉ về độ chi tiết của toàn bộ dòng. patchchỉ phù hợp để thực hiện cùng một thay đổi cho một tệp tương tự (có thể là một phiên bản khác của cùng một tệp hoặc một tệp hoàn toàn khác trong đó số dòng và các dòng xung quanh cho mỗi thay đổi giống hệt với tệp gốc của bạn). Vì vậy, không, chúng không đặc biệt phù hợp cho nhiệm vụ này. Bạn có thể muốn xem qua wdiffnhưng giải pháp có thể yêu cầu một tập lệnh tùy chỉnh. Vì dữ liệu của bạn trông giống như XML, bạn có thể muốn tìm một số công cụ XSL.
tripleee

1
Tại sao tất cả các câu trả lời với các kịch bản tùy chỉnh? Sáp nhập là một vấn đề tiêu chuẩn và phức tạp, và có những công cụ tốt cho nó. Đừng phát minh lại bánh xe.
alexis

Câu trả lời:


23

Bạn không cần patchđiều này; đó là để trích xuất các thay đổi và gửi chúng vào mà không có phần không thay đổi của tệp.

Công cụ để hợp nhất hai phiên bản của một tệp là merge, nhưng như @vonbrandđã viết, bạn cần tệp "cơ sở" mà từ đó hai phiên bản của bạn được chuyển hướng. Để thực hiện hợp nhất mà không có nó, sử dụng diffnhư thế này:

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

Nó sẽ kèm theo từng bộ thay đổi trong các lệnh kiểu C #ifdef/ #ifndef"tiền xử lý", như sau:

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

Nếu một dòng hoặc vùng khác nhau giữa hai tệp, bạn sẽ nhận được "xung đột", trông giống như sau:

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

Vì vậy, lưu đầu ra trong một tập tin, và mở nó trong một trình soạn thảo. Tìm kiếm bất kỳ nơi nào #elsexuất hiện và giải quyết chúng bằng tay. Sau đó lưu tệp và chạy qua grep -vđể loại bỏ các dòng còn lại #if(n)def#endifdòng:

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

Trong tương lai, lưu phiên bản gốc của tệp. mergecó thể cung cấp cho bạn kết quả tốt hơn nhiều với sự giúp đỡ của thông tin thêm. (Nhưng hãy cẩn thận: mergechỉnh sửa một trong các tệp tại chỗ, trừ khi bạn sử dụng -p. Đọc hướng dẫn).


Tôi đã thêm một cái gì đó nếu tôi có mâu thuẫnsed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr

1
Tôi không nghĩ đó là một ý tưởng tốt. Như tôi đã viết trong câu trả lời của mình, bạn nên xóa các #elsedòng thủ công, trong trình chỉnh sửa trong khi giải quyết xung đột.
alexis

6

merge(1) có thể gần với những gì bạn muốn, nhưng điều đó đòi hỏi một tổ tiên chung cho hai tệp của bạn.

Một cách (bẩn!) Làm việc đó là:

  1. Loại bỏ các dòng đầu tiên và cuối cùng, sử dụng grep(1)để loại trừ chúng
  2. Đập vỡ kết quả cùng nhau
  3. sort -u để lại một danh sách được sắp xếp, loại bỏ trùng lặp
  4. Thay thế dòng đầu tiên / cuối cùng

Humm ... một cái gì đó dọc theo dòng:

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

có thể làm.


không hoạt động trong ví dụ cụ thể này, nhưng KHÔNG nói chung: Nếu name in_b_but_different_valgiá trị #00AABBsắp xếp sẽ đặt giá trị đó lên hàng đầu và xóa giá trị thứ hai thay vì giá trị thứ nhất
Rafael T

để có giải pháp tối ưu trong trường hợp này, bạn phải phân tích cú pháp XML, với trình phân tích cú pháp XML thực sự không phải là các bản hack ở trên và tạo ra một đầu ra XML được hợp nhất mới từ đó. diff / patch / sort, v.v ... chỉ là tất cả các hack được điều chỉnh theo "ví dụ cụ thể", đối với một giải pháp chung, chúng chỉ đơn giản là các công cụ sai
frostschutz 2/213

@alzheimer, hãy làm một cái gì đó đơn giản để chỉ cho chúng tôi ...
vonbrand

Rõ ràng diff3hoạt động theo cùng một cách. Yêu cầu một tập tin tổ tiên chung. Tại sao không có công cụ CLI đơn giản nào chỉ hợp nhất 2 tệp lại với nhau dựa trên những gì diffhiển thị.
CMCDragonkai

5

sdiff (1) - hợp nhất các khác biệt của tập tin

Sử dụng --outputtùy chọn, điều này sẽ tương tác hợp nhất bất kỳ hai tập tin. Bạn sử dụng các lệnh đơn giản để chọn thay đổi hoặc chỉnh sửa thay đổi.

Bạn nên chắc chắn rằng EDITORbiến môi trường được đặt. Trình chỉnh sửa mặc định cho các lệnh như "eb" thường edlà trình chỉnh sửa dòng .

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

1
Tôi thấy việc sử dụng vimnhư là EDITOR là tốt hơn. Nhưng đây là giải pháp tốt nhất, nó cũng đi kèm với difflệnh!
CMCDragonkai

1

Đây là một giải pháp đơn giản có thể hợp nhất tối đa 10 tệp :

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

xin lưu ý rằng đối số đến trước có quyền ưu tiên nên bạn phải gọi:

script b.xml a.xml

để có được các giá trị chung được giữ từ b.xmlchứ không phải a.xml.

script b.xml a.xml bên ngoài:

<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

Một hack khủng khiếp khác - có thể được đơn giản hóa, nhưng: 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, thử lần thứ hai, bây giờ trong Perl ( không phải chất lượng sản xuất, không kiểm tra!):

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

Một số khác, sử dụng cut và grep ... (lấy a.xml b.xml làm đối số)

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

echolà hành động mặc định, vì vậy xargs echolà thừa. Tại sao bạn không đơn giản tr '\n' '|'?
tripleee

Điểm hay - đó chỉ là một bản hack nhanh. Tôi sẽ chỉnh sửa nó.
frostschutz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.