Сравнение / Разница двух массивов в bash - PullRequest
44 голосов
/ 22 февраля 2010

Можно ли взять разницу двух массивов в bash.
Было бы здорово, если бы вы могли предложить мне, как это сделать.

код:

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

Ценю вашу помощь.

Ответы [ 7 ]

98 голосов
/ 27 января 2015
echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u

выход

key10
key7
key8
key9

Вы можете добавить сортировку, если вам нужно

28 голосов
/ 23 февраля 2010

Если вы строго хотите Array1 - Array2, то

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

Время выполнения может быть улучшено с помощью ассоциативных массивов, но я лично не стал бы беспокоиться. Если вы манипулируете достаточным количеством данных, чтобы это имело значение, shell - неправильный инструмент.


Для симметричной разницы, такой как ответ Денниса, работают такие инструменты, как comm, пока мы немного помассируем ввод и вывод (поскольку они работают с строковыми файлами, а не с переменными оболочки) .

Здесь мы говорим оболочке использовать новые строки для объединения массива в одну строку и отбрасывать вкладки при чтении строк из comm обратно в массив.

$ oldIFS=$IFS IFS=$'\n\t'
$ Array3=($(comm -3 <(echo "${Array1[*]}") <(echo "${Array2[*]}")))
comm: file 1 is not in sorted order
$ IFS=$oldIFS
$ declare -p Array3
declare -a Array3='([0]="key7" [1]="key8" [2]="key9" [3]="key10")'

Жалуется, потому что, путем лексографической сортировки, key1 < … < key9 > key10. Но поскольку оба входных массива отсортированы одинаково, это предупреждение можно игнорировать. Вы можете использовать --nocheck-order, чтобы избавиться от предупреждения, или добавить | sort -u в подстановку процесса <(…), если не можете гарантировать порядок и уникальность входных массивов.

15 голосов
/ 25 февраля 2010

Каждый раз, когда возникает вопрос, касающийся уникальных значений, которые не могут быть отсортированы, мой разум немедленно переходит к awk. Вот мой взгляд на это.

код

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

выход

$ ./diffArray.sh
key10 key7 key8 key9

* Примечание **: Как и в случае других ответов, если в массиве есть повторяющиеся ключи, они будут сообщены только один раз; это может или не может быть поведение, которое вы ищете. Код awk для обработки, который сложнее и не так чист.

6 голосов
/ 22 февраля 2017

Имея ARR1 и ARR2 в качестве аргументов, используйте comm для выполнения работы и mapfile чтобы вернуть ее в массив 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"

Обратите внимание, что результат может не соответствовать порядку источника.

Бонус, он же «за что ты здесь»:

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"

Использование этих хитрых уловок - наименее наихудший вариант среди других, касающихся параметров массива, передаваемых в bash.

Кроме того, взгляните на comm справочную страницу; на основе этого кода очень легко реализовать, например, array_intersect: просто используйте -12 в качестве параметров связи.

5 голосов
/ 22 февраля 2010

В Баш 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

Edit:

ephemient указал на потенциально серьезную ошибку. Если элемент существует в одном массиве с одним или несколькими дубликатами и вообще не существует в другом массиве, он будет неправильно удален из списка уникальных значений. Версия ниже пытается справиться с этой ситуацией.

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[@]})
2 голосов
/ 15 февраля 2016

Можно также использовать регулярные выражения (на основании другого ответа: Пересечение массива в 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[@]}:

Результат:

$ bash diff-arrays.sh 
4 7 10 12
2 голосов
/ 23 февраля 2010
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[@]}"

выход

$ ./shell.sh
Array4: key7 key8 key9 key10
Array4: key11
...