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
使用真正的编程语言,而不是用于此类任务的外壳程序。
斯特凡Chazelas

1
您是否需要保留元素的顺序?如果存在重复的元素(例如A和B都包含foo两次),是否需要在结果中重复它们?
吉尔(Gilles)'“ SO-不要邪恶”

Answers:


14

comm(1)是比较两个列表的工具,可以为您提供两个列表之间的交集或差值。列表需要排序,但是很容易实现。

要将数组放入适合的排序列表comm

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

这会将数组A转换为排序列表。对B做同样的事情。

要使用comm返回交集:

$ comm -1 -2 file1 file2

-1 -2 表示删除文件1(A)唯一和文件2(B)唯一的条目-两者的交集。

要使其返回文件2(B)中的内容,而不返回文件1(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)))

仅当您的值不包含时,此方法才有效\n
克里斯·

@ChrisDown:是的。我总是尝试编写正确引用的shell脚本并处理所有字符,但是我放弃了\ n。我从没在文件名中看到过它,并且大量的Unix工具与\ n分隔记录一起使用,如果尝试将\ n作为有效字符处理,则会损失很多。
camh 2013年

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
Jason R. Mick

一个不应该设置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与和互换Bintersections直到重新排序总是一样?
吉尔(Gilles)'所以

@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

    因此,首先我们得到了Aand的交集B(这只是它们之间的重复项的集合),说它是is A/\B,然后我们使用了反转Band的交集的操作A/\B(这仅仅是唯一的元素),所以我们得到B\A = ! (B /\ (A/\B))

PS uniq由Richard M. Stallman和David MacKenzie撰写。


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

0

我纯洁的打击方式

由于此变量仅包含vol-XXXwhere XXX是一个十六进制数,因此有一种使用bash数组的快速方法

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

因此,您以与提交相同的顺序处理

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行无用,则可以将其丢弃。
F. Hauri 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.