我写了一个简短的脚本来检查环境变量的存在,如果不存在则失败。该脚本的目的是在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
中的行为不同。
答案 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
有时在您信任您的输入时就足够了。