Seems to me that the easiest way to solve this is with nested for loops:
#!/usr/bin/env bash
a1=(cats cats.in catses dogs dogs.in dogses)
a2=(cats.in dogs.in)
for x in "${!a1[@]}"; do # step through a1 by index
for y in "${a2[@]}"; do # step through a2 by content
if [[ "${a1[x]}" = "$y" || "${a1[x]}" = "${y%.in}" ]]; then
unset a1[x]
fi
done
done
declare -p a1
But depending on your actual data, the following might be better, using two separate for loops instead of nesting.
#!/usr/bin/env bash
a1=(cats cats.in catses dogs dogs.in dogses)
a2=(cats.in dogs.in)
# Flip "a2" array to "b", stripping ".in" as we go...
declare -A b=()
for x in "${!a2[@]}"; do
b[${a2[x]%.in}]="$x"
done
# Check for the existence of the stripped version of the array content
# as an index of the associative array we created above.
for x in "${!a1[@]}"; do
[[ -n "${b[${a1[x]%.in}]}" ]] && unset a1[$x] a1[${x%.in}]
done
declare -p a1
The advantage here would be that instead of looping through all of a2 for each item in a1, you just loop once over each array. Down sides might depend on your data. For example, if contents of a2 are very large, you might hit memory limits. Of course, I can't know that from what you included in your question; this solution works with the data you provided.
NOTE: this solution also depends on an associative array, which is a feature introduced to bash in version 4. If you're running an old version of bash, now might be a good time to upgrade. :)