sh:按字符串名称访问变量值

时间:2019-04-02 13:46:56

标签: shell continuous-integration alpine

我写了一个简短的脚本来检查环境变量的存在,如果不存在则失败。该脚本的目的是在CI服务器上使用,该CI服务器具有通过控制台加载的配置变量。我想确保已设置这些变量,如果尚未设置,则会使作业失败。

这些作业的执行环境是基于Alpine Linux的Docker容器。 我只能访问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

...具有预期的标准输出:

AWS_ACCESS_KEY_ID exists
AWS_SECRET_ACCESS_KEY not set; aborting

如您所见,我已经传递了变量名的 string值,而不是变量本身,以便可以正确记录故障。所有这些都很好。但是,我担心依靠eval来访问行VAR_VAL=$(eval echo "\$$VAR_NAME")中的变量值可能带来的安全隐患。

问题是:这是一种可行的方法,是否有任何需要注意的安全隐患,如果存在,是否有更安全或更佳的选择?我无法使用declare,并且printf的行为似乎也与bash中的行为不同。

1 个答案:

答案 0 :(得分:1)

经过一番居住后,阅读posix shell manual并在posix utilities中找到任何好处,我终于解决了,我将使用变量扩展${var:?}${var?}来检查是否设置了变量或unset,null或not null,并且我将结合使用expr实用程序和BRE posix regex来检查变量是否为有效的变量名。

下面是我最终获得的功能。最后是一个小的测试功能和一些测试用例。我觉得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内部运行时,脚本将输出:

# 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有时在您信任您的输入时就足够了。