如何将包括数组在内的多个参数传递给 Bash 中未定义顺序且数组元素可以有多个单词的函数?
How to pass multiple arguments including arrays to a function in Bash where order is not defined and array elements can have multiple words?
我花了好几个小时想弄清楚这个问题。
我想做的是有一个函数可以接收 N 个参数,其中可以包含数组。
数组可以在任何参数位置,不仅是最后一个!
参数的顺序可以改变,例如,我可以一次将第一个参数作为数组传递,而在另一个调用中作为第二个参数传递!
数组和法线参数必须可以互换!
示例代码(注意本例中参数顺序是固定的,问题见下例):
# Menu title
# List of menu options
# Menu text
menu() {
dialog --clear --stdout --title "" --menu "" 0 0 0 ""
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" ${options[@]} "Text"
第一个例子可以这样解决:
# Menu title
# List of menu options
# Menu text
menu() {
local -n a=
dialog --clear --stdout --title "" --menu "" 0 0 0 "${a[@]}"
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" options "Text"
问题示例:
my_func() {
my_command "" --something "" "" --somethingelse ""
}
optionsa=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
optionsb=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
my_func "Title" ${optionsa[@]} "Text" ${optionsb[@]}
# Note that I could do this and it must work too:
my_func ${optionsa[@]} "Title" ${optionsb[@]} "Text"
# This too must work:
my_func ${optionsa[@]} ${optionsb[@]} "Title" "Text"
扩展数组时,必须可以将值用作命令的参数,如果数组中的值有多个引用的单词(“选项 1”),则必须将其视为单个参数以避免示例将路径作为 "/my dir/"
传递并将其分隔为 </my> <dir/>
.
如何解决第二个示例,其中顺序或参数可以变化?
How can I solve the second example, where the order or parameter can vary?
有两个独立于编程语言的通用解决方案,可以将可变长度的项目列表连接在一起,任何程序员都应该知道:
- 通过计数...
my_func() {
local tmp optionsa title text optionsb
tmp=
shift
while ((tmp--)); do
optionsa+=("")
shift
done
title=""
shift
tmp=
shift
while ((tmp--)); do
optionsb+=("")
shift
done
text=
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func 0 "Title" 0 "Text"
my_func 0 "Title" "${#optionsb[@]}" "${optionsb[@]}" "Text"
my_func "${#optionsa[@]}" "${optionsa[@]}" "Title" "${#optionsb[@]}" "${optionsb[@]}" "Text"
- 使用 sentinel value.
my_func() {
local tmp optionsa title text optionsb
while [[ -n "" ]]; do
optionsa+=("")
shift
done
title=
shift
while [[ -n "" ]]; do
optionsb+=("")
shift
done
text=
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func "" "Title" "" "Text"
my_func "" "Title" "${optionsb[@]}" "" "Text"
my_func "${optionsa[@]}" "" "Title" "${optionsb[@]}" "" "Text"
而且我看到了两个bash具体的解决方案:
- 将数组作为名称传递并使用名称引用。
my_func() {
# Use unique names to avoid nameclashes
declare -n _my_func_optionsa=
local title=
declare -n _my_func_optionsb=
local title=
my_command "${_my_func_optionsa[@]}" "$title" "${_my_func_optionsb[@]}" "$text"
}
# arrays have to exists
my_func optionsa "Title" optionsb "Text"
- 像个真正的男人一样分析论点。这实际上是通用解决方案,因为它通常在创建参数列表时执行数据 serialization,然后在读取参数时执行数据反序列化 - 格式(参数作为选项)特定于 shell.
my_func() {
local args
# I am used to linux getopt, getopts would work as well
if ! args=$(getopt -n "$my_func" -o "a:t:b:x:" -- "$@"); then
echo "my_func: Invalid arguments" >&2
return 1
fi
set -- "$args"
local optionsa title optionsb text
while (($#)); do
case "" in
-a) optionsa+=(""); shift; ;;
-t) title=""; shift; ;;
-b) optionsb+=(""); shift; ;;
-x) text=""; shift; ;;
*) echo "my_func: Error parsing argument: " >&2; return 1; ;;
esac
shift
done
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func -a opta1 -a opta2 -t Title -b optb1 -b optb2 -x text
# or build the options list from arrays:
# ie. perform data serialization
args=()
for i in "${optionsa[@]}"; do
args+=(-a "$i")
done
args+=(-t "title")
for i in "${optionsb[@]}"; do args+=(-b "$i"); done
args+=(-x "text")
my_func "${args[@]}"
一般来说,如果一个函数有常量和少量的参数,就使用参数。如果函数因更多边缘情况而变得复杂,我建议像人一样解析参数 - 使函数通用且抽象,易于扩展和实现边缘情况并处理边缘情况和错误,易于其他程序员理解,易于阅读和可被人眼解析。
因为您的示例代码可能存在一些问题,我建议研究一下 shelll 中的引用是如何工作的,特别是 "${array[@]}"
与 ${array[@]}
的区别,研究 [@]
与 [*]
不同的是,分词扩展如何执行、何时执行以及它如何影响参数。代码中所有未加引号的数组扩展都会出现分词 - 空格将不会保留,在第一个示例中也是如此。
我花了好几个小时想弄清楚这个问题。
我想做的是有一个函数可以接收 N 个参数,其中可以包含数组。
数组可以在任何参数位置,不仅是最后一个!
参数的顺序可以改变,例如,我可以一次将第一个参数作为数组传递,而在另一个调用中作为第二个参数传递!
数组和法线参数必须可以互换!
示例代码(注意本例中参数顺序是固定的,问题见下例):
# Menu title
# List of menu options
# Menu text
menu() {
dialog --clear --stdout --title "" --menu "" 0 0 0 ""
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" ${options[@]} "Text"
第一个例子可以这样解决:
# Menu title
# List of menu options
# Menu text
menu() {
local -n a=
dialog --clear --stdout --title "" --menu "" 0 0 0 "${a[@]}"
}
options=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
menu "Title" options "Text"
问题示例:
my_func() {
my_command "" --something "" "" --somethingelse ""
}
optionsa=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
optionsb=(
1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
my_func "Title" ${optionsa[@]} "Text" ${optionsb[@]}
# Note that I could do this and it must work too:
my_func ${optionsa[@]} "Title" ${optionsb[@]} "Text"
# This too must work:
my_func ${optionsa[@]} ${optionsb[@]} "Title" "Text"
扩展数组时,必须可以将值用作命令的参数,如果数组中的值有多个引用的单词(“选项 1”),则必须将其视为单个参数以避免示例将路径作为 "/my dir/"
传递并将其分隔为 </my> <dir/>
.
如何解决第二个示例,其中顺序或参数可以变化?
How can I solve the second example, where the order or parameter can vary?
有两个独立于编程语言的通用解决方案,可以将可变长度的项目列表连接在一起,任何程序员都应该知道:
- 通过计数...
my_func() {
local tmp optionsa title text optionsb
tmp=
shift
while ((tmp--)); do
optionsa+=("")
shift
done
title=""
shift
tmp=
shift
while ((tmp--)); do
optionsb+=("")
shift
done
text=
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func 0 "Title" 0 "Text"
my_func 0 "Title" "${#optionsb[@]}" "${optionsb[@]}" "Text"
my_func "${#optionsa[@]}" "${optionsa[@]}" "Title" "${#optionsb[@]}" "${optionsb[@]}" "Text"
- 使用 sentinel value.
my_func() {
local tmp optionsa title text optionsb
while [[ -n "" ]]; do
optionsa+=("")
shift
done
title=
shift
while [[ -n "" ]]; do
optionsb+=("")
shift
done
text=
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func "" "Title" "" "Text"
my_func "" "Title" "${optionsb[@]}" "" "Text"
my_func "${optionsa[@]}" "" "Title" "${optionsb[@]}" "" "Text"
而且我看到了两个bash具体的解决方案:
- 将数组作为名称传递并使用名称引用。
my_func() {
# Use unique names to avoid nameclashes
declare -n _my_func_optionsa=
local title=
declare -n _my_func_optionsb=
local title=
my_command "${_my_func_optionsa[@]}" "$title" "${_my_func_optionsb[@]}" "$text"
}
# arrays have to exists
my_func optionsa "Title" optionsb "Text"
- 像个真正的男人一样分析论点。这实际上是通用解决方案,因为它通常在创建参数列表时执行数据 serialization,然后在读取参数时执行数据反序列化 - 格式(参数作为选项)特定于 shell.
my_func() {
local args
# I am used to linux getopt, getopts would work as well
if ! args=$(getopt -n "$my_func" -o "a:t:b:x:" -- "$@"); then
echo "my_func: Invalid arguments" >&2
return 1
fi
set -- "$args"
local optionsa title optionsb text
while (($#)); do
case "" in
-a) optionsa+=(""); shift; ;;
-t) title=""; shift; ;;
-b) optionsb+=(""); shift; ;;
-x) text=""; shift; ;;
*) echo "my_func: Error parsing argument: " >&2; return 1; ;;
esac
shift
done
my_command "${optionsa[@]}" "$title" "${optionsb[@]}" "$text"
}
my_func -a opta1 -a opta2 -t Title -b optb1 -b optb2 -x text
# or build the options list from arrays:
# ie. perform data serialization
args=()
for i in "${optionsa[@]}"; do
args+=(-a "$i")
done
args+=(-t "title")
for i in "${optionsb[@]}"; do args+=(-b "$i"); done
args+=(-x "text")
my_func "${args[@]}"
一般来说,如果一个函数有常量和少量的参数,就使用参数。如果函数因更多边缘情况而变得复杂,我建议像人一样解析参数 - 使函数通用且抽象,易于扩展和实现边缘情况并处理边缘情况和错误,易于其他程序员理解,易于阅读和可被人眼解析。
因为您的示例代码可能存在一些问题,我建议研究一下 shelll 中的引用是如何工作的,特别是 "${array[@]}"
与 ${array[@]}
的区别,研究 [@]
与 [*]
不同的是,分词扩展如何执行、何时执行以及它如何影响参数。代码中所有未加引号的数组扩展都会出现分词 - 空格将不会保留,在第一个示例中也是如此。