如何将包括数组在内的多个参数传递给 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?

有两个独立于编程语言的通用解决方案,可以将可变长度的项目列表连接在一起,任何程序员都应该知道:

  1. 通过计数...

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"
  1. 使用 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具体的解决方案:

  1. 将数组作为名称传递并使用名称引用。

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"
  1. 像个真正的男人一样分析论点。这实际上是通用解决方案,因为它通常在创建参数列表时执行数据 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[@]} 的区别,研究 [@][*] 不同的是,分词扩展如何执行、何时执行以及它如何影响参数。代码中所有未加引号的数组扩展都会出现分词 - 空格将不会保留,在第一个示例中也是如此。