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

У меня есть два массива:

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?

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

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

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

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

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

 $ comm -1 -2 file1 file2 

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

Чтобы он возвращал то, что находится в файле2 (B), но не file1 (A):

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

Вы можете получить все элементы, находящиеся в 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[@]}" 

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

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

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

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

 unset AB abci # 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 

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

 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 

Существует довольно элегантный и эффективный подход к этому, используя 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) 

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

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

 $ 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)) B\A = ! (B /\ (A/\B)) .

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