0

I have two arrays, how would I get the list of elements that appear exclusively in the second, that are not available in the first?

Array1=( "A" "B" "C" "D" ) Array2=( "B" "E" "G" )

I need the output as Array3=("E" "G") because E and G are not present in Array1. I used @ephemient @SiegeX answers but that is not returning what I need.

function arraydiff() {
   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=( "A" "B" "C" "D" )
Array2=( "B" "E" "G" )
Array3=($(arraydiff Array1[@] Array2[@]))
hmedia1
  • 5,552
  • 2
  • 22
  • 27
Eva
  • 515
  • 4
  • 28

4 Answers4

6

Use an associative array to store the elements of the first array, and see if elements of the second array appear as keys in it:

#!/usr/bin/env bash

arraydiff() {
    # Use namerefs for the arrays to work around not being to pass
    # two different arrays as function arguments otherwise
    local -n a1=$1 a2=$2
    # Populate an associative array with the elements of the first array
    local -A elems
    local elem
    for elem in "${a1[@]}"; do
        elems[$elem]=1
    done
    # If an element of the second array doesn't appear in the associative
    # array, print it.
    for elem in "${a2[@]}"; do
        if [[ ! -v elems[$elem] ]]; then
            printf "%s\n" "$elem"
        fi
    done
}

declare -a array1=( A B C D ) array2=( B E G )
readarray -t array3 < <(arraydiff array1 array2)
printf "%s\n" "${array3[@]}"
Shawn
  • 47,241
  • 3
  • 26
  • 60
6

Use comm. The following:

comm -13 <(printf "%s\n" "${array1[@]}" | sort) <(printf "%s\n" "${array2[@]}" | sort)

will output E and G. You can then read it to an array with readarray.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
2

With bash:

#!/bin/bash

array1=( "A" "B" "C" "D" )
array2=( "B" "E" "G" )
array3=("${array2[@]}")

for i in "${array2[@]}"; do
  for j in "${array1[@]}"; do
    [[ "$i" == "$j" ]] && unset array3[$i]
  done
done

array3=("${array3[@]}")      # reassemble array3
declare -p array3

Output:

declare -a array3=([0]="E" [1]="G")
Cyrus
  • 84,225
  • 14
  • 89
  • 153
  • Great answer. What's the point of `declare -p array3` ? – hmedia1 Jul 04 '21 at 20:20
  • @hmedia1: This only outputs array3, where you can see what is in which element. You can see the difference if you leave out `array3=("${array3[@]}")`. – Cyrus Jul 04 '21 at 20:21
  • FYI, @Cyrus -- there's an answer that's intended to be a comment directed to you at https://stackoverflow.com/a/74540321/14122 – Charles Duffy Nov 22 '22 at 23:59
0

It looks like I still don't have enough reputation points to comment.

The answer given by @Cyrus just happened to give the right answer with the example used but is not a general answer. I think this is because the logic is effectively comparing indices rather than values corresponding to said indices.

To see this just try other combinations for array_2.

For example:

array2=( "B" "C" "E" "G" )

will give ("C" "E" "G") (should be ("E" "G")).

Or:

array2=( "E" "G" "B" )

will give ( "G" "B" ) (should be ("E", "G")).

A modification that will work is as follows:

#!/bin/bash

array1=( "A" "B" "C" "D" )
array2=( "C" "E" "G" "B" )
array3=("${array2[@]}")

array1_card=${#array1[@]}
array2_card=${#array2[@]}


for (( i=0; i<array2_card; i++ )); do
  for (( j=0; j<array1_card; j++ )); do
    [[ ${array2[i]} == ${array1[j]} ]] && unset array3[$i]
  done
done

array3=("${array3[@]}")      # reassemble array3
declare -p array3
cosmicaug
  • 33
  • 5