sh: доступ к значению переменной по имени строки - PullRequest
1 голос
/ 02 апреля 2019

Я написал короткий скрипт, который проверяет наличие переменной окружения и завершается ошибкой, если она не существует. Цель этого сценария - использовать его на CI-сервере, на котором переменные конфигурации загружаются через консоль. Я хочу убедиться, что эти переменные были установлены, и провалить задание заранее, если они этого не сделали.

Средой выполнения этих заданий является контейнер Docker на основе Alpine Linux. У меня есть доступ только к sh. . Я бы хотел избежать установки другой оболочки, например bash, чтобы размер изображения был как можно меньше.

Сценарий выглядит примерно так:

#!/bin/sh

AWS_ACCESS_KEY_ID=123  # provided by CI

 _fail_without() {
  VAR_NAME=$1
  VAR_VAL=$(eval echo "\$$VAR_NAME")

   if [[ -z "${VAR_VAL}" ]]; then
    echo "${VAR_NAME} not set; aborting"
    exit 1
  else
    echo "${VAR_NAME} exists"
  fi
}

_fail_without AWS_ACCESS_KEY_ID
_fail_without AWS_SECRET_ACCESS_KEY

... с ожидаемым stdout:

AWS_ACCESS_KEY_ID exists
AWS_SECRET_ACCESS_KEY not set; aborting

Как видите, я передал строковое значение имени переменной, а не саму переменную, так что сбои будут правильно регистрироваться. Все это прекрасно работает. Тем не менее, я обеспокоен потенциальными последствиями безопасности для доступа к eval для доступа к значению переменной в строке VAR_VAL=$(eval echo "\$$VAR_NAME").

Вопрос в том: является ли это жизнеспособным подходом, есть ли какие-либо последствия для безопасности, о которых следует знать, и, если есть, есть ли более безопасная или лучшая альтернатива? Я не могу использовать declare, и printf, похоже, не ведет себя так же, как в bash.

1 Ответ

1 голос
/ 03 апреля 2019

После некоторого задержания, прочитав руководство по оболочке posix и найдя что-нибудь полезное в утилитах posix Я наконец решил, что буду использовать расширения переменных ${var:?} и ${var?}, чтобы проверить, является ли переменнаяустановлено или не установлено, равно нулю или не равно нулю, и что я буду использовать утилиту expr с регулярным выражением BRE posix, чтобы проверить, является ли переменная допустимым именем переменной.

Ниже приведены функции, с которыми я закончил.Небольшая тестовая функция и некоторые тестовые случаи заканчиваются.Я чувствую, что сопоставление expr BRE - самая непереносимая часть всего этого, однако я не смог найти ложных срабатываний в var_is_name.

#!/bin/sh

# var ####################################################################################################

#
# Check if arguments are a valid "name" identifier in the POSIX shell contects
# @args identifiers
# @returns
# 0 - all identifiers are valid names
# 1 - any one of identifiers is not a valid name
# 2 - internal error
# 3 - even worse internal error
var_is_name() {
    # 3.230 Name
    # In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.
    local _var_is_name_i
    for _var_is_name_i; do
        expr "$_var_is_name_i" : '[_a-zA-Z][_a-zA-Z0-9]*$' >/dev/null || return $?
    done
}

# @args identifiers
# @returns Same as var_is_name but returns `2` in case if any of the arguments is not a valid name
var_is_name_error_on_fail() {
    local _var_is_name_error_on_fail_ret
    var_is_name "$@" && _var_is_name_error_on_fail_ret=$? || _var_is_name_error_on_fail_ret=$?
    if [ "$_var_is_name_error_on_fail_ret" -eq 1 ]; then return 2
    elif [ "$_var_is_name_error_on_fail_ret" -ne 0 ]; then return "$_var_is_name_error_on_fail_ret"
    fi
}

# @args identifiers
# @returns 
# 0 - if all identifiers are set
# 1 - if any of the identifiers is not set
# other - in case of error (ex. any of the identifiers is not a valid name)
var_is_set() {
    var_is_name_error_on_fail "$@" || return $?
    local _var_is_set_i
    for _var_is_set_i; do
        if ! ( eval printf %.0s "\"\${$_var_is_set_i?}\"" ) 2>/dev/null; then
            return 1
        fi
    done
    return 0
}

# @args identifiers
# @returns 
# 0 - if all identifiers are null
# 1 - if any of the identifiers is not null
# other - in case of error (ex. any of the identifiers is not a valid name)
var_is_null() {
    var_is_name_error_on_fail "$@" || return $?
    var_is_set "$@" || return $?
    local _var_is_null_i
    for _var_is_null_i; do
        ( eval printf %.0s "\"\${$_var_is_null_i:?}\"" ) 2>/dev/null || return 0
    done
    return 1
}

