3

I am trying to test to see if an element inside an array exists. A test case is

    array1=(a b c)
    array2=(c d e)
    
  for ((i=0; i < ${#array1[@]}; i++)); do
    if [[ ${array1[$i]} == "${array2[${array1[$i]}]}" ]]; then
        echo "${array1[$i]}"
    fi
  done

this looks like it works as expected and prints c into the terminal. The issue is if i try and test for something like a file where it will have .sh as an extension for example you an error. Here is the example code followed by the error.

   array1=(a b c.sh)
    array2=(c.sh d e)
    
  for ((i=0; i < ${#array1[@]}; i++)); do
    if [[ ${array1[$i]} == "${array2[${array1[$i]}]}" ]]; then
        echo "${array1[$i]}"
    fi
  done

bash: c.sh: syntax error: invalid arithmetic operator (error token is ".sh")

I have tried removing the extension by using ::-2 for array1 but that did not work for me. How can i tell if an array element is inside another array in bash, if there is a file extension at the end?

EDIT: Arrays do not work like i think they did it was a happy accident the code works like that. See the marked answer for more details.

0

2 Answers 2

8

The thought was array2[c] = c which meant array1 would see "c" and therefore be true

No, array indexing does not work like that. Currently both your arrays are numerically indexed; the index of the item 'c' in array2 is 0 and that's it – there is no additional feature that would automatically make an element have itself as its index.

So the code really did not work as intended in the first place; it just so happened that array2[c] was interpreted as array2[0], because the "c" in numeric context was taken to refer to the variable named 'c' – and since you have no such variable, the result quietly became 0 which coincides with the result you wanted.

But if you tried the same thing with elements ordered a bit differently, you'd notice that ${array2[d]} also produces the item "c", and ${array2[e]} also produces "c", and if you do e=1 then ${array2[e]} produces "d". In all those cases, the array index is interpreted as an arithmetic expression (e.g. it allows ${array2[i+2]} and similar), not as a string to search for.

With regular, numerically indexed arrays, you would need to loop over the indexes of both arrays to find a matching item:

for (( i = 0; i < ${#array1[@]}; i++ )); do
    for (( j = 0; j < ${#array2[@]}; j++ )); do
        compare array1[i] to array2[j] here
    done
done

This is of course a bit slower since it might need n × m checks.

To actually make array2[c] work, you would need to turn array2 into an associative array using declare -A. Associative arrays (aka 'dicts' or 'maps') use strings as keys/indices, which you need to specify for each element using the [key]=value syntax.

Just to match the way you were trying to use the arrays, let's make the keys identical to the values:

declare -A array2=( [b]=b [c.sh]=c.sh [d]=d [e]=e )

for (( i = 0; i < ${#array1[@]}; i++ )); do
    if [[ ${array1[i]} == "${array2[${array1[i]}]}" ]]; then
        echo "item '${array1[i]}' is present in array2"

Now ${array2[e]} will actually return the value e and the comparison will work.

But since the goal is to imitate sets, you could also go further and get rid of the (redundant) values entirely, replacing the equality test with a simpler "is value present" test:

declare -A array2=([b]=1 [c.sh]=1 [d]=1 [e]=1)

for (( i = 0; i < ${#array1[@]}; i++ )); do
    if [[ ${array2[${array1[i]}]} ]]; then
        echo "item '${array1[i]}' is present in array2"

As a completely unrelated simplification, ${!var[@]} expands to array indices is an easier way to iterate over an array than the C-style loop:

for i in "${!array1[@]}"; do
    if [[ ${array2[${array1[i]}]} ]]; then

But in this case you're not using i for anything except retrieving the value, so you could simplify it further by iterating directly over the values of array1:

for item in "${array1[@]}"; do
    if [[ ${array2[$item]} ]]; then

This also works if both arrays are made into dicts, using the same ${!var[@]} to iterate over the string-based indices:

declare -A array1=([a]=yes [b]=present [c.sh]=sure)
declare -A array2=([b]=1 [c.sh]=1 [d]=1 [e]=1)

for item in "${!array1[@]}"; do
    if [[ ${array2[$item]} ]]; then
        echo "Item '$item' is present as a key in both arrays"
    fi
done
2
  • Thanks for the well explained answer. I had never seen ${!array[@]} before whenever i looked up info about arrays in bash. Are there other symbols or syntax that I should know? Commented Jun 18 at 17:26
  • Probably the only thing I would suggest is declare -p var that shows everything about a variable, specifically all the keys and values of an array. Otherwise, the "Parameter Expansion" section in Bash's manual might be worth reading, but nothing really related to this thread. Commented Jun 18 at 17:53
0

The marked answer is best if you're trying to use only bash features, but if you're willing to use grep, you can come up with something simpler:

array=(a c b "f f" test.sh 3 5)
array2=(b d "f f" test.sh test.sh 7)

for i in "${array[@]}"; do echo "${array2[@]}" | grep -o "$i"; done

This will echo each entry in array2 that matches array. It will handle any well-formed entries in your array, and it will preserve duplicates (not sure if this is what you want.)

This also works for associative arrays, following @grawity_u1686's example:

declare -A array1=([a]=yes [b]=present [c.sh]=sure)
declare -A array2=([b]=1 [c.sh]=1 [d]=1 [e]=1)

To iterate the keys, we can do this:

for i in "${!array1[@]}"; do echo "${!array2[@]}" | grep -o "$i"; done

As you would expect, this prints

c.sh
b

To iterate the values, instead we do

for i in "${array1[@]}"; do echo "${array2[@]}" | grep -o "$i"; done

As you would expect, this prints nothing.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .