If you want to write a shell script or “library”, you may want to test whether the current shell has a given feature or not, for instance for performance concerns.

For instance, if you want to check if a string matches a regular expression and have your script works in a POSIX shell, you will use an external program such has “grep”, “sed” or whatever, which is costly and useless if your script is in fact running in Bash or Zsh which built in support for regular expression matching.

  1. # The POSIX way:
  2. if printf '%s' "$string" | grep --regexp-extended --quiet --regexp="$regexp"
  3. then
  4. # Do what you want.
  5. fi
  6.  
  7. # The Bash way:
  8. if [[ $string =~ $regexp ]]
  9. then
  10. # Do what you want.
  11. fi

A solution to this problem is to define a function for matching a string against a regular expression which will use the built in support if available otherwise the external program.

But how to detect whether a feature is supported or not?

The answer is not difficult but not obvious either.

First, you have to know that, when the shell meet a invalid syntax, it stops the parsing, displays an error message and returns a value different than 0. It does so also with the eval command which can be put in a subshell to contain the parsing error and to prevent it from stopping our script.

  1. code='echo ${string:0:10}'
  2.  
  3. if (eval "$code") 2> /dev/null
  4. then
  5. echo 'Impossible to extract a substring in this shell!' >&2
  6. exit 1
  7. fi

But, because when eval succeeds it returns the return value of the code evaluated, you may use codes which return 0.

To encapsulate this trick, I propose you the following function:

  1. # Checks whether this shell has a given features (variable substitutions, …).
  2. #
  3. # The code passed must be assignable to a variable, so you must use command
  4. # substitution if you want to test a command.
  5. #
  6. # have_feature CODE
  7. have_feature()
  8. {
  9. (eval __have_feature="$1") > /dev/null 2>&1
  10. }

Here are some use cases:

  1. if have_feature '${#VAR}'
  2. then
  3. echo This shell has the length variable substitution.
  4. fi
  5.  
  6. if have_feature '$([[ word =~ . ]])'
  7. then
  8. echo This shell has built in regular expression support.
  9. fi

As usual, this function is defined in my shell library jfsh and if you want to see which features as your shell you may be interested in shell-info.

If you have any questions or remarks, please, let me a comment.