bash中两个数组的比较/差异

Kir*_*ran 46 arrays bash diff compare

是否有可能在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" )
Run Code Online (Sandbox Code Playgroud)

感谢您的帮助.

Ily*_*rov 106

echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u
Run Code Online (Sandbox Code Playgroud)

产量

key10
key7
key8
key9
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以添加排序

  • 他进来了,他把它扔了,然后他离开了.对于想知道如何将值保存到数组的人,请尝试:`Array3 =(\`echo $ {Array1 [@]} $ {Array2 [@]} | tr'''\n'| sort | uniq -u \`)` (23认同)
  • 辉煌.需要**不对称**差异的人的补充说明.您可以通过输出**对称**差异的重复项和您感兴趣的数组来获得它.如果您想要Array2中存在的值,而不是Array1中的值,则可以获取IE.`echo $ {Array2 [@]} $ {Array3 [@]} | tr'''\n'| 排序| uniq -D | uniq`,其中Array3是上面的输出.此外,如果删除数组符号并假设变量是空格分隔的字符串,则此方法符合posix shell. (13认同)
  • 这就是shell编程的意义所在.保持简单,使用可用的工具.如果您想实现其他解决方案,您可以,但您可以更轻松地使用更强大的语言. (7认同)
  • 为了简化@Arwyn的建议,您可以添加两次忽略的数组,以确保只显示Array2中的差异.`echo $ {Array1 [@]} $ {Array1 [@]} $ {Array2 [@]} | tr'''\n'| 排序| uniq -u` (7认同)
  • 对@ChristopherMarkieta 的回答的一个小评论:问题是计算 Array1-Array2,在这种情况下它应该是 `echo ${Array1[@]} ${Array2[@]} ${Array2[@]} | tr ' ' '\n' | 排序 | uniq -u`(Array2 两次)。并感谢对一个很好的答案的补充。 (5认同)
  • 很棒的解决方案.如果数组元素可能包含空格,则会略有改进:`printf'%s \n'"$ {Array1 [@]}""$ {Array2 [@]}"| 排序| uniq -u` (3认同)
  • 添加到 @misberner 的扩展:如果您的数组包含换行符和空格: `printf "%s\0" "${Array1[@]}" "${Array2[@]}" | 排序 -z | uniq-zu`。(输出将以空分隔,需要进行相应处理) (2认同)
  • 实际上,给出的答案是计算对称差。两组中的内容都将被删除,仅一组中的内容将被保留: ```$ echo {1..4} {2..6} | tr ' ' '\n' | 排序| uniq-u| tr '\n' ' '``` 给出 ```1 5 6```。包含第二组两次会产生常规差异: ```$ echo {1..4} {2..6} {2..6} | tr ' ' '\n' | 排序| uniq-u| tr '\n' ' '``` 给出 ```1```。 (2认同)

eph*_*ent 28

如果你真的想要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
Run Code Online (Sandbox Code Playgroud)

使用关联数组可以改善运行时,但我个人不会打扰.如果您正在操纵足够的数据,那么shell就是错误的工具.


对于像Dennis的答案那样的对称差异,现有的工具就像comm工作一样,只要我们按下输入和输出一点(因为它们在基于行的文件上工作,而不是shell变量).

在这里,我们告诉shell使用换行符将数组连接成一个字符串,并在从comm后面读取数组时放弃标签.

$ oldIFS=$IFS IFS=$'\n\t'
$ Array3=($(comm -3 <(echo "${Array1[*]}") <(echo "${Array2[*]}")))
comm: file 1 is not in sorted order
$ IFS=$oldIFS
$ declare -p Array3
declare -a Array3='([0]="key7" [1]="key8" [2]="key9" [3]="key10")'

它抱怨因为,通过词法分类,key1 < … < key9 > key10.但由于两个输入数组的排序方式相似,因此可以忽略该警告.如果无法保证输入数组的顺序和唯一性,可以使用它--nocheck-order来消除警告,或者| sort -u<(…)进程替换中添加一个.

  • 第一个片段+1,它也适用于嵌入空白的元素。第二个片段仅适用于嵌入_spaces_的元素。如果您只需将 `IFS=$'\n\t' ` 直接添加到 `Array3=...` 命令中,就可以不用保存和恢复 `$IFS`。 (2认同)
  • @ mklement0你建议的命令:`IFS = $'\n\t'Array3 =(...)`_will_全局设置`IFS`.试试吧! (2认同)

Sie*_*geX 15

任何时候弹出一个问题来处理可能没有排序的唯一值,我的脑海里立刻就会变成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[@]}
Run Code Online (Sandbox Code Playgroud)

产量

$ ./diffArray.sh
key10 key7 key8 key9
Run Code Online (Sandbox Code Playgroud)

*注意**:与给出的其他答案一样,如果数组中有重复的键,则只会报告一次; 这可能是也可能不是您正在寻找的行为.要处理的awk代码更加混乱,而不是干净.


Pau*_*ce. 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
Run Code Online (Sandbox Code Playgroud)

编辑:

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

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[@]})
Run Code Online (Sandbox Code Playgroud)


Ale*_*ore 6

拥有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"
Run Code Online (Sandbox Code Playgroud)

请注意,结果可能不符合源订单.

奖金又名"这就是你在这里":

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"
Run Code Online (Sandbox Code Playgroud)

使用那些棘手的逃避是处理在bash中传递的数组参数的最差的选择.

另外,看看手册comm页; 基于此代码,它非常容易实现,例如array_intersect:只需使用-12作为comm选项.

  • @lantrix,“mapfile”可以轻松地用“while..read”替换,如果不需要数组,甚至可以完全删除。所有的魔法都发生在“comm”中。 (2认同)

小智 5

也可以使用正则表达式(基于另一个答案: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[@]}:
Run Code Online (Sandbox Code Playgroud)

结果:

$ bash diff-arrays.sh 
4 7 10 12
Run Code Online (Sandbox Code Playgroud)

  • 似乎很奇怪,这在没有评论的情况下被否决了。如果有问题,请大家帮个忙,指出问题所在。 (2认同)