# @args identifiers
# @returns 
# 0 - if all identifiers are not null
# 1 - if any of the identifiers is null
# other - in case of error (ex. any of the identifiers is not a valid name)
var_is_not_null() {
    var_is_name_error_on_fail "$@" || return $?
    var_is_set "$@" || return $?
    local _var_is_not_null_ret
    var_is_null "$@" && _var_is_not_null_ret=$? || _var_is_not_null_ret=$?
    if [ "$_var_is_not_null_ret" -eq 0 ]; then
        return 1
    elif [ "$_var_is_not_null_ret" -eq 1 ]; then
        return 0;
    fi
    return "$_var_is_not_null_ret"
}

#################################################################################################################

var_test() {
    local ret

    var_is_name "$@" && ret=$? || ret=$?
    if [ "$ret" -eq 0 ]; then printf "%s is %s\n" "$1" "name"
    elif [ "$ret" -eq 1 ]; then printf "%s is not %s\n" "$1" "name"
    else printf "err var_is_name %s %s\n" "$1" "$ret"; fi

    var_is_set "$@" && ret=$? || ret=$?
    if [ "$ret" -eq 0 ]; then printf "%s is %s\n" "$1" "set"
    elif [ "$ret" -eq 1 ]; then printf "%s is not %s\n" "$1" "set"
    elif [ "$ret" -eq 2 ]; then printf "var_is_set %s errored\n" "$1"
    else printf "err var_is_set %s %s\n" "$1" "$ret"; fi

    var_is_null "$@" && ret=$? || ret=$?
    if [ "$ret" -eq 0 ]; then printf "%s is %s\n" "$1" "null"
    elif [ "$ret" -eq 1 ]; then printf "%s is not %s\n" "$1" "null"
    elif [ "$ret" -eq 2 ]; then printf "var_is_null %s errored\n" "$1"
    else printf "err var_is_null %s %s\n" "$1" "$ret"; fi

    var_is_not_null "$@" && ret=$? || ret=$?
    if [ "$ret" -eq 0 ]; then printf "%s is %s\n" "$1" "not_null"
    elif [ "$ret" -eq 1 ]; then printf "%s is not %s\n" "$1" "not_null"
    elif [ "$ret" -eq 2 ]; then printf "var_is_not_null %s errored\n" "$1"
    else printf "err var_is_not_null %s %s\n" "$1" "$ret"; fi

    echo
}

var_test '$()'
var_test '$()def'
var_test 'abc$()'
var_test 'abc$()def'
echo "unset a"; var_test a
a=; echo "a=$a"; var_test a
a=""; echo "a=\"\""; var_test a
a='$(echo I will format your harddrive >&2)'; echo "a='$a'"; var_test a
a='!@$%^&*(){}:"|<>>?~'\'; echo "a='$a'"; var_test a

При запуске внутри alpine скрипт будетoutput:

# the script saved in /tmp/script.sh
$ chmod +x /tmp/script.sh
$ docker run --rm -ti -v /tmp:/mnt alpine /mnt/script.sh
$() is not name
var_is_set $() errored
var_is_null $() errored
var_is_not_null $() errored

$()def is not name
var_is_set $()def errored
var_is_null $()def errored
var_is_not_null $()def errored

abc$() is not name
var_is_set abc$() errored
var_is_null abc$() errored
var_is_not_null abc$() errored

abc$()def is not name
var_is_set abc$()def errored
var_is_null abc$()def errored
var_is_not_null abc$()def errored

unset a
a is name
a is not set
a is not null
a is not not_null

a=
a is name
a is set
a is null
a is not not_null

a=""
a is name
a is set
a is null
a is not not_null

a='$(echo I will format your harddrive >&2)'
a is name
a is set
a is not null
a is not_null

a='!@$%^&*(){}:"|<>>?~''
a is name
a is set
a is not null
a is not_null

Тем не менее, я думаю, что это слишком много хлопот для простой проверки "набор переменных или нет".Иногда я просто доверяю другим, они не будут делать странные вещи, и если они это сделают, это сломает их компьютер, а не мой.Поэтому иногда я бы посоветовал просто согласиться на простые решения, подобные вашему, - [ -n "$(eval echo "\"\${$var}\"")" ] && echo "$var is set" || echo "$var is not set иногда достаточно, когда вы доверяете своим данным.

...