I'm trying to re-use the batch file code in order to perform a similar tasks in a menu pages.
The main menu consists of 10+ options.
When I go inside the each menu items, I need to display a following in text
Press [C] to Continue or [X] to exit [C/X]: _
I created labels in each menu time and re-direct to the code which is responsible for prompting the message and do necessary actions.
How can I use this following code as a subroutine, so that I don't have to re-write the code several times.At the moment I hard code it in each menu item. It would have been easy to call it as a sub routine.
:MiniMenu1
SET INPUT1=
SET /P INPUT1=Press [Y] to Continue Installation or [N] to go back [Y/N]:
IF /I '%INPUT1%'=='y' GOTO Mini_cont1
IF /I '%INPUT1%'=='n' GOTO Mini_back1
ECHO ============INVALID INPUT============
ECHO Please select a number from the Menu Options
ECHO -------------------------------------
ECHO ======PRESS ANY KEY TO CONTINUE======
PAUSE > NUL
GOTO MiniMenu1
Where as my code for main menu item pages are
:Selection1
:: MAin menu item 1
GOTO MiniMenu1
:Mini_cont1
:: xCopy update.zip C:\python27\ /y
#echo Update Completed.
pause
:Mini_back1
:: end
GOTO MENU
Ah - thinking along the right lines. Very good.
#echo off
setlocal
call :ask Question number one
if errorlevel 2 goto Q1X
call :ask Question number two
if errorlevel 2 goto Q2X
::get here for Q1Q2 responses both C
goto :eof
:ask
choice /c CX /N /M "%*"
goto :eof
Here's a basic template. From the prompt, type choice /? for instructions about options.
Hint: set "choices=wqzk" then in the subroutine choice /c %choices% /N /M "%*" would allow you to change the choices available. /n prompts with the available choices, so you've no need to specify that in the text, just make it obvious - Whatever, Quit, Zap, Kill should be obvious for wqzk for instance.
The return in %errorlevel% will the the sequence-number of the character chosen. W==>1, Q==>2..K==>4. In the traditional construct, if errorlevel n the comparison is true if errorlevel is n or greater than n so it would be traditional to use
if errorlevel 4 goto QnA4
if errorlevel 3 goto QnA3
if errorlevel 2 goto QnA2
:: if it gets here, errorlevel is 1 hence choice was first character.
which is shorter than the "modern" way
if %errorlevel%==1 goto QnA1
if %errorlevel%==2 goto QnA2
if %errorlevel%==3 goto QnA3
:: if it gets here, errorlevel is 4 or more hence choice was fourth or later character.
Note: %* means all of the arguments passed to the subroutine so /m "%*" neatly shows the arguments passed as a prompt. There's no voodoo about that. But be careful - text only and a few symbols if you like. Symbols with a special meaning to cmd may cause unexpected results
Variables created/changed/deleted after a setlocal will be deleted/restored/resurrected when a matching endlocal is encountered. Consequently, setlocal is often used as the first "action statement" in a batch - the environment is restored to pristine when the batch ends.
To remove variables within a batch using a subroutine, you could use
call :zap we dont want these variables
:zap
if "%1" neq "" set "%1="&shift&goto zap
goto :eof
(to delete variables we dont want these and variables
or :zap version 2
:zap
for %%a in (%*) do set "%%a="
goto :eof
To remove variables which all start with an identical character-pattern, use
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
(which will remove all variables starting $. $ isn't holy - you could substitute xyz for $ here and zap xyz123 xyz789 and xyzylofone for instance)
Naturally, you could also combine the techniques...
But - it's not expensive to ask a new question on SO. Not expensive at all. Cheap even. Asking a new question rather than tagging more issues onto an existing one makes finding a solution easier (like.. someone wanting to know how to delete variables possibly wouldn't expect to find it under a question titled "batch file sub routine" for instance. It also prevents the question from becoming a saga.
Related
I am so confused and all help is appreciated...
so I'm making a little game and this is the code:
#echo off
:menu
echo Welcome To My Game!!!
echo Play & echo.Exit
:: add more menu items above
set /p menInp=
if /i %menInp%==play goto initFile
if /i %menInp$==exit EXIT
:initFile
cls
if NOT exist \MyAdventureGame\AdventureGameSave.txt\ GOTO newGame
if exist \MyAdventureGame\AdventureGameSave.txt\ GOTO Load
:newGame
echo. 2>AdventureGameSave.txt
set Gold = 50
set Xp = 0
set Level = 1
GOTO Save
:Save
echo %Gold% >>AdventureGameSave.txt
echo %Xp% >>AdventureGameSave.txt
echo %Level% >>AdventureGameSave.txt
GOTO Town
:Load
:: add Load functionallity here
GOTO Town
:Town
echo Welcome to town!!!
PAUSE
Now for my question...
when i run it, no matter what I type for my menInp, my program goes to the next line, :initFile, and its almost like my if statements
(if /i %menInp%==play goto initFile
if /i %menInp$==exit EXIT)
are being ignored.
if /i %menInp$==exit EXIT
should be
if /i %menInp%==exit EXIT
or preferably
if /i "%menInp%"=="exit" EXIT
since you are unable to predict the user's input and it may contains spaces. The "s make the string a single token.
This is somewhat of a comment, but may forestall further problems.
Tip for game-generation:
If you reserve a character as a prefix for variables-you-want-to-save (eg all variables I want to save/reload start with #) then all you need to save a game is
set #>"mygamefile.txt"
and all you need to reload a game is
for /f "usebackqdelims=" %%a in ("mygamefile.txt") do set "%%a"
To zap all # variables (useful before reloading a game) use
for /f "delims==" %%a in ('set # 2^>nul') do set "%%a="
Oh and you may consider removing the terminal \ from the filename in the if exist statements (also enclose the entire filename in quotes to allow the name to contain spaces (and follow good practice regardless of whether you actually need to cater for spaces in filenames))
This link
https://www.youtube.com/watch?v=eULe3DNS8DM
Shows a game made in batch. The game, called viewpoint, allows you to move around and shoot at enemies. This game seems to accept keyboard input in real time to perform actions. Is there any way I can get my batch file to accept keyboard input in real time as well?
(Guessing by the "Core" folder I saw in the game folder, there might be some other program or command involved.)
You could use the choice command (which is what snake.bat uses) however it can only take alpha-numeric input one at a time.
Here is a quick program I whipped up which uses choice to take WASD input.
#setlocal enableextensions enabledelayedexpansion
#echo off
prompt $$$G
title movement
color 0a
set length=
for %%a in (1 2 3) do (
set "length=!length! "
)
:controls
cls
echo Use WASD to move your character ([]).
echo.
echo %length%[]
choice /c wasd /n
if %errorlevel% equ 1 call:up
if %errorlevel% equ 2 call:left
if %errorlevel% equ 3 call:down
if %errorlevel% equ 4 call:right
:left
set length=!length:~0,-1!
goto controls
:right
set "length= %length%"
goto controls
:up
set length=!length:~0,-80!
goto controls
:down
set "length= %length%"
goto controls
The main thing you should focus on is the :controls label and how it uses the choice command to read input.
I have made a batch game where users can log in / register. But there is no point in having passwords if a person standing nearby can peep at the password. Normal password fields mask the characters with asterisks (*).
How can mask characters on a batch file?
I've seen this done on cmd before but I have no clue how.
You can use XCOPY for a hidden input, it can handle nearly all characters and you can also implement a backspace logic.
#echo off
setlocal EnableDelayedExpansion
call :input
echo(
echo '!input!'
if "!input!"=="password" echo OK
exit /b
:input
for /F "tokens=1 delims=# " %%a in ('"prompt #$H# & echo on & for %%b in (1) do rem"') do (
set "\b=%%a"
)
set "input="
:keyLoop
call :GetKey
if not defined key exit /b
if "!key!"=="!\b!" (
if defined input (
set "input=!input:~0,-1!"
<nul set /p ".=!\b! !\b!"
)
) ELSE (
<nul set /p ".=*"
set "input=!input!!key!"
)
goto :keyLoop
:GetKey
setlocal DisableDelayedExpansion
set "key="
for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (
if not defined key set "key=%%L"
)
(
endlocal
set "key=^%key:~-1%" !
exit /b
)
This code should be able to handle all characters, like ^!&%<>.
It's also possible to use backspace to delete the last entered character.
The line set "key=^%key:~-1%" ! seems odd, but it's used to escape the ! character with set "key=^!" ! in the delayed expansion context.
And to avoid problems for all other characters the last ! removes the caret, like in set "key=^A" ! will be evaluated to ``set "key=A"`
Ok, this is a bit different to what you may have had in mind, but that's you're fault for choosing batch for game dev.
The way I see it is you have 3 options:
Use an external program you self made in C#, C++, Python, [etc.]
Howver this requires an application to already do this for you (Which there probably is) or for you to have a knowledge in one of these languages
Use the choice command, to continuously take one key input and wait for the user to hit space to signify the end of the password
However this limits the password characters choice, and makes the program look ugly
Use 2 Batch threads, one that masks and tallies input while the other stores it to a variable.
This may be a bit dodgey at times, at would be a bit complicated but may be the only choice you have.
Now, as I was typing this an idea stuck my head on how to achieve this. Since it might take some time to test I thought I'd post the idea (as it seems to be a soloution to this problem, which has been around for a while).
Logic
One Batch Thread will simply use set /p to store all the input into a variable and upon completion will communicate to the other batch thread through the use of waitfor or a simple directory file.
Another Batch Thread would loop the pause >nul statement and would tally the number of times the pause statement is looped, printing out the appropriate amount of *'s. The other important job of this thread is to sense when the user has finished typing the password, upon which it exits.
Im starting to make this batch program now, but for now I'll just keep you informed of my idea so far.
Code
Login.bat
#echo off
Title Password Please:
:: This is the main code
REM This code relies on Masker.bat
REM SET password to be first and thrid letter,
REM of the day of the week.
set pass=%Date:~0,1%%Date:~2,1%
REM START Masker in the same window using /b tag and create hold.pass:
Echo 0 >%temp%\hold.pass
start /b Masker.bat "%pass%" *
REM Simply take input an leave the rest to Masker.bat
set /p pass_input=:
Echo 1 >>%temp%\hold.pass
cls
if /i "%pass%" NEQ "%pass_input%" (
Title Worng Password
Echo Wrong Password... Sorry.
Sleep 5
Exit
)
Pause > nul
REM Rest of Main game code is below or simply
:: START Main.bat & Exit
Masker.bat
#echo off
Title Password Please:
setlocal enabledelayedexpansion
:: This is not the main code
REM This code is called upon by Login.bat (or the Main.bat game code)
REM CREATE the variables "passlen" and "mask":
set password=%~1
set passlen=0
:miniloop
set /a passlen+=1
if "!password:~%passlen%,1!" NEQ "" goto :miniloop
set password=
set mask=%~2
if "%mask%" EQU "" set mask=*
REM MAIN loop
:loop
cls
for /l %%a in (1,1,%passlen%) do (<nul set /p=%mask%)
sleep -m 150
for /f "usebackq" %%a in ("%temp%\hold.pass") do (if "%%~a" EQU "1" Del %temp%\hold.pass & Exit)
goto :loop
It still needs some more improvements, but I've spent aroung 30 min on it with little success to make it dynamically tell you how many characters you have typed in.
Anyone cane take this up, be my guest. Everything else works fine
Mona
This works without pressing enter after input of the password.
If you enter the correct password, ok.
if you enter a wrong password, it will stop when you enter the 9th character (can be adapted).
It does not care about capitalization.
Problem: the password is stored as pure text in the code
#echo off
setlocal enabledelayedexpansion
set "s= abcdefghijklmnopqrstuvwxyz"
set p=
:loop
choice /C %s% /N >nul
set p=%p%!s:~%errorlevel%,1!&set /p =*<nul
if /i "%p%"=="secured" goto :right
if not "%p:~8,1%"=="" goto :wrong
goto :loop
goto :wrong
:right
echo you entered correct password: %p%
goto :eof
:wrong
echo you entered wrong password: %p%
goto :eof
You may use ReadFormattedLine subroutine for all kind of formatted input. For example, the command below read a password of 8 characters, display asterisks in the screen, and continue automatically with no need to press Enter:
call :ReadFormattedLine password="********" /M "Enter password (8 chars): "
This subroutine is written in pure Batch so it does not require any additional program, and it allows several formatted input operations, like read just numbers, convert letters to uppercase, etc. You may download ReadFormattedLine subroutine from Read a line with specific format.
I wonder is there any way to check that if a label exist in a batch file?
If %input%=ABC (
If Label ABC Exists (
Goto ABC
)
)
How can I do this?
Any help will be appreciated.
findstr /i /r /c:"^[ ]*:%input%\>" "%~f0" >nul 2>nul && goto %input%
Search the label in the current batch file and if no errorlevel, label exists
EDITED - I realized there was an error on the way i was handling the end of the label and was going to edit the answer (it has been edited anyway) and i see the dbenham aclarations. He saw the error and corrected it. Thank you. Nice answer as always, BUT this is worse than what you have exposed.
In this moment i have only a XP to test, but this is what works for me. If anyone can test on later windows versions, please.
First problem: the start of the label. As usual dbenham is correct, and any character in the set [;=,<space><tab>0xFF] can precede, single or repeated, the colon of the label. But, as far as it is the first character on the line, and it does not repeat, almost any character can precede the colon of the label (one exception is other colon). So, the following will work without problems
call :test
goto :test
echo this will not be echoed
X=;=:test
echo Hello
NO, this it not a valid line, if the parser try to execute the label line, a "command not recognized" error will happen, BUT is a valid label to call or goto.
Second problem: end of the label. As dbenham identified, most of us place a space and the list of arguments when the label is used to define a function/procedure. This was the error i realized and what has been corrected in my original answer. BUT, a space (and obviously the end of line) is not the only allowed characters after the label name. So, In the previous sample, any of the following labels will work
:test arguments
:test:arguments
:test>arguments
:test<arguments
:test&arguments
And yes,in this case they are valid commands to the parser and are valid labels
AND, of course, the two "problems" can happen at the same time
call :test
goto :test
echo this will not be echoed
< ;;:test:;; > This WORKS
echo Hello
POST EDIT 1 - It seems all this work was done years ago at dostips.com. Thanks to all who compiled the exaustive list referenced in comments. Next time, i'll search first.
POST EDIT 2 - I've been trying to deal with the limitations of findstr to include all cases. Well, there is no way. There are too many limitations, starting with the impossibility of include the 0xff character in a regular expression.
For a robust and simple solution, the answer from dbenham is the best option.
For a more robust, but STILL INCOMPLETE, no bulletproof version, and more complex than dbenham's answer
#echo off
for /l %%i in (1 1 10) do (
call :testLabelExist "test%%i" && echo Label [test%%i] exist || echo Label [test%%i] does not exist
)
exit /b
:test1
:test2
:test3
x:test4
::test5
:test6:
:test7#
:test8 parameters
:test9 parameters
:test10:myData
:testLabelExist
for /f "delims=" %%t in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /d /c #echo 0x09"'
) do (
findstr /i /m /r /c:"^[^:]*[ %%t]*:%~1[ %%t:;,=+]" /c:"^[^:]*[ %%t]*:%~1$" "%~f0" >nul 2>nul
)
exit /b %errorlevel%
And it still leaves out quoted label names, just to name one failure point.
Here is a refined, more robust version of the MC ND answer. (The original answer, his edit addresses many of these same points).
Labels are case insensitive, so the search should be case insensitive.
A valid label may have additional text after the label, so there are two searches required. The additional text is frequently used as documentation. For example: :label documentation is still a valid label.
findstr /ri /c:"^ *:%input% " /c:"^ *:%input%$" "%~f0" >nul 2>nul && goto %input%
The above should work in most situations, but there are a few unlikely conditions that could cause it to fail.
Any of the following characters can appear before the label - , ; = <space> <tab> <0x255>. They all are treated as spaces when they precede a label. But the search above only allows for <space>. A [class] expression could be used, but including <tab> and <0x255> can be awkward.
In a similar fashion, the label can be terminated by some characters other than <space> (a different list).
The label could contain regular expression meta-characters.
The FINDSTR $ anchor only recognizes <CR><LF> as end of line, so the search can fail if the script uses Unix style <LF> line endings.
The search could be refined to handle most of the above conditions. But it is simpler to simply avoid those conditions in your code. I don't think it is possible to define a bullet proof search using a single FINDSTR. A bullet proof search would require at least two FINDSTRs, and one would have to use the /G:file option - yuck.
Try the following code:
echo off
set "label=sub"
REM next line to reset errorlevel to zero:
(call )
call :%label% 2>nul || (echo %label% not found & exit /b 1)
echo back from %label%
Exit /b 0
:sab
echo here we are
first I would just rethink everything and every time I created a label, I'd go to the top in a "constants or variables area" and predefine each of the labels I had in the file... so after writing, I'd go and manually check all the labels and just do a
for %%l in ( :label1 :label2 :label3 :label4 :EOF ) do set x_labelexistconst_%%l=.
where :label1 :label2 :label3 are all eyeballed and manually entered -- think of it all as if CMD required items to be declared before use like many languages do, but if really lazy you can automate this with findstr as they have with the subroutine examples and have a line like this instead
for /F "tokens=1" %%l in ( 'findstr /i /r /c:"^[ ]*:[a-z0-9]*" "%~f0"' ) do set x_labelexistconst_%%l=.
in the code either of those loaded array of variables would it would be used like
if defined x_labelexistconst_%input% goto %input%
if defined x_labelexistconst_%input% call %input%
… the advantages of this methodology would be that there wouldn't be a CALL and then new findstr "EACH AND EVERYTIME" you did a "if exist label", but instead you would just do it "ONCE" at the beginning to preload the list/array/construct of labels.
I think this option also has the advantage of being able to do && || after the goto call because error levels aren't used to check for labels so
if defined x_labelexistconst_%input% (call %input% && echo call ok || echo call bad)
Ultimately searching file with findstr and CALL and GOTO (which reread the original bat file or something) are both very slow commands and avoiding their use or limiting their use to once or never is the best option for bat files -- especially over slow network connections where the bat file may be large. And it makes the entire script smaller simpler and easier to understand.
I didn't debug this or test this lots but it just seems like a better idea.
If you add this to the top of your function:
if "%~1" == "test" goto:eof
You can use this to check if your function exists:
call :myFunction test 2>nul
if %errorlevel% == 0 call :myFunction
Example:
call :myFunction test 2>nul
if "%errorlevel%" == "0" (call :myFunction) else (echo function does not exist)
goto:eof
:myFunction
if "%~1" == "test" goto:eof
echo Function exists
goto:eof
FOR /L %%i IN (1,1,100) DO (
choice
echo %ErrorLevel%
)
%ErrorLevel% is always 0 no matter what choice you enter.
You are checking the errorlevel the wrong way.
Variables and commands inside a bracket pair like this...
(
command1
command2
command3
)
...act like they were run on a single line, like this command1 & command2 & command3.
Try this at the command line.
choice & echo %errorlevel%
If you execute the above command more than once, you will see that the previous errorlevel is echoed, not the current one.
Or try this on the command line:
set x=yes
( echo %x%
set x=no
echo %x%
)
Your output will be:
yes
yes
Just as if you'd entered echo %x% & set x=no& echo %x%
I like to think of it as the system doesn't have the time to update the variables. (Though it's more accurate to say that the variables only get updated after the entire line is executed.) This is true with all variables, not just the errorlevel.
To make variables in for loops work normally you need to call an internal label in your batch file (or an external batch file) like this.
#echo off
FOR /L %%i IN (1,1,100) DO call :dostuff %%i
goto :eof
:dostuff
choice /m "Question #%1"
echo %ErrorLevel%
==================================== Solution To Question Below
Alternatively, Microsoft has created a method for accessing the current value of variables inside of a bracket pair, they call it 'Delayed Expansion' because the line of code is interpreted twice.
To activate this mode you use the setlocal command with the enableDelayedExpansion switch, and access the variables with the ! character like this. FYI endlocal turns off the effects.
#echo off
setlocal enableDelayedExpansion
for /L %%i in (1,1,100) do (
choice /m "Question #%%i"
echo !ErrorLevel!
)
endlocal
As you can see my first example is easier to code, but my second example is easier to read. Whichever method you use will depend upon your needs and re-usability.
The setlocal command also has the effect of creating temporary variables that die after the endlocal command. This means you don't need to delete them when your batch file ends, and reverts any variables you changed during execution back to their original values. This is nice because you don't have to worry about 'stepping on' any preexisting variables.