按特定顺序循环数组

按特定顺序循环数组

我有一个可能包含也可能不包含的数组BMC,并且我正在循环遍历数组的每个元素。如果它确实包含BMC我想BMC成为循环的最后一次迭代。

该数组是使用命令行选项创建的,因此我认为如果不进行重大更改,我无法强制执行创建顺序。

例子:

#!/usr/bin/env bash

while getopts cnBba opt; do
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        B)  options+=(BMC);;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done


# This is probably really confusing because it's redacted but this script also
# allows for -a to be used and then I'll query an endpoint that gives me a list
# of all available firmware for the given server.  It could return some or all
# of the possible options in no particular order.
mapfile -t types < <(curl some_firmware_endpoint)
if [[ $all == true ]]; then
    options=($(printf '%s\n' "${types[@]}" NIC | sort -u))
fi

for opt in "${options[@]}"; do
    echo "option is $opt"
done

产生以下输出:

$ ./script.sh -Bbcn
option is BMC
option is BIOS
option is CPLD
option is NIC

但我想要这个输出:

$ ./script.sh -Bbcn
option is BIOS
option is CPLD
option is NIC
option is BMC

我有一个解决方案,但它在数组中创建一个空元素,我确信我可以进一步处理它,但可能有更正确的方法来做到这一点。

我的解决方案:

#!/usr/bin/env bash

while getopts cnBba opt; do
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        B)  options+=(BMC);;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done


# This is probably really confusing because it's redacted but this script also
# allows for -a to be used and then I'll query an endpoint that gives me a list
# of all available firmware for the given server.  It could return some or all
# of the possible options in no particular order.
mapfile -t types < <(curl some_firmware_endpoint)
if [[ $all == true ]]; then
    options=($(printf '%s\n' "${types[@]}" NIC | sort -u))
fi

if [[ "${options[@]}" =~ BMC ]]; then
    options=("${options[@]/BMC/}" BMC)
fi

for opt in "${options[@]}"; do
    echo "option is $opt"
done

其产生:

$ ./script.sh -Bbcn
option is
option is BIOS
option is CPLD
option is NIC
option is BMC

-a all 选项:

all 选项将查询一个端点(基本上只是一个 json 对象),该端点将具有适用于服务器类型的固件列表。有些服务器可能只返回BIOS BMC,有些可能返回BIOS BMC NIC CPLD SATA StorageController甚至更多。由于 json 对象的组织方式,它们中的大多数将单独列出 NIC 固件,因此我在使用 -a 时手动添加该错误,并且如果该特定 NIC 没有固件,则单独处理该错误,然后从那时起一些其中还有网卡,sort -u如果网卡列出两次,我会删除它。

答案1

稍后创建数组。如果-a使用该选项,请调用您的 enpoint 并BMC使用 重新排列最后的字符串sed。如果未使用,请从给定的命令行选项创建数组。

(注意:我在这里使用文字制表符进行缩进。此处文档无法正常使用空格。或者您可以手动删除缩进。)

#!/bin/bash

# Fake endpoint response.
get_valid () {
        cat <<-END_LIST
                BIOS
                BMC
                tiny green peas
                CPLD
                MICROBE
        END_LIST
}

a_opt=false
unset -v c_opt n_opt B_opt b_opt

while getopts acnBb opt; do
        case $opt in
                a)      a_opt=true      ;;
                c)      c_opt=1 ;;
                n)      n_opt=1 ;;
                B)      B_opt=1 ;;
                b)      b_opt=1 ;;
                *)      :       # error handling here
        esac
done


if "$a_opt"; then
        readarray -t options < <(
                get_valid |
                sed -e '/^BMC$/{ h; d; }' \
                    -e '${ p; g; /^$/d; }'
        )
else
        options=(
                ${c_opt:+CPLD}
                ${n_opt:+NIC}
                ${b_opt:+BIOS}
                ${B_opt:+BMC}
        )
fi

printf 'Option: "%s"\n' "${options[@]}"

不带-aoptions上面代码中的数组是使用与每个解析选项对应的字符串按分配中列出的顺序创建的。仅当设置了相应的变量时,字符串才会包含在数组中_opt(实际值并不重要)。

With -aoptions数组是根据端点返回的字符串创建的。如果在端点的输出中找到该字符串BMC(单独在一行上),则通过将其保存在保留空间中来将其移动到末尾,直到处理完最后一行。

$ bash script -bBcn
Option: "CPLD"
Option: "NIC"
Option: "BIOS"
Option: "BMC"
$ bash script -a
Option: "BIOS"
Option: "tiny green peas"
Option: "CPLD"
Option: "MICROBE"
Option: "BMC"

答案2

特殊情况很特殊,您可以在循环后手动将其踢出:

...
handle_opt() {
    echo "option is $1"
}

do_BMC=0
for opt in "${options[@]}"; do
    if [[ $opt = BMC ]]; then do_BMC=1; continue; fi
    handle_opt "$opt"
done

if [[ $do_BMC= 1 ]]; then
    handle_opt "BMC"
fi

或者,固定常量数组中选项的处理顺序,并使用例如关联数组来告诉哪些选项已启用。尽管这确实意味着从外部程序读取输入并不那么简单mapfile。 (这可以看作是 Kusalananda 解决方案的变体)。

未经测试,但你明白了:

order=(CPLD BIOS NIC BMC)  # keep BMC last
declare -A options         # associative array

while getopts cnBba opt; do
    case $opt in
        c)  options[CPLD]=1 ;;
        n)  options[NIC]=1  ;;
        B)  options[BMC]=1  ;;
        b)  options[BIOS]=1 ;;
        a)  all=true ;;
    esac
done

if [[ $all == true ]]; then
    while IFS= read -r line; do
        options[$line]=1;
    done < <(curl some_firmware_endpoint)
fi

for opt in "${order[@]}"; do
    if [[ "${options[$opt]}" != 1 ]]; then continue; fi

    handle_opt "$opt"
done

答案3

请记住,您B在循环遍历选项然后options[]稍后填充时得到了,例如使用标量变量:

$ cat script.sh
#!/usr/bin/env bash

while getopts cnBba opt; do
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        B)  gotB=1;;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done

# mapfile and it's associated loop would go here

(( gotB )) && options+=(BMC)

for opt in "${options[@]}"; do
    echo "option is $opt"
done

$ ./script.sh -Bbcn
option is BIOS
option is CPLD
option is NIC
option is BMC

$ ./script.sh -bcn
option is BIOS
option is CPLD
option is NIC

或者,如果您的 bash 版本支持,-v您可以保留一个单独的选项数组getopts,然后B在其中测试是否为索引:

$ cat script.sh
#!/usr/bin/env bash

declare -A opts

while getopts cnBba opt; do
    opts["$opt"]=''
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done

# mapfile and it's associated loop would go here

[[ -v opts[B] ]] && options+=(BMC)

for opt in "${options[@]}"; do
    echo "option is $opt"
done

或者先将要添加到末尾的任何选项保存在不同的数组中,然后options[]再从该数组填充:

$ cat script.sh
#!/usr/bin/env bash

while getopts cnBba opt; do
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        B)  deferred+=(BMC);;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done

# mapfile and it's associated loop would go here

for opt in "${deferred[@]}"; do
    options+=("$opt")
done

for opt in "${options[@]}"; do
    echo "option is $opt"
done

如评论中所述,在上述任何脚本中添加和 之间添加mapfile关联的循环。while getoptsBoptions[]

答案4

谢谢大家的回答。我最终选择了这个:

#!/usr/bin/env bash

while getopts cnBba opt; do
    case $opt in
        c)  options+=(CPLD);;
        n)  options+=(NIC);;
        B)  options+=(BMC);;
        b)  options+=(BIOS);;
        a)  all=true;;
    esac
done

mapfile -t types < <(curl some_firmware_endpoint)
if [[ $all == true ]]; then
    options=($(printf '%s\n' "${types[@]}" NIC | sort -u))
fi

if [[ "${options[@]}" =~ BMC ]]; then
    options=("${options[@]/BMC/}" BMC)
fi

for opt in "${options[@]}"; do
    [[ -z "$opt" ]] && continue
    echo "option is $opt"
done

如果 options 包含BMC它将被删除,然后在最后读取,然后由于创建了一个 null 元素,我只需检查循环中该元素是否为 null 并跳过该迭代。

相关内容