Подстановка переменных bash не работает в Solaris

У меня есть этот фрагмент кода, работающий на нескольких Linux-блоках, и Solaris 10 с bash 3.6 (iirc). Тем не менее, на коробке 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

Я выделил приведенный выше код в своем собственном скрипте, я сравнил set -o Настройки на этом поле с другим. Я в замешательстве. Если я смогу заставить код работать без подстановки, даже если у ARGS нет элемента 1 и я работаю set -o nounsetтогда я буду использовать другой кусок кода.

1 ответ

Решение

Изменения, влияющие на это, произошли в Bash 4.3 и Bash 4.4. Заметим:

  • Нет ошибки в 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
    

    но на самом деле это не выводит мое значение по умолчанию: 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} считается "плохой заменой" во всех версиях, даже без 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
}
  • Переименуйте имена переменных в верхнем регистре в строчные, чтобы избежать конфликта с переменными оболочки и среды
  • Делать args местный, чтобы функционировать
  • котировка "$@" в аргументах, чтобы избежать разбиения перед назначением элементов массива
  • Проверить, если args[1] пустая строка, убедитесь, что не запущена жалоба ${args[1]:-}
  • Лечить случаи для пустой и непустой строки отдельно

В качестве альтернативы, если f () это не упрощение, и вы никогда не получите доступ к элементам, кроме того, что вы показываете, вы могли бы еще больше упростить

f () {
    if [[ -z ${2:-} ]]; then
        echo 0
    else
        echo $(( ${#2} - $(clean_count "$2") ))
    fi
}
Другие вопросы по тегам