簡體   English   中英

Bash中兩個數組的比較/差異

[英]Compare/Difference of two arrays in Bash

是否可以在 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" )
echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u

輸出

key10
key7
key8
key9

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

如果你嚴格想要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行讀回數組時丟棄制表符。

$ 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 如果您不能保證輸入數組的順序和唯一性,請在<(…)過程替換中使用| sort -u

每當出現處理可能無法排序的唯一值的問題時,我的思緒都會立即進入 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 代碼比較混亂,而且不那么干凈。

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"

使用那些棘手的 eval 是處理傳入 bash 的數組參數的最不糟糕的選擇。

另外,請查看comm聯機幫助頁; 基於此代碼,它很容易實現,例如, array_intersect :只需使用 -12 作為通信選項。

在 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

編輯:

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

也可以使用正則表達式(基於另一個答案: 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
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

@ilya-bystrov 最受好評的答案計算了Array1Array2的差異。 請注意,這是一樣的去除項目Array1 ,同時也是在Array2 @ilya-bystrov 的解決方案是連接兩個列表並刪除非唯一值。 Array2包含不在Array1項目時,這是一個巨大的差異: Array3將包含在Array2中但不在Array1

下面是去除項目純巴什解決方案Array1 ,同時也是在Array2 (注意額外的"key11"Array2 ):

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中包含重復的值,必須先刪除重復的(注意重復"key10"Array1 ):

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很大,情況也是如此:這對於很多項目來說是一個非常低效的解決方案,即使對於少數項目 (<100) 來說可以忽略不計。 如果您需要處理大型數組,請不要使用 Bash。

此代碼替換為diff

echo ${test1[@]} ${test2[@]} | sed 's/ /\n/g' | sort | uniq -u

對於結果反向使用uniq -d

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM