Пересечение массива в bash
Как вы сравниваете два массива в bash, чтобы найти все пересекающиеся значения?
Скажем:
array1 содержит значения 1 и 2
array2 содержит значения 2 и 3
В результате я должен вернуть 2.
Мой собственный ответ, который я еще не могу опубликовать из-за небольшой репутации:
for item1 in $array1; do
for item2 in $array2; do
if [[ $item1 = $item2 ]]; then
result=$result" "$item1
fi
done
done
Я также ищу альтернативные решения.
Ответы
Ответ 1
Элементы списка 1 используются как регулярное выражение, просмотренное в списке2 (выраженное как строка: ${list2 [*]}):
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[@]}
Результат
1 2 3 6 8 9 11
Ответ 2
Принимая @Raihan ответ и работая с не файлами (хотя созданы FD)
Я знаю, это немного обман, но, похоже, хорошая альтернатива.
Побочным эффектом является то, что выходной массив будет лексикографически отсортирован, надеюсь, что все в порядке
(также не знаете, какой тип данных у вас есть, поэтому я просто тестировал с номерами, может потребоваться дополнительная работа, если у вас есть строки со специальными символами и т.д.)
result=($(comm -12 <(for X in "${array1[@]}"; do echo "${X}"; done|sort) <(for X in "${array2[@]}"; do echo "${X}"; done|sort)))
Тестирование:
$ array1=(1 17 33 99 109)
$ array2=(1 2 17 31 98 109)
result=($(comm -12 <(for X in "${array1[@]}"; do echo "${X}"; done|sort) <(for X in "${array2[@]}"; do echo "${X}"; done|sort)))
$ echo ${result[@]}
1 109 17
p.s. Я уверен, что был способ получить массив, чтобы вывести одно значение в строке без цикла for
, я просто забыл (IFS?)
Ответ 3
Если это было два файла (вместо массивов), вы искали пересекающиеся строки, вы могли бы использовать команду comm
.
$ comm -12 file1 file2
Ответ 4
Ваш ответ не будет работать по двум причинам:
-
$array1
просто расширяется до первого элемента array1
. (По крайней мере, в моей установленной версии Bash, как это работает. Это не похоже на документальное поведение, поэтому это может быть зависящая от версии quirk.)
- После добавления первого элемента в
result
, result
будет содержать пробел, поэтому следующий запуск result=$result" "$item1
будет ужасно ошибочным. (Вместо добавления к result
он выполнит команду, состоящую из первых двух элементов, при этом переменная среды result
будет установлена в пустую строку.) Исправление:. Оказывается, я был неверно об этом: разбиение слов не происходит внутри заданий. (См. Комментарии ниже.)
Что вы хотите, так это:
result=()
for item1 in "${array1[@]}"; do
for item2 in "${array2[@]}"; do
if [[ $item1 = $item2 ]]; then
result+=("$item1")
fi
done
done
Ответ 5
Теперь, когда я понимаю, что вы подразумеваете под "массивом", я думаю, в первую очередь, что вы должны использовать фактические массивы Bash. Они гораздо более гибкие, в этом (например) элементы массива могут содержать пробелы, и вы можете избежать риска того, что *
и ?
будут инициировать расширение имени файла.
Но если вы предпочитаете использовать свой существующий подход в строках, разделенных пробелами, то я согласен с предложением RHT использовать Perl:
result=$(perl -e 'my %array2 = map +($_ => 1), split /\s+/, $ARGV[1];
print join " ", grep $array2{$_}, split /\s+/, $ARGV[0]
' "$array1" "$array2")
(Разрывы строк просто для чтения, вы можете избавиться от них, если хотите.)
В приведенной выше команде Bash встроенная программа Perl создает хэш с именем %array2
, содержащий элементы второго массива, а затем печатает любые элементы первого массива, которые существуют в %array2
.
Это будет немного отличаться от вашего кода в том, как он обрабатывает повторяющиеся значения во втором массиве; в вашем коде, если array1
содержит x
дважды, а array2
содержит x
три раза, тогда result
будет содержать x
шесть раз, тогда как в моем коде result
будет содержать только x
дважды. Я не знаю, имеет ли это значение, поскольку я не знаю ваших точных требований.