How can I prevent unsupported 'shopt' options from causing errors in my .bashrc?
I don't see what's wrong with redirecting errors to /dev/null
. If you want your code to be robust to set -e
, use the common idiom … || true
:
shopt -s direxpand 2>/dev/null || true
If you want to run some fallback code if the option does not exist, use the return status of shopt
:
if shopt -s direxpand 2>/dev/null; then
… # the direxpand option exists
else
… # the direxpand option does not exist
fi
But if you really dislike redirecting the error away, you can use the completion mechanism to perform introspection. This assumes that you don't have antiquated machines with bash ≤ 2.03 that didn't have programmable completion.
shopt_exists () {
compgen -A shopt -X \!"$1" "$1" >/dev/null
}
if shopt_exists direxpand; then
shopt -s direxpand
fi
This method avoids forking, which is slow on some environments such as Cygwin. So does the straightforward 2>/dev/null
, I don't think you can beat that on performance.
Check if direxpand
is present in the output of shopt
and enable it if it is:
shopt | grep -q '^direxpand\b' && shopt -s direxpand
When you know for sure that a specific shopt
option is available at a certain major/minor/patch release of Bash, you can inspect the $BASH_VERSION
variable or the elements of the $BASH_VERSINFO[]
array in order to enable it conditionally.
Here's a test for Bash 4.2.29 or greater, the version where direxpand
was first introduced to the 4.2 series:
if [[ $BASH_VERSION == 4.2.* && ${BASH_VERSINFO[2]} -ge 29 ]] ||
[[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 3 ]] ||
[[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
shopt -s direxpand
fi
Edit: To be clear, this is a ridiculously over-engineered solution for simply ignoring an error message coming from your login scripts, but I did want to document it regardless, for my own edification.
Note the braces around ${BASH_VERSINFO[index]}
, which are required, and the use of -eq
and -gt
, which do integer rather than (locale-dependent) lexical comparisons. If unquoted, the RHS of the ==
operator is treated as "extglob" patterns within Bash [[
/]]
conditionals, as noted here, which makes a more aesthetic "starts with" comparison than a regex would, IMO.
The $BASH_VERSINFO
array contains all the information you'd see in the output of bash --version
:
bash --version | head -1
# result:
# GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
declare -p BASH_VERSINFO
# result:
# declare -ar BASH_VERSINFO='([0]="4" [1]="3" [2]="48" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")'
When it isn't clear from the documentation for shopt
at which Bash version(s) became supported or changed their behavior, the method proposed by Luciano is fine:
# note the '-q' so that the matched pattern isn't actually printed
shopt | grep -q direxpand && shopt -s direxpand
...as is the solution proposed by Gilles of just ignoring the error (shopt -s direxpand 2>/dev/null
), and perhaps checking $?
if absolutely necessary.
References: 1, 2, 3
Related reading: Set and Shopt - Why Two?