web-dev-qa-db-ja.com

bashの2つの配列の比較/差分

Bashで2つの配列の違いを取ることは可能ですか?.
あなたが私にそれを行う方法を提案できれば本当に素晴らしいでしょう。

コード:

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" )

あなたの助けに感謝。

45
Kiran

厳密に_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
_

連想配列を使用するとランタイムが改善される可能性がありますが、個人的には気にしません。そのために十分なデータを操作している場合、シェルは間違ったツールです。


デニスの答えのような対称的な違いについては、入力を少し処理し、シェル変数ではなく行ベースのファイルで動作するため、 comm のような既存のツールが機能します)。

ここでは、改行を使用して配列を単一の文字列に結合し、commから配列に戻る行を読み取るときにタブを破棄するようシェルに指示します。

 $ oldIFS = $ IFS IFS = $ '\ n\t' 
 $ Array3 =($(comm -3 <(echo "$ {Array1 [*]}")<(echo " $ {Array2 [*]} ")))
 comm:ファイル1はソートされた順序ではありません
 $ IFS = $ oldIFS 
 $ declare -p Array3 
 declare -a Array3 = '([0] = "key7" [1] = "key8" [2] = "key9" [3] = "key10")' 

文句のソートにより、_key1 < … < key9 > key10_であるため、文句を言います。ただし、両方の入力配列は同様にソートされるため、その警告を無視しても問題ありません。 _--nocheck-order_を使用して警告を取り除くか、入力配列の順序と一意性を保証できない場合は、<(…)プロセス置換内に_| sort -u_を追加できます。

28
ephemient
echo ${Array1[@]} ${Array2[@]} | tr ' ' '\n' | sort | uniq -u

出力

key10
key7
key8
key9

必要に応じて並べ替えを追加できます

102
Ilya Bystrov

ソートされていない可能性のある一意の値を処理する質問がポップアップするたびに、私の心はすぐに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コードは厄介で、それほどきれいではありません。

15
SiegeX

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潜在的に深刻なバグを指摘しました。要素が1つ以上の重複を持つ1つの配列に存在し、他の配列にまったく存在しない場合、一意の値のリストから誤って削除されます。以下のバージョンはその状況を処理しようとします。

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

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"

これらのトリッキーなevalsを使用することは、bashで渡す配列パラメーターを扱う他の中で最も最悪のオプションです。

また、commマンページをご覧ください。このコードに基づいて、非常に簡単に実装できます。たとえば、array_intersect:通信オプションとして-12を使用します。

6
Alex Offshore
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
2
ghostdog74

正規表現を使用することも可能です(別の答えに基づいて: 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
2
Denis Gois