问题
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