Пересечение двух массивов в BASH

12

У меня есть два массива, как это:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

Массивы не отсортированы и могут даже содержать дублированные элементы.

  1. Я хотел бы сделать пересечение этих двух массивов и сохранить элементы в другом массиве. Как бы я это сделал?

  2. Кроме того, как я могу получить список элементов, которые появляются в B и недоступны в A?

Богдан
источник
2
Используйте настоящий язык программирования, а не оболочку для такого рода задач.
Стефан Шазелас
1
Вам нужно сохранить порядок элементов? Если есть дублированные элементы (например, A и B содержат по fooдва раза), нужно ли их дублировать в результате?
Жиль "ТАК - перестань быть злым"

Ответы:

13

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

Чтобы получить ваши массивы в отсортированный список, подходящий для comm:

$ printf '%s\n' "${A[@]}" | LC_ALL=C sort

Это превратит массив A в отсортированный список. Сделайте то же самое для Б.

Чтобы использовать commдля возврата пересечения:

$ comm -1 -2 file1 file2

-1 -2 говорит удалить записи, уникальные для file1 (A) и уникальные для file2 (B) - пересечение двух.

Чтобы он вернул то, что находится в файле 2 (B), но не в файле 1 (А):

$ comm -1 -3 file1 file2

-1 -3 говорит удалить записи, уникальные для file1 и общие для обоих - оставляя только те, которые уникальны для file2.

Чтобы ввести два конвейера comm, используйте функцию «Замена процесса» bash:

$ comm -1 -2 <(pipeline1) <(pipeline2)

Чтобы захватить это в массиве:

$ C=($(command))

Собираем все вместе:

# 1. Intersection
$ C=($(comm -12 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))

# 2. B - A
$ D=($(comm -13 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))
CAMH
источник
Это будет работать, только если ваши значения не содержат \n.
Крис Даун
@ChrisDown: Это верно. Я всегда стараюсь писать сценарии оболочки, которые правильно цитируются и обрабатывают все символы, но я отказался от \ n. Я НИКОГДА не видел его в имени файла, и большая куча инструментов Unix работает с записями с разделителями \ n, которые вы теряете много, если попытаетесь обработать \ n как допустимый символ.
camh
1
Я видел это в именах файлов при использовании файловых менеджеров GUI, которые неправильно очищают входные имена файлов, скопированные откуда-то еще (также никто ничего не говорил о именах файлов).
Крис Даун
Чтобы защитить \nэто попробуйте:arr1=( one two three "four five\nsix\nseven" ); arr2=( ${arr1[@]:1} "four five\\nsix" ); n1=${#arr1[@]}; n2=${#arr2[@]}; arr=( ${arr1[@]/ /'-_-'} ${arr2[@]/ /'-_-'} ); arr=( $( echo "${arr[@]}"|tr '\t' '-t-'|tr '\n' '-n-'|tr '\r' '-r-' ) ); arr1=( ${arr[@]:0:${n1}} ); arr2=( ${arr[@]:${n1}:${n2}} ); unset arr; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr1[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr2[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n\n'; unset arr1; unset arr2
Джейсон Р. Мик
Не следует устанавливать LC_ALL=C. Вместо этого установите LC_COLLATE=Cдля того же увеличения производительности без других побочных эффектов. Чтобы получить правильные результаты, вам также необходимо установить такое же сопоставление, commкоторое использовалось для sort, например:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal
4

Вы можете получить все элементы, которые находятся как в A, так и в B, просматривая оба массива и сравнивая:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

intersections=()

for item1 in "${A[@]}"; do
    for item2 in "${B[@]}"; do
        if [[ $item1 == "$item2" ]]; then
            intersections+=( "$item1" )
            break
        fi
    done
done

printf '%s\n' "${intersections[@]}"

Вы можете получить все элементы в B, но не в A аналогичным образом:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

not_in_a=()

for item1 in "${B[@]}"; do
    for item2 in "${A[@]}"; do
        [[ $item1 == "$item2" ]] && continue 2
    done

    # If we reached here, nothing matched.
    not_in_a+=( "$item1" )
done

printf '%s\n' "${not_in_a[@]}"
Крис Даун
источник
Упражнение: если вы перепутать Aи B, это intersectionsвсегда то же самое до переназначения?
Жиль "ТАК - перестань быть злым"
@Gilles Если массивы могут содержать повторяющиеся элементы, нет.
Крис Даун
3

Существует довольно элегантный и эффективный подход для этого, использующий uniq- но нам нужно будет исключить дубликаты из каждого массива, оставив только уникальные элементы. Если вы хотите сохранить дубликаты, есть только один способ «перебрать оба массива и сравнить».

Предположим, у нас есть два массива:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

Прежде всего, давайте преобразуем эти массивы в наборы. Мы сделаем это потому, что существует пересечение математических операций, которое известно как пересечение множеств, а множество - это совокупность отдельных объектов, отличных или уникальных . Если честно, я не знаю, что такое «пересечение», если мы говорим о списках или последовательностях. Хотя мы можем выбрать подпоследовательность из последовательности, но эта операция (выборка) имеет немного другое значение.

Итак, давайте преобразимся!

$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
  1. пересечения:

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d

    Если вы хотите сохранить элементы в другом массиве:

    $ intersection_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d)
    
    $ echo $intersection_set
    vol-175a3b54 vol-71600106 vol-98c2bbef

    uniq -dозначает показывать только дубликаты (я думаю, uniqэто довольно быстро из-за его реализации: я думаю, что это делается с помощью XORоперации).

  2. Получить список элементов, которые появляются в Bи не доступны A, т.е.B\A

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u

    Или с сохранением в переменной:

    $ subtraction_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u)
    
    $ echo $subtraction_set
    vol-27991850 vol-2a19386a vol-615e1222 vol-7320102b vol-8f6226cc vol-b846c5cf vol-e38d0c94

    Таким образом, сначала у нас есть пересечение Aи B(что является просто набором дубликатов между ними), скажем, что это так A/\B, а затем мы использовали операцию инвертирования пересечения Bи A/\B(что является просто уникальными элементами), так что мы получаем B\A = ! (B /\ (A/\B)).

