web-dev-qa-db-fra.com

Comparer/Différence de deux tableaux dans bash

Est-il possible de prendre la différence de deux tableaux en bash.
Ce serait vraiment bien si vous pouviez me proposer le moyen de le faire.

Code:

Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" ) 

Array3 =diff(Array1, Array2)

Array3 ideally should be :
Array3=( "key7" "key8" "key9" "key10" )

Apprécier ton aide.

41
Kiran

Si vous voulez strictement Array1 - Array2, alors

Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" )

Array3=()
for i in "${Array1[@]}"; do
    skip=
    for j in "${Array2[@]}"; do
        [[ $i == $j ]] && { skip=1; break; }
    done
    [[ -n $skip ]] || Array3+=("$i")
done
declare -p Array3

Le runtime peut être amélioré avec des tableaux associatifs, mais personnellement, je ne m'en soucierais pas. Si vous manipulez suffisamment de données pour que cela compte, Shell est le mauvais outil.


Pour une différence symétrique comme celle de Dennis, les outils existants tels que comm work fonctionnent aussi longtemps que nous modifions un peu les entrées et les sorties (puisqu'elles fonctionnent sur des fichiers linéaires, pas sur des variables Shell).

Ici, nous invitons le shell à utiliser les nouvelles lignes pour joindre le tableau en une seule chaîne, et ignorons les tabulations lors de la lecture des lignes de comm dans un tableau.

 $ oldIFS = $ IFS IFS = $ '\ n\t' 
 $ Array3 = ($ comm -3 <(echo "$ {Array1 [*]}") <(echo "$ {Array2 [ *]} "))) 
 comm: le fichier 1 n'est pas dans l'ordre de tri 
 $ IFS = $ oldIFS 
 $ declare -p Array3 
 declare -a Array3 = '([0] =" key7 "[1] =" key8 "[2] =" key9 "[3] =" key10 ") '

Il se plaint parce que, par tri lexographique, key1 < … < key9 > key10. Mais comme les deux tableaux d’entrée sont triés de la même manière, il est bon d’ignorer cet avertissement. Vous pouvez utiliser --nocheck-order pour supprimer l'avertissement ou ajouter un | sort -u dans le processus de substitution <(…) si vous ne pouvez pas garantir l'ordre et l'unicité des tableaux d'entrée.

25
ephemient
echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u

Sortie

key10
key7
key8
key9

Vous pouvez ajouter un tri si vous avez besoin

94
Ilya Bystrov

Chaque fois qu'une question surgit concernant des valeurs uniques qui ne peuvent pas être triées, mon esprit devient immédiatement stupide. Voici mon point de vue sur elle.

Code

#!/bin/bash

diff(){
  awk 'BEGIN{RS=ORS=" "}
       {NR==FNR?a[$0]++:a[$0]--}
       END{for(k in a)if(a[k])print k}' <(echo -n "${!1}") <(echo -n "${!2}")
}

Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" )
Array3=($(diff Array1[@] Array2[@]))
echo ${Array3[@]}

Sortie

$ ./diffArray.sh
key10 key7 key8 key9

* Remarque **: Comme pour les autres réponses, s'il y a des clés en double dans un tableau, elles ne seront signalées qu'une fois. cela peut ou peut ne pas être le comportement que vous recherchez. Le code awk à gérer est plus compliqué et moins propre.

15
SiegeX

Avec ARR1 et ARR2 comme arguments, utilisez comm pour exécuter le travail et mapfile pour le remettre dans le tableau RESULT:

ARR1=("key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10")
ARR2=("key1" "key2" "key3" "key4" "key5" "key6")

mapfile -t RESULT < \
    <(comm -23 \
        <(IFS=$'\n'; echo "${ARR1[*]}" | sort) \
        <(IFS=$'\n'; echo "${ARR2[*]}" | sort) \
    )

echo "${RESULT[@]}" # outputs "key10 key7 key8 key9"

Notez que le résultat peut ne pas correspondre à l'ordre source.

Bonus aka "c'est ce que vous êtes ici pour":

function array_diff {
    eval local ARR1=\(\"\${$2[@]}\"\)
    eval local ARR2=\(\"\${$3[@]}\"\)
    local IFS=$'\n'
    mapfile -t $1 < <(comm -23 <(echo "${ARR1[*]}" | sort) <(echo "${ARR2[*]}" | sort))
}

# usage:
array_diff RESULT ARR1 ARR2
echo "${RESULT[@]}" # outputs "key10 key7 key8 key9"

L'utilisation de ces évaluations difficiles est la moins mauvaise des options parmi d'autres traitant des paramètres de tableau transmis en bash.

Consultez également la page de manuel comm; basé sur ce code, il est très facile à implémenter, par exemple, array_intersect: utilisez simplement -12 comme options de communication.

6
Alex Offshore

Dans Bash 4:

declare -A temp    # associative array
for element in "${Array1[@]}" "${Array2[@]}"
do
    ((temp[$element]++))
done
for element in "${!temp[@]}"
do
    if (( ${temp[$element]} > 1 ))
    then
        unset "temp[$element]"
    fi
done
Array3=(${!temp[@]})    # retrieve the keys as values

Modifier:

ephemient a signalé un bug potentiellement sérieux. Si un élément existe dans un tableau avec un ou plusieurs doublons et n'existe pas du tout dans l'autre tableau, il sera incorrectement supprimé de la liste des valeurs uniques. La version ci-dessous tente de gérer cette situation.

declare -A temp1 temp2    # associative arrays
for element in "${Array1[@]}"
do
    ((temp1[$element]++))
done

for element in "${Array2[@]}"
do
    ((temp2[$element]++))
done

for element in "${!temp1[@]}"
do
    if (( ${temp1[$element]} >= 1 && ${temp2[$element]-0} >= 1 ))
    then
        unset "temp1[$element]" "temp2[$element]"
    fi
done
Array3=(${!temp1[@]} ${!temp2[@]})
5
Dennis Williamson
Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" )
Array3=( "key1" "key2" "key3" "key4" "key5" "key6" "key11" )
a1=${Array1[@]};a2=${Array2[@]}; a3=${Array3[@]}
diff(){
    a1="$1"
    a2="$2"
    awk -va1="$a1" -va2="$a2" '
     BEGIN{
       m= split(a1, A1," ")
       n= split(a2, t," ")
       for(i=1;i<=n;i++) { A2[t[i]] }
       for (i=1;i<=m;i++){
            if( ! (A1[i] in A2)  ){
                printf A1[i]" "
            }
        }
    }'
}
Array4=( $(diff "$a1" "$a2") )  #compare a1 against a2
echo "Array4: ${Array4[@]}"
Array4=( $(diff "$a3" "$a1") )  #compare a3 against a1
echo "Array4: ${Array4[@]}"

sortie

$ ./Shell.sh
Array4: key7 key8 key9 key10
Array4: key11
2
ghostdog74

Il est également possible d'utiliser regex (basé sur une autre réponse: Intersection de tableau dans bash ):

list1=( 1 2 3 4   6 7 8 9 10 11 12)
list2=( 1 2 3   5 6   8 9    11 )

l2=" ${list2[*]} "                    # add framing blanks
for item in ${list1[@]}; do
  if ! [[ $l2 =~ " $item " ]] ; then    # use $item as regexp
    result+=($item)
  fi
done
echo  ${result[@]}:

Résultat:

$ bash diff-arrays.sh 
4 7 10 12
0
Denis Gois