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. 행에 이름 태그는 동일하지만 값이 다른 경우 두 번째 값을 가져옵니다

이 작업을 bash 스크립트 안에 적용하고 싶기 때문에 다른 프로그램이 더 적합하다면 diff와 patch를 반드시 수행 할 필요는 없습니다.


diff하나의 파일에 있지만 다른 파일이 아닌 전체 행의 세분성에만있는 행을 알려줄 수 있습니다. patch유사한 파일 (같은 파일의 다른 버전 또는 각 변경에 대한 줄 번호와 주변 줄이 원본 파일과 동일한 파일 일 수도 있음)을 동일하게 변경하는 데만 적합합니다. 따라서이 작업에는 특히 적합하지 않습니다. 살펴보고 싶을 수도 wdiff있지만 솔루션에는 사용자 지정 스크립트 가 필요할 수 있습니다 . 데이터는 XML처럼 보이므로 일부 XSL 도구를 찾아 볼 수 있습니다.
tripleee

1
왜 사용자 정의 스크립트로 모든 답을 얻습니까? 병합은 표준적이고 복잡한 문제이며이를위한 좋은 도구가 있습니다. 바퀴를 재발 명하지 마십시오.
Alexis

답변:


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

1
나는 그것이 좋은 생각이라고 생각하지 않습니다. 내 대답에 쓴 것처럼 #else충돌 해결 중 편집기에서 줄을 수동으로 제거해야합니다 .
Alexis Alexis

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 등은 "특별한 예"에 맞춰진 모든 해킹 일뿐입니다. 일반적인 해결책으로는 단순히 잘못된 도구입니다
frostschutz

@alzheimer, 우리를 보여줄 간단한 것을 채찍질하십시오.
vonbrand

분명히 diff3같은 방식으로 작동합니다. 공통 조상 파일이 필요합니다. diff표시 되는 내용에 따라 두 개의 파일을 병합하는 간단한 CLI 도구가없는 이유는 무엇입니까?
CMCDragonkai 2016 년

5

sdiff (1)-파일 차이를 나란히 병합

--output옵션을 사용하면 두 파일을 대화식으로 병합합니다. 간단한 명령 을 사용 하여 변경을 선택하거나 변경을 편집합니다.

EDITOR환경 변수가 설정되어 있는지 확인해야합니다 . "eb"와 같은 명령의 기본 편집기는 일반적으로 ed라인 편집기 입니다.

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

1
내가 사용 찾아 vim더 나은 같은 편집기로. 그러나 이것은 최고의 솔루션이며 diff명령도 함께 제공됩니다 !
CMCDragonkai 2016 년

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

좋아, 두 번째 시도, 이제 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.