it-swarm.com.ru

Сравнение/Разница двух массивов в bash

Можно ли взять разницу двух массивов в 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" )

Ценю твою помощь.

41
Kiran

Если вы строго хотите 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: файл 1 находится не в отсортированном порядке 
 $ IFS = $ oldIFS 
 $ Declare -p Array3 
 декларировать -a Array3 = '([0] =" key7 "[1] =" key8 "[2] =" key9 "[3] =" key10 ") '

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

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

Результат

key10
key7
key8
key9

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

94
Ilya Bystrov

Каждый раз, когда всплывает вопрос об уникальных значениях, которые не могут быть отсортированы, мой разум немедленно переходит к 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 для обработки, который сложнее и не так чист.

15
SiegeX

Имея 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 manpage; на основе этого кода очень легко реализовать, например, array_intersect: просто используйте -12 в качестве параметров связи.

6
Alex Offshore

В 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

Правка:

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

Результат

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

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