Сравнение/различие двух массивов в 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" )
Цените свою помощь.
Ответы
Ответ 1
Если вы строго хотите Array1 - Array2
, то
$ 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
Время выполнения может быть улучшено с помощью ассоциативных массивов, но я лично не стал бы беспокоиться. Если вы манипулируете достаточным количеством данных для этого, оболочка - это неправильный инструмент.
Для симметричной разницы, такой как ответ Дениса, существующие инструменты, такие как 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
в подстановку процесса <(…)
, если вы не можете гарантировать порядок и уникальность входных массивов.
Ответ 2
echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u
Выход
key10
key7
key8
key9
Вы можете добавить сортировку, если вам нужно
Ответ 3
В любой момент, когда возникает вопрос об уникальных значениях, которые не могут быть отсортированы, мой ум сразу же переходит к 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 для обработки является более грязным, а не чистым.
Ответ 4
В 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
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[@]})
Ответ 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[@]}"
Выход
$ ./shell.sh
Array4: key7 key8 key9 key10
Array4: key11
Ответ 6
Имея 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"
Обратите внимание, что результат может не соответствовать порядку источника.
Бонус aka ", что вы здесь за":
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 как параметры компиляции.
Ответ 7
Можно также использовать регулярное выражение (на основе другого ответа: Пересечение массива в 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