it-swarm.com.de

Vergleich/Unterschied zweier Arrays in Bash

Ist es möglich, die Differenz von zwei Arrays in Bash zu nehmen.
Wäre wirklich großartig, wenn Sie mir den Weg empfehlen könnten.

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

Schätze deine Hilfe.

41
Kiran

Wenn Sie unbedingt Array1 - Array2 wollen, dann

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

Die Laufzeit kann mit assoziativen Arrays verbessert werden, aber ich persönlich würde mich nicht darum kümmern. Wenn Sie so viele Daten bearbeiten, dass dies wichtig ist, ist Shell das falsche Werkzeug.


Für einen symmetrischen Unterschied wie Dennis 's Antwort funktionieren vorhandene Werkzeuge wie comm , solange wir die Eingabe und Ausgabe ein wenig massieren (da sie auf linienbasierten Dateien und nicht auf Shell-Variablen arbeiten).

Hier weisen wir die Shell an, das Array mit Zeilenumbrüchen zu einer einzigen Zeichenfolge zusammenzufügen und Registerkarten zu verwerfen, wenn Zeilen aus comm in ein Array zurückgelesen werden.

 $ oldIFS = $ IFS IFS = $ '\ n\t' 
 $ Array3 = ($ (Komm -3 <(Echo "$ {Array1 [*]}")) <(Echo "$ {Array2 [ *]} "))) 
 comm: Datei 1 ist nicht in sortierter Reihenfolge 
 $ IFS = $ oldIFS 
 $ declare -p Array3 
 declare -a Array3 = '([0] =" key7 "[1] =" key8 "[2] =" key9 "[3] =" key10 ") '

Es beschwert sich, weil key1 < … < key9 > key10 durch lexographische Sortierung. Da beide Eingabearrays jedoch ähnlich sortiert sind, können Sie diese Warnung ignorieren. Sie können --nocheck-order verwenden, um die Warnung zu entfernen, oder einen | sort -u innerhalb der <(…)-Prozessersetzung hinzufügen, wenn Sie die Reihenfolge und Eindeutigkeit der Eingabearrays nicht garantieren können.

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

Ausgabe

key10
key7
key8
key9

Sie können bei Bedarf eine Sortierung hinzufügen

94
Ilya Bystrov

Jedes Mal, wenn eine Frage auftaucht, die sich auf eindeutige Werte bezieht, die möglicherweise nicht sortiert werden, geht mein Verstand sofort in die falsche Richtung. Hier ist meine Meinung dazu.

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[@]}

Ausgabe

$ ./diffArray.sh
key10 key7 key8 key9

* Hinweis **: Wie bei anderen Antworten werden doppelte Schlüssel in einem Array nur einmal gemeldet. Dies kann das Verhalten sein, nach dem Sie suchen. Der Awk-Code, der damit umgehen soll, ist unordentlich und nicht so sauber.

15
SiegeX

Verwenden Sie ARR1 und ARR2 als Argumente, verwenden Sie comm, um den Job auszuführen, und mapfile, um ihn in das RESULT-Array zurückzusetzen:

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"

Beachten Sie, dass das Ergebnis die Quellreihenfolge möglicherweise nicht erfüllt.

Bonus aka "dafür sind Sie hier":

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"

Die Verwendung dieser kniffligen Evals ist die am wenigsten schlimmste Option, unter anderem bei Array-Parametern, die in bash übergeben werden.

Schauen Sie sich auch die comm-Manpage an. Basierend auf diesem Code ist es sehr einfach zu implementieren, zum Beispiel array_intersect: Verwenden Sie einfach -12 als Kommunikationsoptionen.

6
Alex Offshore

In 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

Bearbeiten:

ephemient weist auf einen möglicherweise schwerwiegenden Fehler hin. Wenn ein Element in einem Array mit einem oder mehreren Duplikaten vorhanden ist und im anderen Array überhaupt nicht vorhanden ist, wird es falsch aus der Liste der eindeutigen Werte entfernt. Die nachfolgende Version versucht, mit dieser Situation umzugehen.

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
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[@]}"

ausgabe

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

Es ist auch möglich, Regex zu verwenden (basierend auf einer anderen Antwort: Array-Schnittpunkt in 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[@]}:

Ergebnis:

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