[英]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 代碼比較混亂,而且不那么干凈。
將ARR1
和ARR2
作為參數,使用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 最受好評的答案計算了Array1
和Array2
的差異。 請注意,這是不一樣的去除項目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' 接受的答案。 如果Array1
和Array2
很大,情況也是如此:這對於很多項目來說是一個非常低效的解決方案,即使對於少數項目 (<100) 來說可以忽略不計。 如果您需要處理大型數組,請不要使用 Bash。
此代碼替換為diff
echo ${test1[@]} ${test2[@]} | sed 's/ /\n/g' | sort | uniq -u
對於結果反向使用uniq -d
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.