bash 变量替换不适用于 Solaris

bash variable substitution not working on Solaris

我在几个 Linux 盒子上有这个代码片段 运行,还有一个带有 bash 3.6 (iirc) 的 Solaris 10 盒子。但是,在 Solaris 11 机器上,使用 GNU bash, version 4.4.11(1)-release (sparc-sun-solaris2.11) 会出现以下错误。

#!/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

收到错误:./gather_data.bash: line 15: ${#ARGS[1]:-0} - $(CLEAN_COUNT ${ARGS[1]:-0}) : bad substitution

我已经将上面的代码隔离在它自己的脚本中,我已经将那个盒子上的 shopt 和 set -o 设置与另一个盒子进行了比较。我很困惑。如果我可以让代码在没有替换的情况下工作,即使 ARGS 没有元素 1 并且我是 运行 set -o nounset,那么我将使用另一段代码。

影响此的更改发生在 Bash 4.3 和 Bash 4.4 中。观察:

  • Bash4.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
    

    但这实际上并没有打印我的默认值:var[1] 是空字符串,因此 0-u 似乎忽略了 var 没有元素。 echo "${#var[1]:-1}"echo "${#var[1]}"echo "${#var[1]}" 之间的行为没有区别,它们都打印 0.

  • Bash 4.3 抱怨未绑定变量:

    $ 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 抱怨换人:

    $ 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
    

    即使没有set -u:

    bash-4.4# set +o nounset
    bash-4.4# declare -a var && echo "${#var[1]:-1}"
    bash: ${#var[1]:-1}: bad substitution
    

此外,${#var:-1} 在所有版本中都被视为 "bad substitution",即使没有 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

我在 NEWS 中看不到任何关于此行为更改的提及,但这似乎是有道理的,因为 ${#var[0]:-1} 无论如何都不会默认为 1,所以现在行为在标量和数组之间是一致的。

话虽如此,我将按如下方式重写您的函数:

f () {
    local args=("$@")
    if [[ -z ${args[1]:-} ]]; then
        echo 0
    else
        echo $(( ${#args[1]} - $(clean_count "${args[1]}") ))
    fi
}
  • 将大写变量名重命名为小写以避免与 shell 和环境变量
  • 冲突
  • 使 args 局部于函数
  • 在 args 中引用 "$@" 以避免在分配给数组元素之前拆分
  • 检查 args[1] 是否为空字符串,确保 ${args[1]:-}
  • 不会触发未设置的投诉
  • 分别处理空字符串和 non-empty 字符串的情况

或者,如果 f () 不是简化,并且您从不访问显示内容以外的元素,则可以进一步简化为

f () {
    if [[ -z ${2:-} ]]; then
        echo 0
    else
        echo $(( ${#2} - $(clean_count "") ))
    fi
}