Bash case 语句在输入无效时重新启动

Bash case statement restart on invalid input

试图找出在 case 语句中 return 的方法。例如,如果您 运行 这个....

while :
do 
    clear
    cat<<-EOF
    ======================
    Foo Bar Doo Dah Setup
    ======================

    (1) Foo
    (2) Bar
    (q) Quit

    ----------------------
    EOF
    read
    case $REPLY in
    "1")  foo="foo" ;;
    "2")  bar="bar" ;;
    "q")  break     ;;
     * )  echo "Invalid Option"
    esac
    sleep .5

    clear
    cat<<-EOF
    ======================
    Foo Bar Doo Dah Setup
    ======================

    (1) Doo
    (2) Dah
    (q) Quit

    ----------------------
    EOF
    read
    case $REPLY in
    "1")  doo="doo" ;;
    "2")  dah="dah" ;;
    "q")  break     ;;
     * )  echo "Invalid Option"
    esac
    sleep .5

done

...然后输入 "Invalid Option" 然后您会注意到它会转到下一个案例,而不是重新评估案例。

解决方法还不错,只需将 case 语句嵌套到 while 循环中的 if 语句中...

while read; do
    if [ $REPLY -ge 1 -a $REPLY -le 2 ]; then
        case $REPLY in
        "1")  foo="foo" ;;
        "2")  bar="bar" ;;
        esac
        break
    elif [ $REPLY == q ]; then 
        break
    else
        echo "Invalid Option"
    fi
done

说的似乎有点多,有人知道某种形式的循环控制来重新运行来自案例选择的案例陈述吗?

如果您需要在继续下一个菜单之前从菜单中进行有效选择,那么您需要围绕选择过程进行循环。您可能还应该计算失败次数并在连续失败几次(例如 10 次)后终止。

shell 循环结构同时支持 breakcontinue,并且可以选择在它们后面跟一个数字,指示应该打破多少循环级别(默认级别数是 1).

代码还应注意 read 检测到的 EOF 并终止循环。这是通过下面代码中的 answer 变量实现的。

这导致代码如下:

retries=0
max_retries=10
while [ $retries -lt $max_retries ]
do 
    clear
    cat <<-'EOF'
    ======================
    Foo Bar Doo Dah Setup
    ======================

    (1) Foo
    (2) Bar
    (q) Quit

    ----------------------
    EOF
    retries=0
    answer=no
    while read
    do
        case "$REPLY" in
        "1")  foo="foo"; answer=yes; break;;
        "2")  bar="bar"; answer=yes; break;;
        "q")  break 2;;  # Or exit, or return if the code is in a function
         * )  echo "Invalid Option ('$REPLY' given)" >&2
              if [ $((++retries)) -ge $max_retries ]; then break 2; fi
              ;;
        esac
    done
    if [ "$answer" = "no" ]; then break; fi   # EOF in read loop

    sleep .5

    clear
    cat <<-'EOF'
    ======================
    Foo Bar Doo Dah Setup
    ======================

    (1) Doo
    (2) Dah
    (q) Quit

    ----------------------
    EOF
    retries=0
    answer=no
    while read
    do
        case $REPLY in
        "1")  doo="doo"; answer=yes;;
        "2")  dah="dah"; answer=yes;;
        "q")  break 2;;
         * )  echo "Invalid Option ('$REPLY' given)"" >&2
              if [ $((++retries)) -ge $max_retries ]; then break 2; fi
              ;;
        esac
    done
    if [ "$answer" = "no" ]; then break; fi   # EOF in read loop
    sleep .5
    echo "$foo$bar $doo$dah"   # Do something with the entered information
done

我并不完全喜欢没有名字的 read,尤其是因为它是 Bash 扩展而不是标准 shell 功能(POSIX read 实用程序不提供默认变量名称),省略名称会不必要地限制脚本的可移植性。

还要注意,here 文档的开始标记用引号引起来,因此 here 文档的内容不会受到 shell 扩展。 - 表示前导制表符(但不是空格)已从此处文档和文档标记行的结尾删除。

问题中第一个循环的代码也可以是 'fixed',方法是使用 continue 而不是嵌套的 while 循环。但是,如果第二个循环只是重试第二个提示,则继续外循环并跳过第一个菜单会很复杂。显示的解决方案是对称的,并正确隔离了两个提示中每个提示的输入。

我选择不在重试时重新回显菜单,但循环该代码也不难。可以使用:

retries=0
answer=no
while   clear
        cat <<-'EOF'
        ======================
        Foo Bar Doo Dah Setup
        ======================

        (1) Foo
        (2) Bar
        (q) Quit

        ----------------------
        EOF
        read
do
    …processing $REPLY as before…
done

然而,这样做会导致很多人头疼,因为人们通常不会意识到在 while 语句之后可以有一个命令列表,而且只有最后一个命令的退出状态控制是否循环是否继续下一次迭代。

我个人避免在 shell 脚本中使用制表符,因此我可能会创建变量来保存菜单:

menu1=$(cat <<-'EOF'
======================
Foo Bar Doo Dah Setup
======================

(1) Foo
(2) Bar
(q) Quit

----------------------
EOF
)

循环中的提示可以是:

while clear; echo "$menu1"; read

哪个更容易理解(双引号很重要)。如果 clear 命令运行良好并成功退出,您可以使用 && 代替 ;