Comparar / Diferencia de dos matrices en bash

¿Es posible tomar la diferencia de dos matrices en bash.
Sería realmente genial si pudieras sugerirme la forma de hacerlo.

Código:

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

Aprecio tu ayuda.

Si estrictamente quieres Array1 - Array2 , entonces

 $ Array3 = ()
 $ for i en "$ {Array1 [@]}";  hacer
 > saltar =
 > para j en "$ {Array2 [@]}";  hacer
 > [[$ i == $ j]] && {skip = 1;  descanso;  }
 > hecho
 > [[-n $ skip]] ||  Array3 + = ("$ i")
 > hecho
 $ declare -p Array3

El tiempo de ejecución podría mejorarse con arreglos asociativos, pero personalmente no me molestaría. Si está manipulando datos suficientes para que importe, shell es la herramienta incorrecta.


Para una diferencia simétrica como la respuesta de Dennis, las herramientas existentes, como la comm , funcionan siempre que masajeemos un poco la entrada y la salida (ya que funcionan en archivos basados ​​en líneas, no en variables de shell).

Aquí, le decimos al shell que use líneas nuevas para unir el conjunto en una sola cadena, y descarte las tabs cuando lee líneas de comm en una matriz.

 $ oldIFS = $ IFS IFS = $ '\ n \ t'
 $ Array3 = ($ (comm -3 <(echo "$ {Array1 [*]}") <(echo "$ {Array2 [*]}")))
 comm: el archivo 1 no está ordenado
 $ IFS = $ oldIFS
 $ declare -p Array3
 declare -a Array3 = '([0] = "clave7" [1] = "clave8" [2] = "clave9" [3] = "clave10")'

Se queja porque, por clasificación key1 < … < key9 > key10 . Pero como ambas matrices de entrada están ordenadas de manera similar, está bien ignorar esa advertencia. Puede usar --nocheck-order para deshacerse de la advertencia, o agregar un | sort -u | sort -u dentro de la sustitución del proceso <(…) si no puedes garantizar el orden y la singularidad de las matrices de entrada.

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

Salida

 key10 key7 key8 key9 

Puede agregar clasificación si necesita

Cada vez que aparece una pregunta que trata sobre valores únicos que pueden no ser ordenados, mi mente inmediatamente se vuelve loca. Aquí está mi opinión sobre esto.

Código

 #!/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[@]} 

Salida

 $ ./diffArray.sh key10 key7 key8 key9 

* Nota **: al igual que otras respuestas dadas, si hay claves duplicadas en una matriz, solo se informarán una vez; esto puede o no ser el comportamiento que estás buscando. El código awk para manejar es más desordenado y no tan limpio.

En 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 

Editar:

ephemient señaló un error potencialmente serio. Si un elemento existe en una matriz con uno o más duplicados y no existe en absoluto en la otra matriz, se eliminará incorrectamente de la lista de valores únicos. La siguiente versión intenta manejar esa situación.

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

Teniendo ARR1 y ARR2 como argumentos, use comm para hacer el trabajo y mapfile para ponerlo nuevamente en la matriz 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" 

Tenga en cuenta que el resultado puede no cumplir con el orden de origen.

Bonus alias "para eso estás aquí":

 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" 

Usar esas evaluaciones complicadas es la opción menos mala entre otras relacionadas con los parámetros de matriz que pasan en bash.

Además, eche un vistazo a la página de manual de comm ; basado en este código, es muy fácil de implementar, por ejemplo, array_intersect : simplemente use -12 como opciones de comunicación.

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

salida

 $ ./shell.sh Array4: key7 key8 key9 key10 Array4: key11 

Es posible usar expresiones regulares también (basado en otra respuesta: intersección de matriz en 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[@]}: 

Resultado:

 $ bash diff-arrays.sh 4 7 10 12