I have this code snippet running on several Linux boxes, and a Solaris 10 box with bash 3.6 (iirc). However, on a Solaris 11 box, with GNU bash, version 4.4.11(1)-release (sparc-sun-solaris2.11)
it gives the following error.
#!/bin/env bash
CLEAN_COUNT() {
local L_STRING=$(sed '/[^[:graph:][:space:]]/{
s/[^[:graph:][:space:]]//g; s/\[[0-9]*m//g; s/(B//g
}' <<<$*) || return 1
echo ${#L_STRING}
}
f() {
ARGS=($@)
echo $((${#ARGS[1]:-0} - $(CLEAN_COUNT ${ARGS[1]:-0}) ))
}
f one two three four
Error received: ./gather_data.bash: line 15: ${#ARGS[1]:-0} - $(CLEAN_COUNT ${ARGS[1]:-0}) : bad substitution
I've isolated the above code in it's own script, I've compared the shopt and set -o
settings on that box with another one. I'm perplexed. If I can get the code to work without the substitution, even if ARGS has no element 1 and I'm running set -o nounset
, then I will use another piece of code.
Changes affecting this happened in Bash 4.3 and Bash 4.4. Observe:
No error in Bash 4.2:
$ docker run --rm -it bash:4.2 bash -u
bash-4.2$ bash --version | head -n 1
GNU bash, version 4.2.53(2)-release (x86_64-pc-linux-musl)
bash-4.2$ declare -a var && echo "${#var[1]:-1}"
0
but this doesn't actually print my default value: var[1]
is the empty string, hence 0
. -u
seems to ignore that var
has no elements. There is no difference in behaviour between echo "${#var[1]:-1}"
, echo "${#var[1]}"
and echo "${#var[1]}"
, they all print 0
.
Bash 4.3 complains about unbound variable:
$ docker run --rm -it bash:4.3 bash -u
bash-4.3$ bash --version | head -n 1
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-musl)
bash-4.3$ declare -a var && echo "${#var[1]:-1}"
bash: var: unbound variable
Bash 4.4 complains about substitution:
$ docker run --rm -it bash:4.4 bash -u
bash-4.4$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-musl)
bash-4.4$ declare -a var && echo "${#var[1]:-1}"
bash: ${#var[1]:-1}: bad substitution
even without set -u
:
bash-4.4# set +o nounset
bash-4.4# declare -a var && echo "${#var[1]:-1}"
bash: ${#var[1]:-1}: bad substitution
Also, ${#var:-1}
is considered "bad substitution" in all versions, even without set -u
:
$ for v in 3.2 4.{0..4}; do docker run --rm -it bash:$v; done
bash-3.2# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-3.2# exit
exit
bash-4.0# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-4.0# exit
exit
bash-4.1# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-4.1# exit
exit
bash-4.2# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-4.2# exit
exit
bash-4.3# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-4.3# exit
exit
bash-4.4# echo "${#var:-1}"
bash: ${#var:-1}: bad substitution
bash-4.4# exit
exit
I can't see any mention of changes to this behaviour in NEWS
, but it seems to make sense, as ${#var[0]:-1}
doesn't default to 1
anyway, so now the behaviour is consistent across scalars and arrays.
This being said, I'd rewrite your function as follows:
f () {
local args=("$@")
if [[ -z ${args[1]:-} ]]; then
echo 0
else
echo $(( ${#args[1]} - $(clean_count "${args[1]}") ))
fi
}
args
local to function"$@"
in args to avoid splitting before assigning to array elementsargs[1]
is the empty string, make sure no unset complaint is triggered with ${args[1]:-}
Alternatively, if f ()
is not a simplification and you never access elements other than what you show, you could further simplify to
f () {
if [[ -z ${2:-} ]]; then
echo 0
else
echo $(( ${#2} - $(clean_count "$2") ))
fi
}