如何在bash中实现多菜单DRY编程

How to implement DRY programming with multiple menus in bash

我的 bash 脚本中有选择菜单,我很好奇是否有一种方法可以简化和压缩菜单而不重复我自己。我尝试了很多不同的方法都无济于事,有没有人知道压缩这段代码的好方法:

    PS3="some menu"
    options=("option1" "option2" "option3" "exit")
    select opt in "${options[@]}"
    do
        case $opt in
            "option1")
                something
                ;;
            "option2")
                something else
                ;;
            "option3")
                something different
                ;;
            "exit")

                ;;
            *) echo "invalid option $REPLY";;
        esac
        read -p "Press Enter to continue"
        clear
        return 1
    done

    PS3="some menu 2"
    options=("option 1" "option2" "exit")
    select opt in "${options[@]}"
    do
        case $opt in
            "option 1")
                something
                ;;
            "option 2")
                something
                ;;
            
            "exit")
                exit
                ;;
            *) echo "invalid option $REPLY";;
        esac
        read -p "Press Enter to continue"
        clear
        return 1
    done

我想压缩这段代码而不在我的代码中重复多个菜单。

放在一个函数里调用两次,像这样:

#!/usr/bin/env bash

prompt() {
    local opt
    local -A isExpected
    local -a options
    PS3=""
    shift
    options=()
    for opt; do
        isExpected["$opt"]=1
        options+=("$opt")
    done
    isExpected["exit"]=1
    options+=("exit")
    select opt in "${options[@]}"
    do
        if (( "${isExpected[$opt]}" )); then
            case $opt in
                "option 1")
                    something
                    ;;
               "option 2")
                    something
                    ;;
                "option 3")
                    something
                    ;;
                "exit")
                    ;;
                *) printf 'invalid option "%s"\n' "$REPLY" >&2;;
            esac
        else
            printf 'unexpected option "%s"\n' "$opt" >&2
        fi
        read -r -p "Press Enter to continue"
        clear
        return 1
    done
}

prompt "some menu" "option 1" "option 2" "option 3"
prompt "some menu 2" "option 1" "option 2"

显然,您可以将所有常用选项移动到 prompt() 中,就像我为 "exit" 所做的那样。如果出现的选项顺序无关紧要,那么您可以去掉 isExpected[],只使用一个关联数组来保存 options 并使用 ${!options[@]}" 访问 select。

您可以使用分派 table,将选项文本映射到该选项的函数。这需要 bash 版本 4.4+ 才能使用 local -n namerefs

fn_a1() { echo "do something here for option a1"; }
fn_a2() { echo "do something here for option a2"; }
fn_a3() { echo "do something here for option a3"; }
fn_b1() { echo "do something here for option b1"; }
fn_b2() { echo "do something here for option b2"; }

prompt() {
    local PS3=": "
    local -n _options= _dispatch=
    select opt in "${_options[@]}"; do
        if [[ -v _dispatch["$opt"] ]]; then
            "${_dispatch[$opt]}"
            break
        fi
    done
}

declare -A dispatch_table
declare -a options

options=( "option a1" "option a2" "option a3" exit )
dispatch_table=(
    ["option a1"]=fn_a1
    ["option a2"]=fn_a2
    ["option a3"]=fn_a3
    ["exit"]=exit
)
prompt "some menu" options dispatch_table

options=( "option b1" "option b2" exit )
dispatch_table=(
    ["option b1"]=fn_b1
    ["option b2"]=fn_b2
    ["exit"]=exit
)
prompt "some menu 2" options dispatch_table

我将选项作为一个单独的数组发送,这样我就可以控制菜单的顺序:遍历关联数组的键没有固有顺序。