团队,
我正在尝试实现一项功能,即由用户检查 ENV 中预定义变量列表中所有变量的内容,然后使用所有结果变量作为参数运行我的函数。
因此,有 3 种可能性:
1 - All variables hold same value
[DatasetSize & BlockSize == DEFAULT ]
2 - All variables hold unique value
[DatasetSize & BlockSize != DEFAULT ]
3 - Some are unique and some distinct.
[DatasetSize || BlockSize = DEFAULT ]
下面的代码适用于案例 1 和案例 2,但不适用于案例 3。对于案例 3,当任何变量具有“DEFAULT”时,它只会执行第二个 elif,而不是第三个 elif。
#!/bin/bash
#exit on fail without running next command
set -eo pipefail
export TestType="read"
export IOEngine="psync"
export DatasetSize="DEFAULT"
export BlockSize="NON-DEFAULT"
preset="DEFAULT"
declare -a static_vars=(TestType IOEngine)
declare -a dynamic_vars=(DatasetSize BlockSize )
declare -a vars=(TestType IOEngine DatasetSize BlockSize)
for var_name in "${vars[@]}"
do
if [ -z "$(eval "echo \$$var_name")" ]; then
echo "Missing environment variable $var_name"
exit 1
fi
done
overwrite_all() {
printf "all defaults in func\n"
}
overwrite_some() {
printf "some defaults in func\n"
}
overwrite_none() {
printf " none defaults in func\n"
}
overwrite() {
#not sure how to overwrite only those vars that have nonDEFAULT value.
export DatasetSize="changed"
export BlockSize="changed"
echo "DatasetSize=$DatasetSize BlockSize=$BlockSize"
}
if [[ "$IOEngine" == "psync" && ( "$TestType" == "read" || "$TestType" == "randread" ) ]]; then
iter=1
while [ $iter -lt 2 ]
do
echo "all are defaults; being to call your script with these parameters"
echo $var_name
if [[ ${dynamic_vars[@]} == $preset ]]; then
echo "calling over_none"
overwrite_none
elif [[ ${dynamic_vars[@]} != "DEFAULT" ]]; then
echo "calling over_all"
overwrite_all
overwrite
elif [[ $DatasetSize == "DEFAULT" || $BlockSize == "DEFAULT" ]] && [[ $DatasetSize != "DEFAULT" && $BlockSize != "DEFAULT" ]]; then
echo "calling over_some"
overwrite_some
overwrite
else
echo "done"
fi
iter=$[$iter+1]
done
else
echo "ITengine not found"
fi
echo "out of loop"
输出:
calling over_all
all non-defaults in func
Data setSize=changed BlockSize=changed
out of loop
预期输出:
some defaults in func
DatasetSize=changed BlockSize=changed
答案1
我将指出我认为您代码中的主要缺陷。它类似于以下几行:
if [[ ${dynamic_vars[@]} == $preset ]]; then
你的情况中的片段[[ ${dynamic_vars[@]} == $preset ]]
相当于
[[ "DatasetSize BlockSize" == DEFAULT ]]
这根本不符合你想要的逻辑。问题:
==
(小问题或设计使然)。运算符右侧未加引号的部分[[ … ]]
被视为模式。DEFAULT
不包含*
也不包含,因此在本例中无所谓;但一般来说,您可能需要用双引号引起来$preset
。Unquoted
${array[@]}
像 unquoted 一样进行分词$string
。如果您设置此项:array=(nospace "with space") string="nospace with space"
则将
${array[@]}
扩展为三个实体,所以将扩展为$string
。但"${array[@]}"
将扩展为二实体,而"$string"
将扩展为一个实体。您可以使用以下代码自行测试:sh -c 'echo $#' whatever ${array[@]} sh -c 'echo $#' whatever $string sh -c 'echo $#' whatever "${array[@]}" sh -c 'echo $#' whatever "$string"
这意味着您几乎总是想要使用,
"${array[@]}"
因为数组的全部意义在于传递精确数量的参数(这里是:2),其中一些可能包括空格或类似的东西。您的特定数组包含变量的名称。通常,此类名称不能包含空格或其他一些字符,这使得它们很安全(我认为即使涉及文件名扩展)。您可以将它们放在(空格分隔)
string
或中array
,并通过${array[@]}
,"${array[@]}"
或$string
(但不是"$string"
)检索。但这仍然是一个例外,它只在变量名称受到限制时才有效。因此,您的
${dynamic_vars[@]}
通常会扩展为两个实体("${dynamic_vars[@]}"
正常情况下会这样)。但有一个问题:内部未加引号的变量[[ … ]]
无论如何都会被视为单个实体(这对于 来说很特殊[[ … ]]
),因此"DatasetSize BlockSize"
。注意如果你这样做了
[[ "${dynamic_vars[@]}" == "$preset" ]]
,这将“起作用”[[ "DatasetSize" "BlockSize" == "DEFAULT" ]]
这在语法上是错误的( 两边应该只有一个参数
==
)。但无论是
[[ "DatasetSize BlockSize" == DEFAULT ]]
还是[[ "DatasetSize" "BlockSize" == "DEFAULT" ]]
(甚至也不是[[ "DatasetSize" == "DEFAULT" && "BlockSize" == "DEFAULT" ]]
,如果你以某种方式得到了这个)都不是你想要的。这引出了我们的下一个观点。您的
dynamic_vars
数组包含字符串,而不是变量或它们的值。我的意思是您可以轻松地从中获取DatasetSize
(即文字DatasetSize
字符串)。这显然不同于$DatasetSize
扩展为(即字符串DEFAULT
)。您需要一个额外的在将结果与 进行比较之前,请先查看扩展级别$preset
。实际上,您在其他地方执行了额外的扩展。在此代码片段中:
for var_name in "${vars[@]}" do if [ -z "$(eval "echo \$$var_name")" ]; then echo "Missing environment variable $var_name" exit 1 fi done
eval "echo \$$var_name"
是一种扩展变量的麻烦方法,变量的名称存储在另一个变量中(术语是间接扩张)。在 Bash 中,有专门的语法:(${!var_name}
通常您需要用双引号括住它)。相关行可以是if [ -z "${!var_name}" ]; then
注意
"$(eval "echo \$$var_name")"
危险的。如果 final 变量在别人的控制之下,他们就可以将代码注入到你的脚本中。"${!var_name}"
在这件事上是安全的。此外,在此代码片段中,您使用了
for var_name in "${vars[@]}"
。这是遍历数组的好方法。您可以在"${dynamic_vars[@]}"
代码的后面使用此方法来测试每个名称。
把所有这些放在一起,你可以通过计算保存默认值的变量的数量来获得你想要的东西;就像这样:
#!/bin/bash
DatasetSize="DEFAULT"
BlockSize="NON-DEFAULT"
preset="DEFAULT"
declare -a dynamic_vars=( DatasetSize BlockSize )
number_of_dynamic_vars="${#dynamic_vars[@]}"
number_of_default_values=0
for var in "${dynamic_vars[@]}"
do
[[ "${!var}" == "$preset" ]] && ((number_of_default_values++))
done
if [[ "$number_of_default_values" -eq 0 ]]; then
echo "No defaults"
elif [[ "$number_of_default_values" -eq "$number_of_dynamic_vars" ]]; then
echo "All defaults"
else
echo "Mixed"
fi
通用算法可以重建(优化),因此“混合”会尽快宣布(最佳情况:在测试前两个变量后)。但是,对于只有少数变量的情况,这样做不值得;对于两个变量(或更少),您总是需要检查所有变量。
注意,我没有if
按照你原来的顺序测试()。我是在(0
可能性 1)之前测试$number_of_dynamic_vars
(可能性 2)。这样,如果数组dynamic_vars
恰好有 0 个元素,我会得到“无默认值”而不是“所有默认值”。这只是因为在这个边缘情况我希望看到“无默认值”。但如果“无默认值”意味着大量额外工作,而“所有默认值”则不意味着,我想将这种极端情况放入“所有默认值”篮子中(即$number_of_dynamic_vars
首先进行测试)。
补充笔记:
[
并且[[
不同。大多数情况下我用的[[
即使也[
足够了。- 我在 中对变量使用了双引号
[[ … ]]
,尽管在许多情况下不需要引用。我的前提是“除非您知道自己在做什么并且有充分的理由不这样做,否则请始终引用变量”。只有很少有些情况下,你不必引用就能得到你想要的东西;有些情况下,引用并不重要;还有许多情况缺乏适当的引用会给你带来麻烦。