comment utiliser le correctif et le diff pour fusionner deux fichiers et résoudre automatiquement les conflits

19

J'ai lu des informations sur les différences et les correctifs, mais je ne sais pas comment appliquer ce dont j'ai besoin. Je suppose que c'est assez simple, alors pour montrer mon problème, prenez ces deux fichiers:

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>

Je veux avoir une sortie, qui ressemble à ceci (l'ordre n'a pas d'importance):

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

La fusion doit contenir toutes les lignes selon ces règles simples:

  1. toute ligne qui se trouve uniquement dans l'un des fichiers
  2. si une ligne a la même étiquette de nom mais une valeur différente, prenez la valeur de la seconde

Je veux appliquer cette tâche dans un script bash, donc il ne doit pas nécessairement être fait avec diff et patch, si un autre programme est mieux adapté

Rafael T
la source
diffpeut vous dire quelles lignes se trouvent dans un fichier mais pas dans l'autre, mais uniquement sur la granularité de lignes entières. patchne convient que pour apporter les mêmes modifications à un fichier similaire (peut-être une version différente du même fichier ou un fichier entièrement différent où cependant les numéros de ligne et les lignes environnantes pour chaque modification sont identiques à votre fichier d'origine). Donc non, ils ne sont pas particulièrement adaptés à cette tâche. Vous voudrez peut-être y jeter un œil, wdiffmais la solution nécessite probablement un script personnalisé. Étant donné que vos données ressemblent à XML, vous voudrez peut-être rechercher un outil XSL.
tripleee
1
Pourquoi toutes les réponses avec des scripts personnalisés? La fusion est un problème standard et complexe, et il existe de bons outils pour cela. Ne réinventez pas la roue.
alexis

Réponses:

23

Vous n'en avez pas besoin patch; c'est pour extraire les modifications et les envoyer sans la partie inchangée du fichier.

L'outil pour fusionner deux versions d'un fichier est merge, mais comme @vonbrandécrit, vous avez besoin du fichier "de base" à partir duquel vos deux versions ont divergé. Pour faire une fusion sans, utilisez diffcomme ceci:

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

Il enfermera chaque ensemble de modifications dans les commandes de style C #ifdef/ #ifndef"préprocesseur", comme ceci:

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

Si une ligne ou une région diffère entre les deux fichiers, vous obtiendrez un "conflit", qui ressemble à ceci:

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

Enregistrez donc la sortie dans un fichier et ouvrez-la dans un éditeur. Recherchez les endroits où #elseapparaît et résolvez-les manuellement. Ensuite, enregistrez le fichier et exécutez-le grep -vpour vous débarrasser des lignes restantes #if(n)defet #endif:

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

À l'avenir, enregistrez la version d'origine du fichier. mergepeut vous donner de bien meilleurs résultats à l'aide des informations supplémentaires. (Mais attention: mergeédite l'un des fichiers sur place, sauf si vous l'utilisez -p. Lisez le manuel).

alexis
la source
J'ai ajouté quelque chose pour si j'avais un conflitsed -e "s/^#else.*$/\/\/ conflict/g"
lockwobr
1
Je ne pense pas que ce soit une bonne idée. Comme je l'ai écrit dans ma réponse, vous devriez supprimer les #elselignes manuellement, dans l'éditeur pendant la résolution des conflits.
alexis
6

merge(1) est probablement plus proche de ce que vous voulez, mais cela nécessite un ancêtre commun à vos deux fichiers.

Une façon (sale!) De le faire est:

  1. Débarrassez-vous des première et dernière lignes, utilisez grep(1)pour les exclure
  2. Écrasez les résultats ensemble
  3. sort -u laisse une liste triée, élimine les doublons
  4. Remplacer la première / dernière ligne

Humm ... quelque chose dans le sens:

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

pourrait faire.

vonbrand
la source
fonctionne dans cet exemple particulier, mais PAS en général: si le name in_b_but_different_vala une valeur de #00AABBtri mettra cela en haut et effacera la deuxième valeur au lieu de la première
Rafael T
pour la solution optimale dans ce cas, vous devez analyser le XML, avec un véritable analyseur XML et non les hacks ci-dessus, et produire une nouvelle sortie XML fusionnée à partir de cela. diff / patch / sort, etc. ne sont que des hacks adaptés à des "exemples particuliers", pour une solution générale, ils sont tout simplement les mauvais outils
frostschutz
@alzheimer, concoctez quelque chose de simple à nous montrer ...
vonbrand
Fonctionne apparemment de diff3la même manière. Exiger un fichier ancêtre commun. Pourquoi n'y a-t-il pas d'outil CLI simple qui fusionne simplement 2 fichiers ensemble en fonction de ce qui diffs'affiche.
CMCDragonkai
5

sdiff (1) - fusion côte à côte des différences de fichiers

Utilisez l' --outputoption, cela fusionnera de manière interactive deux fichiers. Vous utilisez des commandes simples pour sélectionner une modification ou modifier une modification.

Vous devez vous assurer que la EDITORvariable d'environnement est définie. L'éditeur par défaut pour des commandes comme "eb" est généralement edun éditeur de ligne .

EDITOR=nano sdiff -o merged.txt file1.txt file2.txt
Cody Allan Taylor
la source
1
Je trouve vimque l' utilisation en tant que RÉDACTEUR est meilleure. Mais c'est la meilleure solution, elle vient aussi avec la diffcommande!
CMCDragonkai
1

Voici une solution simple qui fonctionne en fusionnant jusqu'à 10 fichiers :

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

veuillez noter que l'argument qui vient en premier a la priorité , vous devez donc appeler:

script b.xml a.xml

d'obtenir des valeurs communes b.xmlplutôt que a.xml.

script b.xml a.xml sorties:

<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>
neurino
la source
1

Un autre piratage horrible - pourrait être simplifié, mais: 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>"
frostschutz
la source
0

OK, deuxième essai, maintenant en Perl ( pas de qualité de production, pas de vérification!):

#!/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";
vonbrand
la source
0

Un autre, en utilisant cut et grep ... (prend a.xml b.xml comme arguments)

#!/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>"
frostschutz
la source
echoest l'action par défaut, donc xargs echosuperflue. Pourquoi ne le fais-tu pas de tr '\n' '|'toute façon?
tripleee
Bon point - c'est juste un hack rapide. Je vais le modifier.
frostschutz