【问题标题】:Compare/Difference of two arrays in BashBash中两个数组的比较/差异
【发布时间】:2026-01-01 15:15:02
【问题描述】:

是否可以在 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" )

【问题讨论】:

  • 浏览了解决方案后,我决定在必须对它们进行比较的情况下不使用数组。

标签: arrays bash diff compare


【解决方案1】:

@ilya-bystrov 最受好评的答案计算了Array1Array2 的差异。请注意,这与从Array1 中删除 项目相同,这些项目也在Array2 中。 @ilya-bystrov 的解决方案是连接两个列表并删除非唯一值。当Array2 包含不在Array1 中的项目时,这是一个巨大的差异:Array3 将包含在Array2 中的值,但不在Array1 中。

这是一个纯 Bash 解决方案,用于删除来自 Array1 中的项目,这些项目也在 Array2 中(注意 Array2 中的附加 "key11"):

Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" "key11" )
Array3=( $(printf "%s\n" "${Array1[@]}" "${Array2[@]}" "${Array2[@]}" | sort | uniq -u) )

Array3 将由"key7" "key8" "key9" "key10" 组成,并在尝试从Array1 中删除项目时排除意外的"key11"

请注意:这假定Array1 中的所有值都是唯一的。否则他们不会出现在Array3。如果Array1 包含重复值,则必须先删除重复值(注意Array1 中重复的"key10"):

Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" "key10" )
Array2=( "key1" "key2" "key3" "key4" "key5" "key6" "key11" )
Array3=( $({ printf "%s\n" "${Array1[@]} | sort -u; printf "%s\n" "${Array2[@]}" "${Array2[@]}"; } | sort | uniq -u) )

如果您想将Array1 中的重复项复制到Array2,请使用@ephemient 接受的答案。如果Array1Array2 很大,情况也是如此:这对于很多项目来说是一个非常低效的解决方案,即使它对于少数项目(

【讨论】:

    【解决方案2】:

    如果你严格要求Array1 - Array2,那么

    Array1=( "key1" "key2" "key3" "key4" "key5" "key6" "key7" "key8" "key9" "key10" )
    Array2=( "key1" "key2" "key3" "key4" "key5" "key6" )
    
    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
    

    使用关联数组可能会改善运行时,但我个人不会打扰。如果您要处理足够多的数据,那么 shell 是错误的工具。


    对于像 Dennis 的回答这样的对称差异,只要我们稍微调整输入和输出(因为它们适用于基于行的文件,而不是 shell 变量),现有的工具(如 comm)就可以工作。

    在这里,我们告诉 shell 使用换行符将数组连接成单个字符串,并在将 comm 中的行读回数组时丢弃制表符。

    $旧IFS=$IFS IFS=$'\n\t' $ Array3=($(comm -3

    它之所以抱怨,是因为按照字典排序,key1 < … < key9 > key10。但是由于两个输入数组的排序相似,因此可以忽略该警告。如果不能保证输入数组的顺序和唯一性,可以使用--nocheck-order 消除警告,或者在<(…) 进程替换中添加| sort -u

    【讨论】:

    • +1 表示第一个 sn-p,它也适用于嵌入空格的元素。第二个 sn-p 仅适用于嵌入 空格 的元素。如果您只是将IFS=$'\n\t' 直接添加到Array3=... 命令之前,则可以取消保存和恢复$IFS
    • @mklement0 您建议的命令:IFS=$'\n\t' Array3=( ... ) 全局设置IFS。试试看!
    • @gniourf_gniourf:感谢您的关注!因为我的谬误可能对其他人也很有吸引力,所以我将留下我的原始评论并在这里解释:虽然将 ad-hoc, command-local variable assignment 添加到简单的命令,它在这里不起作用,因为我的命令完全由分配组成No command name(外部可执行文件,内置)跟随分配,这使得 all 它们global(在当前 shell 的上下文中);见man bash,部分SIMPLE COMMAND EXPANSION)。
    • 你能举例说明如何在 C-shell (csh) 中执行此操作吗?
    • @Stefan:呃,永远不应该使用 csh。 set Array3 = ( ) 987654338 set skip = 0 987654340 if ( "$i" == "$j" ) then 987654342 break 987654344 end 987654346 set Array3 = ( $Array3:q "$i" ) 987654348 end所有的控制语句需要对自己行.
    【解决方案3】:

    ARR1ARR2 作为参数,使用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"
    

    请注意,结果可能不符合源顺序。

    奖金又名“这就是你在这里的目的”:

    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 中传递的数组参数的其他方法中,使用那些棘手的 eval 是最糟糕的选择。

    另外,看看comm manpage;基于此代码,它很容易实现,例如array_intersect:只需使用 -12 作为通讯选项即可。

    【讨论】:

    • 注意到mapfile 需要 bash 4
    • @lantrix, mapfile 可以很容易地替换为while..read,如果不需要数组,甚至可以完全删除。所有的魔法都发生在comm
    【解决方案4】:

    也可以使用正则表达式(基于另一个答案:Array intersection in 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
    

    【讨论】:

    • 似乎很奇怪,这被否决了,没有评论。如果它有问题,请帮大家一个忙,并指出问题所在。
    【解决方案5】:
    echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u
    

    输出

    key10
    key7
    key8
    key9
    

    如果需要,可以添加排序

    【讨论】:

    • 他进来了,他指挥了它,然后他离开了。对于任何想知道如何将值保存到数组的人,试试这个:Array3=(`echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u `)
    • 这就是 shell 编程的意义所在。保持简单,使用可用的工具。如果您想实现其他解决方案,可以,但使用更强大的语言可能会更轻松。
    • 太棒了。需要不对称差异的人的附加说明。您可以通过输出 对称 差异和您感兴趣的数组的副本来获得它。如果您想要 Array2 中存在的值,而不是 Array1 中的值。 echo ${Array2[@]} ${Array3[@]} | tr ' ' '\n' | sort | uniq -D | uniq,其中 Array3 是上面的输出。此外,如果您删除数组符号并假设变量是空格分隔的字符串,则此方法与 posix shell 兼容。
    • 很棒的解决方案。如果数组元素可能包含空格,则略有改进:printf '%s\n' "${Array1[@]}" "${Array2[@]}" | sort | uniq -u
    • 为了简化@Arwyn 的建议,您可以添加两次忽略的数组,以确保仅显示 Array2 中的差异。 echo ${Array1[@]} ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u
    【解决方案6】:

    在 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
    

    编辑:

    ehemient 指出了一个潜在的严重错误。如果一个元素存在于一个具有一个或多个重复项的数组中,并且在另一个数组中根本不存在,它将被错误地从唯一值列表中删除。下面的版本试图处理这种情况。

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

    【讨论】:

    • 执行对称差分,并假设原始数组没有重复。所以这不是我首先想到的,但它适用于 OP 的一个例子。
    • @ephemient:对,平行于diff(1),它也是对称的。此外,此脚本只需将它们添加到第一个版本第二行的列表中即可查找任意数量的数组唯一的元素。我添加了一个编辑,它提供了一个版本来处理一个数组中没有出现在另一个数组中的重复项。
    • 非常感谢..我在想是否有任何明显的方法可以做到这一点..如果我不知道有任何命令可以很容易地给出 2 个数组的差异..感谢您的支持和帮助。我修改了代码以读取 2 个文件的差异,这更容易编程
    • 您的第二个 sn-p 将不起作用,因为 &gt; 仅适用于 (( ... )),不适用于 [[ ... ]];在后者中,它必须是-gt;但是,由于您的意思可能是 &gt;= 而不是 &gt;,因此应将 &gt; 替换为 -ge。要明确说明“对称”在这种情况下的含义:输出是一个 single 数组,其中包含 either 数组唯一的值。
    • @mklement0: &gt; 确实在双方括号内工作,但在词法上而不是在数字上。因此,在比较整数时,应该使用双括号 - 所以你在这方面是正确的。我已经相应地更新了我的答案。
    【解决方案7】:

    每当出现处理可能无法排序的唯一值的问题时,我都会立即想到 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 代码比较混乱,而且不够干净。

    【讨论】:

    • 总结行为和约束:(a) 执行一个对称的区别:输出一个single数组,其中元素对either 输入数组(它与 OP 的样本数据恰好与仅输出 first 数组唯一的元素相同),(b)仅适用于没有嵌入空格的元素(满足OP 的要求),以及 (c) 输出数组中的元素顺序与输入元素的顺序没有任何保证关系,因为 awk 无条件使用 associative 数组 - 已证明通过示例输出。
    • 此外,对于 bash 不支持将 arrays 作为参数传递的问题,此答案使用了一个聪明且值得注意但令人费解的解决方法:Array1[@]Array2[@] 作为 strings - 各自的数组名称加上所有下标后缀 [@]- 传递给 shell 函数 diff()(作为参数 $1$2,像往常一样)。然后,shell 函数使用 bash 的变量 indirection ({!...}) 来间接 引用原始数组的所有元素(${!1} 和 `${!1}') .
    • 如何将字符串“a b C”转换为数组?
    • 发现一个错误:Array2 中的元素不在Array1 中将显示在diff()
    • 此解决方案不适用于包含空格的数组元素。由于未加引号的字符串被 shell 扩展,示例脚本可能会以多种方式失败。如果在运行脚本之前执行touch Array1@,它会失败,因为字符串Array1[@]Array2[@] 被用作不带引号的shell GLOB 模式。如果一个数组包含元素 * 则失败,因为该未加引号的 GLOB 模式匹配当前目录中的所有文件。
    【解决方案8】:
    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
    

    【讨论】: