我有一个可能包含也可能不包含的数组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[@]}"
不带-a
:options
上面代码中的数组是使用与每个解析选项对应的字符串按分配中列出的顺序创建的。仅当设置了相应的变量时,字符串才会包含在数组中_opt
(实际值并不重要)。
With -a
:options
数组是根据端点返回的字符串创建的。如果在端点的输出中找到该字符串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 getopts
B
options[]
答案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 并跳过该迭代。