变量名称存储在 bash 数组中;如何循环遍历数组并根据常见模式测试它们的值?

变量名称存储在 bash 数组中;如何循环遍历数组并根据常见模式测试它们的值?

团队,

我正在尝试实现一项功能,即由用户检查 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 ]]

这根本不符合你想要的逻辑。问题:

  1. ==(小问题或设计使然)。运算符右侧未加引号的部分[[ … ]]被视为模式。DEFAULT不包含*也不包含,因此在本例中无所谓;但一般来说,您可能需要用双引号引起来$preset

  2. 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" ]],如果你以某种方式得到了这个)都不是你想要的。这引出了我们的下一个观点。

  3. 您的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首先进行测试)。


补充笔记:

  • [并且[[不同。大多数情况下我用的[[即使也[足够了。
  • 我在 中对变量使用了双引号[[ … ]],尽管在许多情况下不需要引用。我的前提是“除非您知道自己在做什么并且有充分的理由不这样做,否则请始终引用变量”。只有很少有些情况下,你不必引用就能得到你想要的东西;有些情况下,引用并不重要;还有许多情况缺乏适当的引用会给你带来麻烦

相关内容