SIMMS7400 wrote:is there anyway to return to the menu prompts WITHOUT using a GOTO label?
I ask because scripts can become cluttered with many GOTO labels and look "bad". I know they are necessary but figured I'd ask.
First, I agree with Squashman, and I'm glad to see you took his advice. For the benefit of anyone who is not familiar with how to use CHOICE, here are a couple variations on how to accomplish the current task. The first is more generic, and the second uses the options from your example script.
Code: Select all
:: choice_menu1.bat
@echo off
setlocal
rem :: Present options to user. This can be done in the CHOICE command itself.
echo [1] Option 1
echo [2] Option 2
echo [3] Option 3
echo\
rem :: Get input from user. (Message always has space added after it.)
rem :: The /N switch prevents showing the [123] options, since we did that above.
choice /c 123 /n /m "Enter a number matching one of the options above:"
rem :: Process input. Must test ERRORLEVEL values in descending order.
rem :: IF ERRORLEVEL is same as IF %ERRORLEVEL% GEQ (greater than or equal to).
rem :: ERRORLEVEL 0 means user used CTRL+BREAK or CTRL+C to not enter input.
rem :: ERRORLEVEL 255 means an error in the CHOICE command itself.
if not errorlevel 255 if errorlevel 1 (
rem :: You can use GOTO or CALL here.
if errorlevel 3 goto sub_3
if errorlevel 2 goto sub_2
if errorlevel 1 goto sub_1
) else echo There was an error.
endlocal
exit /b
::=============================================================================
:: Subroutines
::=============================================================================
:sub_1
echo You chose option 1.
goto :eof
:sub_2
echo You chose option 2.
goto :eof
:sub_3
echo You chose option 3.
goto :eof
Code: Select all
:: choice_menu2.bat
@echo off
setlocal
rem :: Manually set value for demo.
set "INT_OPT=5"
rem :: Get input from user. (Message always has space added after it.)
choice /c EMR /m "Press E to Exit, M to Map Ghost Users, or R to Redo Option: %INT_OPT%:"
rem :: Process input. Must test ERRORLEVEL values in descending order.
rem :: IF ERRORLEVEL is same as IF %ERRORLEVEL% GEQ (greater than or equal to).
rem :: ERRORLEVEL 0 means user used CTRL+BREAK or CTRL+C to not enter input.
rem :: ERRORLEVEL 255 means an error in the CHOICE command itself.
if not errorlevel 255 if errorlevel 1 (
rem :: You can use GOTO or CALL here.
if errorlevel 3 call :redo_option %INT_OPT%
if errorlevel 2 call :map_ghost_users
if errorlevel 1 goto :exit
) else echo There was an error.
:exit
endlocal
exit /b
::=============================================================================
:: Subroutines
::=============================================================================
:redo_option
echo You chose to redo option %1.
goto :eof
:map_ghost_users
echo You chose to map ghost users.
goto :eof
Second, GOTOs really aren't evil. While CHOICE is the ideal tool for what you're doing, not every system has it installed. It was standard for a long time, then was taken away on 2000 and XP, then put back on Vista and later. Sometimes we have to accomodate XP users, which is why the SET /P approach is so popular.
All the different kinds of FOR/WHILE/UNTIL loops you see in various languages are just fancy wrappers around a sequence of "test condition then goto line" commands. Since BATCH doesn't include a WHILE command, it's perfectly reasonable to roll your own. Your current code can be simplified so that the GOTO is a little cleaner:
Code: Select all
@ECHO OFF
:: DOS TIPS EXAMPLE
:1
echo --------------------------------------
echo [1] Exit - No further action required
echo [2] Map Ghost Users
echo [3] Redo Option: %INT_OPT%
echo.
set /p BRSQLDB_Q=Option:
IF %BRSQLDB_Q%==1 GOTO S4
IF %BRSQLDB_Q%==2 GOTO S5
IF %BRSQLDB_Q%==3 GOTO GET_SQL_CREDS
rem :: for %%i in (1 2 3) do if %BRSQLDB_Q% NEQ %%i ( GOTO 1 )
rem :: Getting this far means that all the IF tests failed, so entry must be invalid.
GOTO 1
:S4
:S5
:GET_SQL_CREDS
Lastly, yes, it can be done, but at a cost. The only way I know is to create an infinite loop, and inside the loop, prompt for input. If the input is good, break out of the loop. If it's not, echo an error message, and the loop will automatically take you back to the top and prompt for input again. The catch is that breaking out of the loop requires quitting the batch entirely. On the plus side,
it runs a bit faster than the GOTO version, since the loop loads all commands into memory, while the GOTO requires repeated file scans.
Code: Select all
:: menu_loop.bat
@echo off
setlocal enableDelayedExpansion
rem :: Create an infinite loop instead of defining a label.
rem :: The counter increments by 0, so will never reach 2.
for /l %%L in (1,0,2) do (
rem :: Present options to user.
echo [1] Option 1
echo [2] Option 2
echo [3] Option 3
echo\
rem :: Get input from user.
set /p "choice=Enter choice number: "
rem :: If valid option, call appropriate subroutine, then break out of loop.
rem :: Breaking out requires quitting the batch; I haven't found a way to
rem :: continue on to other tasks. It might be possible by calling external
rem :: batch files instead of subroutines. Haven't tried that.
rem :: Breakout technique courtesy of our very own jeb, via StackOverflow.
rem :: http://stackoverflow.com/a/5488111/3326340
for %%A in (1 2 3) do if "%%A" equ "!choice!" (
call :sub_!choice!
call :stop
)
rem :: If we got past the inner FOR loop, we must have an invalid option.
echo Invalid option. Try again.
)
rem :: The call to :stop forces the batch to quit, so we never reach the EXIT.
rem :: I include it anyway because not having an EXIT makes me nervous.
exit /b
::=============================================================================
:: Subroutines
::=============================================================================
:sub_1
echo You chose option 1.
goto :eof
:sub_2
echo You chose option 2.
goto :eof
:sub_3
echo You chose option 3.
goto :eof
:stop
call :__stop 2>nul
:__stop
() creates a syntax error, quits the batch