0

Following on from this question, how can one use Bash to check if an array contains values from another array?

The reason this comes up is because I'd like to get a list of commands available to a user into an array and then check that against the dependencies for my script, as a way to test the "environment is ready" before continuing. The reason I use compgen for this is because it's a builtin and is also available on shared hosts that have jailshells.

Here's what I have:

#!/bin/bash

readarray available_commands < <(compgen -c)

required_commands=(
  grep
  sed
  rsync
  find
  something-to-fail
)

for required_command in "${required_commands[@]}"; do
  if [[ ${available_commands[*]} =~ ${required_command} ]]; then
    echo "${required_command} found"
  else
    echo "${required_command} not found"
  fi
done

It seems to work, but reading through the other answer about IFS and so forth, what am I missing here? Or how could the approach be improved, sticking with pure Bash?

nooblag
  • 678
  • 3
  • 23

3 Answers3

3

You could use comm which takes two sorted lists, and outputs a list of items, indented into three columns according to whether they appeared only in the one list, the other list, or both. Individual columns can be suppressed using the -1, -2, and -3 flags.

missing_commands=($(
  comm -23 <(printf "%s\n" "${required_commands[@]}" | sort) <(compgen -c | sort)
))
$ printf "%s\n" "${missing_commands[@]}"
rsync
something-to-fail
pmf
  • 24,478
  • 2
  • 22
  • 31
  • 1
    As OP's checking the existence of `grep`, `sed`, `find`, etc... then don't you think that `comm` and `sort` might not be available? – Fravadona Aug 20 '23 at 14:00
  • Fair point. They could be tested separately with a static list, while this approach can then be used with a dynamic list of all the other commands. – pmf Aug 20 '23 at 14:03
3

You don't need compgen as you can directly check the existence of a command with command -v

#!/bin/bash

required_commands=(
  grep
  sed
  rsync
  find
  something-to-fail
)

for cmd in "${required_commands[@]}"
do
    if command -v "$cmd" >&3
    then
        echo "$cmd found"
    else
        echo "$cmd not found"
    fi
done 3>/dev/null
Fravadona
  • 13,917
  • 1
  • 23
  • 35
2

You need readarray -t, and here it's easier to use pattern (instead of regex) for comparison :

#!/bin/bash

readarray -t available_commands < <(compgen -c)

required_commands=(
  grep
  sed
  rsync
  find
  something-to-fail
)

for required_command in ${required_commands[@]}; do
  if [[ " ${available_commands[*]} " = *\ ${required_command}\ * ]]; then
    echo "${required_command} found"
  else
    echo "${required_command} not found"
  fi
done

-t is mandatory, otherwise, newline(\n) is the last character of each command.

If you use regex, a dot(.) in the command would match any character.

Philippe
  • 20,025
  • 2
  • 23
  • 32
  • That's great, thanks. Why do you think `-t` is better in this case for `readarray` though? And I assume pattern matching is faster than regex, and that's also a bonus? – nooblag Aug 20 '23 at 14:38