PS uniqбыл написан Ричардом М. Столлманом и Дэвидом Маккензи.

Kenichi
источник
1

Игнорируя эффективность, вот подход:

declare -a intersect
declare -a b_only
for bvol in "${B[@]}"
do
    in_both=""
    for avol in "${A[@]}"
    do
        [ "$bvol" = "$avol" ] && in_both=Yes
    done
    if [ "$in_both" ]
    then
        intersect+=("$bvol")
    else
        b_only+=("$bvol")
    fi
done
echo "intersection=${intersect[*]}"
echo "In B only=${b_only[@]}"
John1024
источник
0

Мой чистый путь

Как содержать эти переменные только vol-XXXгде XXXэто шестнадцатеричное число, есть быстрый способ с использованием Баш массивов

unset A B a b c i                    # Only usefull for re-testing...

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e
   vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618
   vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b
   vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

for i in ${A[@]#vol-};do
    [ "${a[$((16#$i))]}" ] && echo Duplicate vol-$i in A
    ((a[$((16#$i))]++))
    ((c[$((16#$i))]++))
  done
for i in ${B[@]#vol-};do
    [ "${b[$((16#$i))]}" ] && echo Duplicate vol-$i in B
    ((b[$((16#$i))]++))
    [ "${c[$((16#$i))]}" ] && echo Present in A and B: vol-$i
    ((c[$((16#$i))]++))
  done

Это должно вывести:

Present in A and B vol-175a3b54
Present in A and B vol-98c2bbef
Present in A and B vol-71600106

В этом состоянии ваша среда bash содержит:

set | grep ^c=
c=([391789396]="2" [664344656]="1" [706295914]="1" [942425979]="1" [1430316568]="1"
[1633554978]="1" [1902117126]="2" [1931481131]="1" [2046269198]="1" [2348972751]="1"
[2377892602]="1" [2405574348]="1" [2480340688]="1" [2562898927]="2" [2570829524]="1"
[2654715603]="1" [2822487781]="1" [2927548899]="1" [3091645903]="1" [3654723758]="1"
[3817671828]="1" [3822495892]="1" [4283621042]="1")

Чтобы вы могли:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 1 ] &&
        printf "Present only in B: vol-%8x\n" $i
  done

Это сделает:

Present only in B: vol-27991850
Present only in B: vol-2a19386a
Present only in B: vol-615e1222
Present only in B: vol-7320102b
Present only in B: vol-8f6226cc
Present only in B: vol-b846c5cf
Present only in B: vol-e38d0c94

Но это численно! Если вы хотите оригинальный заказ, вы можете:

for i in ${B[@]#vol-};do
    [ ${c[((16#$i))]} -eq 1 ] && printf "Present in B only: vol-%s\n" $i
  done

Таким образом, вы отображаете vols в том же порядке, что и submited:

Present in B only: vol-e38d0c94
Present in B only: vol-2a19386a
Present in B only: vol-b846c5cf
Present in B only: vol-7320102b
Present in B only: vol-8f6226cc
Present in B only: vol-27991850
Present in B only: vol-615e1222

или

for i in ${!a[@]};do
    [ ${c[$i]} -eq 1 ] && printf "Present only in A: vol-%8x\n" $i
  done

для показа только в A :

Present only in A: vol-382c477b
Present only in A: vol-5540e618
Present only in A: vol-79f7970e
Present only in A: vol-8c027acf
Present only in A: vol-8dbbc2fa
Present only in A: vol-93d6fed0
Present only in A: vol-993bbed4
Present only in A: vol-9e3bbed3
Present only in A: vol-a83bbee5
Present only in A: vol-ae7ed9e3
Present only in A: vol-d9d6a8ae
Present only in A: vol-e3d6a894
Present only in A: vol-ff52deb2

или даже:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 2 ] && printf "Present in both A and B: vol-%8x\n" $i
  done

будет повторно печатать :

Present in both A and B: vol-175a3b54
Present in both A and B: vol-71600106
Present in both A and B: vol-98c2bbef
Ф. Хаури
источник
Конечно, если Duplicateлинии бесполезны, их можно просто отбросить.
Ф. Хаури