Bash case statement restart on invalid input

醉酒当歌 提交于 2019-12-11 02:38:50

问题


Trying to figure out a way to return in a case statement. For example, if you run this....

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

...and enter an "Invalid Option" then you'll notice it moves on to the next case instead of re-evaluating the case.

The workaround isn't too bad just have to nest the case statement into an if statement within a while loop ...

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

That being said seems a bit much, anyone know of some form a loop control to rerun a case statement from a case selection?


回答1:


If you need a valid choice from the menu before continuing to the next menu, then you need a loop around the choosing process. You should probably also count the failures and terminate after some number of consecutive failures such as 10.

The shell loop constructs support both break and continue, and these can optionally be followed by a number indicating how many loop levels should be broken (the default number of levels is 1).

The code should also heed EOF detected by read and terminate the loops. That's achieved with the answer variable in the code below.

This leads to code like:

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

I'm not entirely keen on read with no name after it, not least because it is a Bash extension rather than standard shell functionality (the POSIX read utility does not provide a default variable name), and omitting the name unnecessarily limits the scripts portability.

Note, too, that the here documents have the start marker enclosed in quotes so that the content of the here document is not subjected to shell expansions. The - indicates that leading tabs (but not spaces) are deleted from the here document and the end of document marker line.

The code in the question for the first loop could also be 'fixed' by using a continue instead of a nested while loop. However, if the second loop was to retry just the second prompt, continuing the outer loop and skipping the first menu would be complex. The solution shown is symmetric and properly isolates the input for each of the two prompts.

I chose not to re-echo the menu on a retry, but it would not be hard to loop on that code too. It would be feasible to use:

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

However, doing so will cause many scratched heads as people are not often aware that you can have a list of commands after a while statement and it is only the exit status of the last that controls whether the loop continues another iteration or not.

I personally avoid tabs in shell scripts, so I'd probably create variables to hold the menus:

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

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

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

The prompt in the loop could then be:

while clear; echo "$menu1"; read

which is easier to understand (and the double quotes are crucial). You could use && in place of ; if the clear command is well behaved and exits successfully.



来源:https://stackoverflow.com/questions/29071286/bash-case-statement-restart-on-invalid-input

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!