Jérèm Le Blond Jérèm Le Blond - 2 months ago 8
Bash Question

sudo, Bad substitution, indirect parameter expansion

Here a script that try to do a 'ifndef' (ifndef.sh):

#!/bin/bash

################# __ifndef__ #################
MAIN_SCRIPT=$(cd "$(dirname "$0")" && pwd -P)"/$(basename $0)"
script_path=$(echo $MAIN_SCRIPT | sed -e "s@/@_@g" -e "s@\.@_@g" -e "s@-@_@")
define_var="ALREADY_SOURCED_$script_path"
echo "Main_script: $MAIN_SCRIPT"
echo "script_path: $script_path"
echo "define_var: $define_var"
echo "??: ${!define_var}"
[[ ! -z ${!define_var} ]] && return
export ALREADY_SOURCED_$script_path="defined"
################# __ifndef__ #################


When I try to execute this code in an another file (
main.sh
):

. ./ifndef.sh

echo "TEST !"


With sudo, I always get this error:

./main.sh: 10: ./ifndef.sh: Bad substitution


The command I used to launch the script is:
./main.sh


I don't know why
${!define_var}
is problematic.

Answer

You can't run a bash script with sh -- it can only be run with bash. In particular, you're using several features here (including both indirect expansion a la ${!foo} and the extended test operator [[ ]]) that aren't part of the POSIX sh standard, and so aren't guaranteed to be available when run with sh.

Instead of running sh yourscript, run bash yourscript or just ./yourscript (with a shebang set and execute permissions granted); and instead of starting it with #!/bin/sh, start it with #!/bin/bash. If this is being sourced, ensure that the script you're sourcing it from is itself started with bash.


The other thing here is that your escaping isn't complete enough to ensure that your $script_path really is a valid shell variable name. Instead of trying to fix that, just switch to using an associative array -- that ways keys can contain any character other than NUL, which is invalid in UNIX paths anyhow:

#!/usr/bin/env bash
# uses the first bash interpreter on the PATH, so on MacOS X, can use MacPorts bash
# if sourcing this, be sure to do the same in the script you're sourcing it from.

### start version check
[ -n "$BASH_VERSION" ] || {
  echo "Current shell is not bash; must use bash 4.0 or later" >&2
  return 1 || exit 1
}
[[ $BASH_VERSION =~ ^([4-9][.]|[0-9][0-9]+) ]] || {
  echo "Bash version 4 or newer is required (using $BASH_VERSION)" >&2
  return 1 || exit 1
}
### end version check

### start redefinition check
declare -A already_sourced
[[ ${already_sourced[$BASH_SOURCE]} ]] && return
already_sourced[$BASH_SOURCE]=1
### end redefinition check

The above requires Bash 4.x, which isn't shipped with MacOS, but is available through MacPorts.

Comments