Edit:
I shall thank Magoo for the detailed answer.
I edited my batch file according to Magoo's answer.
Here is the new code:
#echo off
mode 62,22
:Begin
choice /c 12345 /M "Insert a number (min. 1 - max. 5): "
set "CommandVar0=%errorlevel%"
choice /c 12345 /M "Please confirm the number you entered: "
set "CommandVarC=%errorlevel%"
if %CommandVarC% NEQ %CommandVar0% (
echo Insert a matching number between 1~5 for confirmation
goto Begin
)
if %CommandVar0% == 1 set /P CommandVar1=Insert ID:
if %CommandVar0% == 2 set /P CommandVar1=Insert ID: & set /P CommandVar2=Insert ID:
if %CommandVar0% == 3 set /P CommandVar1=Insert ID: & set /P CommandVar2=Insert ID: & set /P CommandVar3=Insert ID:
if %CommandVar0% == 4 set /P CommandVar1=Insert ID: & set /P CommandVar2=Insert ID: & set /P CommandVar3=Insert ID: & set /P CommandVar4=Insert ID:
if %CommandVar0% == 5 set /P CommandVar1=Insert ID: & set /P CommandVar2=Insert ID: & set /P CommandVar3=Insert ID: & set /P CommandVar4=Insert ID: & set /P CommandVar5=Insert ID:
:loop
start EXAMPLE.exe %CommandVar1%
if defined commandvar2 start EXAMPLE.exe %CommandVar2%
if defined commandvar3 start EXAMPLE.exe %CommandVar3%
if defined commandvar4 start EXAMPLE.exe %CommandVar4%
if defined commandvar5 start EXAMPLE.exe %CommandVar5%
rem wait 10 seconds
for /L %%y in (1,1,10) do (
timeout /t 1 > nul
tasklist /fi "imagename eq EXAMPLE.exe" |find ":" > nul
if NOT errorlevel 1 goto begin
)
taskkill /f /im EXAMPLE.exe
timeout /t 5
goto loop
The issue I'm having right now is that;
Due to my lack of knowledge, I don't know how to expand this command:
set "commandvar1="
set "commandvar2="
set /P CommandVar1=Insert ID:
if not defined commandvar1 goto begin
if "%CommandVar0%"=="2" (
set /P CommandVar2=Insert ID:
if not defined commandvar2 goto begin
)
e.g. to set "commandvar5=" etc. So instead I'm still using my complicated command which does the job for now.
How can I add the normal timer to this command?
for /L %%y in (1,1,10) do (
timeout /t 1 > nul
tasklist /fi "imagename eq EXAMPLE.exe" |find ":" > nul
if NOT errorlevel 1 goto begin
)
I want a timer to actually display the countdown of the command being executed like the usual timeout /t command if possible.
Addressing the core of the problem:
start EXAMPLE.exe %CommandVar1% & start EXAMPLE.exe %CommandVar2%
Will start two instances of example.exe passing the contents of commandvar? which may be response to "Insert ID:" or (rarely) nothing
Immediately thereafter, you are executing tasklist and looking for :.
If either the tasks have actually been established and is running, the tasklist output will NOT contain : so find will NOT find : and hence set errorlevel to 1
If the tasks have not been started or have both terminated, then the output of tasklist would be INFO: No tasks are running which match the specified criteria. which contains : and hence errorlevel will be set to 0.
Your next line says if **either** task is running (errorlevel 1), say "not found" and go to Begin otherwise (ie. neither task is running) then delay, taskkill the non-existent tasks, delay again and go back to Begin.
Another hidden problem is that when the routine returns to begin, if CommandVar2 has been set, then it will not be cleared, so it will remain set for the next input-cycle, regardless of the data input.
So we need to tackle the logic problems as well as the batch-syntax-specific problems.
Tip: Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. If the syntax set var="value" is used, then the quotes become part of the value assigned.
First thing to tackle is the choose-and-check part. The value assigned to a variable by a set /p can be any string. That string may be the expected string or an unexpected string (obviously). An unexpected string may take many forms - it might contain spaces for instance, or unbalanced quotes, or % or parentheses or other strings or characters that have significance in the batch language. Also, responding with Enter to a set /p will leave the variable unchanged, not set it to nothing.
When the obvious parameter-check is executed via if %var1% == %var2% ..., then the values of the variables are substituted, and the comparison executed, so had the user input break me for instance, then batch would attempt to execute if break me == 2 ... and batch would see me where it expects to see a comparison-operator like ==.
The usual way to by-pass this problem is by "quoting both sides", ie. if "%var1%" == "%var2%" ... This is useful in the case that you may be processing some string containing spaces that is being returned from a command, but what happens if the user enters break " me ?
Consequently, the recommendation is to use the choice command to make 1-character choices (see choice /? from the prompt for documentation, or thousands of items on SO for examples)
Hence, your code should be
choice /c 12 /M "Insert a number (min. 1 - max. 2): "
set "CommandVar0=%errorlevel%"
choice /c 12 /M "Please confirm the number you entered: "
set "CommandVarC=%errorlevel%"
At this point, we are sure that commandvar? contains 1 or 2, so we can indeed use
if %CommandVarC% NEQ %CommandVar0% (
echo Insert a matching number between 1~2 for confirmation
goto Begin
)
Note: I've used the string-assignment syntax, even though the value assigned from %errorlevel% must be a pure-numeric string. It's also possible to use set /a var=%errorlevel% here. set /a assigns a value that we know is numeric (and can also do calculations) and needs no quotes.
There's no point in else... here -it serves to complicate matters. The code will simply continue to the next line if the goto is not executed.
if %CommandVarC% GTR 2 (echo Maximum number is 2.
goto Begin
) else (
is not required. We know that CommandVarC can only be 1 or 2.
if %CommandVar0% == 1 set /P CommandVar1=Insert ID:
if %CommandVar0% == 2 set /P CommandVar1=Insert ID: & set /P CommandVar2=Insert ID:
Well, this is overcomplicated, and doesn't quite do what is required. CommandVar1 is always required, and CommandVar2 will remain set if it has been set in the past.
Try:
set "commandvar1="
set "commandvar2="
set /P CommandVar1=Insert ID:
if not defined commandvar1 goto begin
if %CommandVar0% == 2 set /P CommandVar2=Insert ID:
Well, primitively. We know commandvar0 is a single digit, so we can use a simple ==. We also may or may not have received a response for commandvar2.
So
if "%CommandVar0%"=="2" (
set /P CommandVar2=Insert ID:
if not defined commandvar2 goto begin
)
may be more "according to Hoyle"
Summing up (before we tackle the next part), try using
choice /c 12 /M "Insert a number (min. 1 - max. 2): "
set "CommandVar0=%errorlevel%"
choice /c 12 /M "Please confirm the number you entered: "
set "CommandVarC=%errorlevel%"
if %CommandVarC% NEQ %CommandVar0% (
echo Insert a matching number between 1~2 for confirmation
goto Begin
)
set "commandvar1="
set "commandvar2="
set /P CommandVar1=Insert ID:
if not defined commandvar1 goto begin
if "%CommandVar0%"=="2" (
set /P CommandVar2=Insert ID:
if not defined commandvar2 goto begin
)
If we get past this point, I gather we should launch the executable, possibly twice, then wait for it to run, with a run-time limit of 10 seconds.
So,
start EXAMPLE.exe %CommandVar1%
rem only start a second instance if commandvar2 has been specified
if defined commandvar2 start EXAMPLE.exe %CommandVar2%
rem wait a sec...or 10
for /L %%y in (1,1,10) do (
timeout /t 1 >nul
tasklist /fi "imagename eq EXAMPLE.exe" |find ":" > nul
if NOT errorlevel 1 goto begin
)
rem kill tasks still running
taskkill /f /im EXAMPLE.exe
timeout /t 5
goto begin
for /L is explained at for /? from the prompt. Here, it counts %%y from 1 to 10 in steps of 1.
Each step delays 1 second (the >nul suppresses the countdown) then tests whether an executable is running. If NONE is running, then the tasklist will report a line containing : and errorlevel will be set to 0.
IF ERRORLEVEL n is TRUE if the runtime (ie. current) errorlevel is n or greater than n. IF ERRORLEVEL 0 is therefore always true. IF NOT ERRORLEVEL 1 is a test for errorlevel=0.
So, if there is no executable running, goto begin.
Once this test has been done 10 times, if an executable is running, then we exit the for /L loop and taskkill the remaining task(s) and back to the future...
--- To add Commandvars ---
The method is limited to 9 items without complications.
Change the 12 in the choice commands to 123456789 (or whatever your limit is).
add
for /L %%e in (1,1,9) do set "commandvar%%e="
in place of set "commandvar1=" set "commandvar2="
This will set all variablenames commandvar1..9 to nothing.
Then
for /L %%e in (1,1,9) do (
set /P CommandVar%%e="Insert ID for instance %%e: "
if not defined commandvar%%e (
if %%e==1 goto begin
goto startprocs
)
)
:startprocs
for /L %%e in (1,1,9) do if defined commandvar%%e call start "Instance %%e" u:\dummy.bat %%commandvar%%e%%
Note that the set/p has a quoted prompt-string. This allows the string to have a terminal space. It's possible to have that string "nude" but if your editor gets over-enthusiastic and removes terminal spaces, then you'd lose that one. It also serves to make the space obvious (stray spaces can cause havoc in batch)
Then input your data.
If the first entry is empty, back to the start. You could change that goto to an exit, which would terminate the cmd instance. or exit /b which would terminate this batch, but keep your cmd session open.
If any other entry is empty, it's the signal to start processing as we've finished our list.
The for /L for starting the processes has been changed a little. First, the start is immediately followed by a quoted string. This is the title which would appear in the process's window. It could be empty if you wish, but it should not be omitted in the general case. Start regards the first quoted string in the command as the window title to be used, so if it is omitted, a quoted string in the command may be eaten by start, used as a title and not passed to the process being started.
Then there's the call. This invokes a parser trick. Suppose %%e is 2. The command would be executed as call start "Instance 2" u:\dummy.bat %%commandvar%%e%% where %%commandvar%%e%% would be interpreted left-to-right as %commandvar2% as % is the "escape character" for %. Escaping a character is where we want to use a character without its special meaning. %%e is replaced by 2 first because it's an active metavariable (loop-control character or parameter-number). CALLin the resultant command start "Instance 2" u:\dummy.bat %commandvar2% then substitutes the current value of commandvar2 for the process.
=== Summary thought ===
With this new approach to have multiple commandvaar entries, there appears to be no reason for the nomination of a number of entries and then checking that number, so that entire section could be regarded as redundant.
If you remove that section, then there's no reason for the restriction to 9 items. You could set the limit to any number you like by replacing the 9 with 22, 35, 957 - whatever takes your fancy. The process will now start whenever you reply Enter to a prompt Insert ID for instance ??:
Parham.8, the error level is set after each command has been executed, if the next command succeeds, the error level will take 0 independent of the previous one. Use && to execute a command only if the previous command's error level is 0. Ex.
start EXAMPLE.exe %CommandVar1% && start EXAMPLE.exe %CommandVar2% && tasklist /fi "imagename eq EXAMPLE.exe" |find ":" > nul
Related
I know that when comparing stuff for equality in a batch file it's common to enclose both sides in quotes, like
IF "%myvar% NEQ "0"
But when comparing using "greater than" or "less than", this doesn't work because the operands would then be treated as strings with quotes around them. So you can instead just do
IF %myvar% GTR 20000
The caveat is that if the variable %myvar% isn't declared, it would be like doing
IF GTR 20000
which is a syntax error.
I came up with the following workaround:
IF 1%myvar% GTR 120000
which I'm hoping would result in IF 1 GTR 120000 if myvar is undefined, and it seems to work.
Is this a safe way to compare numbers and accounting for undeclared variables, or did I just open up a whole new can of caveats?
Let us assume the batch file contains:
#echo off
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range 0 to 20000.
set /P "MyVar=Enter number [0,20000]: "
As I explained by my answer on How to stop Windows command interpreter from quitting batch file execution on an incorrect user input? the user has the freedom to enter really anything including a string which could easily result in breaking batch file execution because of a syntax error or resulting in doing something the batch file is not written for.
1. User entered nothing
If the user hits just key RETURN or ENTER, the environment variable MyVar is not modified at all by command SET. It is easy to verify in this case with environment variable MyVar explicitly undefined before prompting the user if the user entered a string at all with:
if not defined MyVar goto PromptUser
Note: It is possible to use something different than set "MyVar=" like set "MyVar=1000" to define a default value which can be even output on prompt giving the user the possibility to just hit RETURN or ENTER to use the default value.
2. User entered a string with one or more "
The user could enter a string with one or more " intentionally or by mistake. For example pressing on a German keyboard key 2 on non-numeric keyboard with CapsLock currently enabled results in entering ", except German (IBM) is used on which CapsLock is by software only active for the letters. So if the user hits 2 and RETURN quickly or without looking on screen as many people do on typing on keyboard, a double quote character instead of 2 was entered by mistake by the user.
On MyVar holding a string with one or more " all %MyVar% or "%MyVar%" environment variable references are problematic because of %MyVar% is replaced by Windows command processor by user input string with one or more " which nearly always results in a syntax error or the batch file does something it was not designed for. See also How does the Windows Command Interpreter (CMD.EXE) parse scripts?
There are two solutions:
Enable delayed expansion and reference the environment variable using !MyVar! or "!MyVar!" as now the user input string does not affect anymore the command line executed by cmd.exe after parsing it.
Remove all " from user input string if this string should never contain a double quote character.
Character " is definitely invalid in a string which should be a number in range 0 to 20000 (decimal). For that reason two more lines can be used to prevent wrong processing of user input string caused by ".
set "MyVar=%MyVar:"=%"
if not defined MyVar goto PromptUser
The Windows command processor removes all doubles quotes already on parsing this line before replacing %MyVar:"=% with the resulting string. Therefore the finally executed command line set "MyVar=whatever was entered by the user" is safe on execution.
The example above with a by mistake entered " instead of 2 results in execution of set "MyVar=" which undefines the environment variable MyVar which is the reason why the IF condition as used before must be used again before further processing of the user input.
3. User entered non-valid character(s)
The user should enter a positive decimal number in range 0 to 20000. So any other character than 0123456789 in user input string is definitely invalid. Checking for any invalid character can be done for example with:
for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser
The command FOR does not execute goto PromptUser if the entire string consists of just digits. In all other cases including a string starting with ; after zero or more digits results in execution of goto PromptUser because of input string contains a non-digit character.
4. User entered number with leading 0
Windows command processor interprets numbers with a leading 0 as octal numbers. But the number should be interpreted as decimal number even on user input it with one or more 0 at beginning. For that reason the leading zero(s) should be removed before further processing variable value.
for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"
if not defined MyVar set "MyVar=0"
FOR removes all 0 at beginning of string assigned to MyVar and assigns to loop variable I the remaining string which is assigned next to environment variable MyVar.
FOR runs in this case set "MyVar=%%I" even on user entered 0 or 000 with the result of executing set "MyVar=" which undefines environment variable MyVar in this special case. But 0 is a valid number and therefore the IF condition is necessary to redefine MyVar with string value 0 on user entered number 0 with one or more zeros.
5. User entered too large number
Now it is safe to use the command IF with operator GTR to validate if the user entered a too large number.
if %MyVar% GTR 20000 goto PromptUser
This last verification works even on user entering 82378488758723872198735897 which is larger than maximum positive 32 bit integer value 2147483647 because of the range overflow results in using 2147483647 on execution of this IF condition. See my answer on weird results with IF for details.
6. Possible solution 1
An entire batch file for safe evaluation of user input number in range 0 to 20000 for only decimal numbers is:
#echo off
set "MinValue=0"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
if not defined MyVar goto PromptUser
set "MyVar=%MyVar:"=%"
if not defined MyVar goto PromptUser
for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser
for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"
if not defined MyVar set "MyVar=0"
if %MyVar% GTR %MaxValue% goto PromptUser
rem if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
This solution gives the batch file writer also the possibility to output an error message informing the user why the input string was not accepted by the batch file.
The last IF condition with operator LSS is not needed if MinValue has value 0 which is the reason why it is commented out with command REM for this use case.
7. Possible solution 2
Here is one more safe solution which has the disadvantage that the user cannot enter a decimal number with one or more leading 0 being nevertheless interpreted decimal as expected usually by users.
#echo off
set "MinValue=0"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
if not defined MyVar goto PromptUser
setlocal EnableDelayedExpansion
set /A "Number=MyVar" 2>nul
if not "!Number!" == "!MyVar!" endlocal & goto PromptUser
endlocal
if %MyVar% GTR %MaxValue% goto PromptUser
if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
This solution uses delayed environment variable expansion as written as first option on point 2 above.
An arithmetic expression is used to convert the user input string to a signed 32 bit integer interpreting the string as decimal, octal or hexadecimal number and back to a string assigned to environment variable Number on which decimal numeral system is used by Windows command processor. An error output on evaluation of the arithmetic expression because of an invalid user string is redirected to device NUL to suppress it.
Next is verified with using delayed expansion if the number string created by the arithmetic expression is not identical to the string entered by the user. This IF condition is true on invalid user input including number having leading zeros interpreted octal by cmd.exe or a number entered hexadecimal like 0x14 or 0xe3.
On passing the string comparison it is safe to compare value of MyVar with 20000 and 0 using the operators GTR and LSS.
Please read this answer for details about the commands SETLOCAL and ENDLOCAL because there is much more done on running setlocal EnableDelayedExpansion and endlocal than just enabling and disabling delayed environment variable expansion.
8. Possible solution 3
There is one more solution using less command lines if the value 0 is out of valid range, i.e. the number to enter by the user must be greater 0.
#echo off
set "MinValue=1"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
set /A MyVar+=0
if %MyVar% GTR %MaxValue% goto PromptUser
if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
This code uses set /A MyVar+=0 to convert the user entered string to a 32-bit signed integer value and back to a string as suggested by aschipfl in his comment above.
The value of MyVar is 0 after command line with the arithmetic expression if the user did not input any string at all. It is also 0 if the user input string has as first character not one of these characters -+0123456789 like " or / or (.
A user input string starting with a digit, or - or + and next character is a digit, is converted to an integer value and back to a string value. The entered string can be a decimal number or an octal number or a hexadecimal number. Please take a look on my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files which explains in detail how Windows command processor converts a string to an integer value.
The disadvantage of this code is that a by mistake input string like 7"( instead of 728 caused by holding Shift on pressing the keys 2 and ( on a German keyboard is not detected by this code. MyVar has value 7 on user enters by mistake 7"(. Windows command processor interprets just the characters up to first not valid character for a decimal, hexadecimal or octal number as integer value and ignores the rest of the string.
The batch file using this code is safe against an unwanted exit of batch file processing because of a syntax error never occurs independent on what the user inputs. But a by mistake wrong input number is in some cases not detected by the code resulting in processing the batch file further with a number which the user did not want to use.
Answering the call to nitpick
Mofi has been requesting I write my own solution here, that is "shorter" as I pointed out to him the way he wrote his code using & instead of ( followed by a command then a carriage return and another command, or `( followed by a carriage return, followed by another command followed by a carriage return followed by another command) sets a precedent which makes this a hard task to agree on.
I also did not think this was the POINT of providing the answers perse, I mean I used to, but when changes are minor, and mainly fixing logic, or offering a minorly different solution, is that really a big difference? Does that really warrant being a separate answer?
That said, I don't see a better way without editing his response.. but this still leaves unresolved questions on what is being judged shorter.
Unfortunately as well, in discussing with Mofi he has edited his answer to one that can result in invalid choices.
While I have pointed this out, and I'm sure this was just a minor oversite on his part, I feel like not posting the code here has contributed to him actively deteriorating the quality of his question, which is always a possible outcome when nitpicking.
while Mofi was the driving force in that activity, I don't like the effect it's had on him as I was trying to avoid exactly this effect on my code by not getting into it, so I have decided to post the code comparison to bring some closure for them.
Please not, I will post his original code (the most recent one that did not use the erroneous method), and then refactored to how I would write it, and I will post my Original code, and then refactored to how I believe he would write it (may not be in that order but I will call out each)
So below is the result
Mofi Original:
This is hard to say if you should count every line, there are some instances where & is used to queue up commands and the IFS never use Parenthesis which I wouldn't generally do.
#echo off
set "MinValue=0"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
if not defined MyVar goto PromptUser
setlocal EnableDelayedExpansion
set /A "Number=MyVar" 2>nul
if not "!Number!" == "!MyVar!" endlocal & goto PromptUser
endlocal
if %MyVar% GTR %MaxValue% goto PromptUser
if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
My Code Refactored to Mofi's Form
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET /A "_Min=-1","_Max=20000"
:Menu
CLS
SET "_Input="
REM Prompt user for a positive number in range %_Min% to %_Max%.
SET /P "_Input=Enter number [%_Min%,%_Max%]: "
SET /A "_Tmp=%_input%" && if /I "!_input!" EQU "!_Tmp!" if !_Input! GEQ %_Min% if !_Input! LEQ %_Max% SET _Input & pause & GOTO :EOF
GOTO :Menu
Mofi's Code Refactored
Mofi's above code Refactored to my more compacted form Where ( have the first command follow except when used on an IF statement, and ) follow the last command. This also makes the entire portion that really does the validation EASY to discern, it is only the portion within the :PromtUser function, not counting REM lines or blank lines this is 13 lines of code.
#(SETLOCAL
echo off
SET /A "MinValue=0","MaxValue=20000")
CALL :Main
( ENDLOCAL
EXIT /B )
:Main
CALL :PromptUser MyVar
REM Output value of environment variable MyVar for visual verIFication.
SET MyVar
PAUSE
GOTO :EOF
:PromptUser
SET "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
SET /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
IF NOT DEFINED MyVar GOTO :PromptUser
Setlocal EnableDelayedExpansion
SET /A "Number=MyVar" 2>nul
IF not "!Number!" == "!MyVar!" (
Endlocal
GOTO :PromptUser )
Endlocal
IF %MyVar% GTR %MaxValue% (
GOTO :PromptUser )
IF %MyVar% LSS %MinValue% (
GOTO :PromptUser )
GOTO :EOF
My Code in My Compact Form
To compare here is my code also in the same compact form I refactored Mofi's code to above. Again, only the lines inside of the function itself are "doing the heavy lifting" here and need compare. I did forget that when I worked on my code originally I was trying to match Mofi's form, and it allowed me an extra nicety in keeping my && ( in the following line or all as a single line. So I will post two varients
#(SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET /A "_Min=-1","_Max=20000" )
CALL :Main
( ENDLOCAL
EXIT /B )
:Main
CALL :Menu _input
REM Output value of environment variable _input for visual verIFication.
SET _input
PAUSE
GOTO :EOF
:Menu
CLS
SET "_input="
REM Prompt user for a positive number in range %_Min% to %_Max%. Store it in "_input"
SET /P "_Input=Enter number [%_Min%,%_Max%]: "
SET /A "_Tmp=%_input%" && (
IF /I "!_input!" EQU "!_Tmp!" IF !_Input! GEQ %_Min% IF !_Input! LEQ %_Max% GOTO :EOF )
GOTO :Menu
My Code in My Compact Form 2
#(SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET /A "_Min=-1","_Max=20000" )
CALL :Main
( ENDLOCAL
EXIT /B )
:Main
CALL :Menu
REM Output value of environment variable _input for visual verification.
SET _input
PAUSE
GOTO :EOF
:Menu
CLS
SET "_input="
REM Prompt user for a positive number in range %_Min% to %_Max%. Store it in "_input"
SET /P "_Input=Enter number [%_Min%,%_Max%]: "
SET /A "_Tmp=%_input%" || GOTO :Menu
IF /I "!_input!" EQU "!_Tmp!" (
IF !_Input! GEQ %_Min% (
IF !_Input! LEQ %_Max% (
GOTO :EOF ) ) )
GOTO :Menu
I expect to be able to buy items in the shop and have it do the correct subtraction. In the code below, you start out with 10 gold pieces, (gp), but whenever option 2 or 4, to spend 5gp or 1gp, is entered, it takes away all 10gp. I know that it's because it doesn't go past the first if %input%== 1 but I don't know how to fix it, I have tried almost everything, including if/else statements, but I may not have been doing them properly.
:shop
cls
echo You see a middle aged man behind the counter
echo of the shop as well as a younger man sweeping the floors.
echo "Hello young travelers. Welcome, is there anything
echo I can help you find?"
:purchase
echo --------------------------------------------------------
echo %name%
echo Gold: %gp%
echo --------------------------------------------------------
echo.
echo 1) Battleaxe 10gp Stats: 1d8(S) Versatile(1d10)
echo 2) Mace 5gp Stats: 1d6(B)
echo 3) L.Crossbow 20gp Stats: 1d8(P) Range 80/320
echo 4) 5 Bolts 1gp Equip with Crossbow
echo 5) Go Back
echo.
set /p input=Enter:
if %input%== 5 goto main
if %input%== 1
if %gp% LSS 10 goto nofunds
set /a gp= %gp% - 10
goto shopcont
if %input%== 2
if %gp% LSS 5 goto nofunds
set /a gp= %gp% - 5
goto shopcont
if %input%== 3
if %gp% LSS 20 goto nofunds
set /a gp= %gp% - 20
goto shopcont
if %input%== 4
if %gp% LSS 1 goto nofunds
set /a gp= %gp% - 1
goto shopcont
goto shop
:nofunds
cls
echo You don't have enough gold to purchase that item.
pause >nul
goto shop
:shopcont
cls
echo Would you like to purchase anything else?
goto purchase
I am still new at this so examples and explanations would be wonderful!
Please do not tell me to use choice.exe instead of Set /P, unless it will fix the actual issue.
In the below example, I have used Set /P under :purchase to satisfy your ill advised stipulation to not use choice.exe, (which I used under :shopcont instead).
:shop
ClS
Echo You see a middle aged man behind the shop counter, as well as a
Echo younger man sweeping the floor.
Echo(
Echo "Welcome young travellers, is there anything I can help you with?"
:purchase
Set "input="
Set "invalid=true"
Echo(
Echo ------------------------------------------------------------------
Echo(%name%
Echo Gold: %gp%
Echo ------------------------------------------------------------------
Echo(
Echo 1. Battleaxe 10gp [Stats: 1d8(S) Versatile(1d10)]
Echo 2. Mace 5gp [Stats: 1d6(B)]
Echo 3. L.Crossbow 20gp [Stats: 1d8(P) Range 80/320]
Echo 4. 5 Bolts 1gp [Equip with Crossbow]
Echo 5. Go Back
Echo(
Set /P "input=Enter: "
For /L %%A In (1,1,5) Do If "%%~A" == "%input:"=%" Set "invalid="
If Defined invalid ClS & GoTo purchase
If %input% Equ 5 GoTo main
If %input% Equ 4 If %gp% GEq 1 Set /A gp -=1 & GoTo shopcont
If %input% Equ 3 If %gp% GEq 20 Set /A gp -=20 & GoTo shopcont
If %input% Equ 2 If %gp% GEq 5 Set /A gp -=5 & GoTo shopcont
If %input% Equ 1 If %gp% GEq 10 Set /A gp -=10 & GoTo shopcont
Echo You do not have enough gold to purchase that item.
:shopcont
"%__AppDir__%choice.exe" /M "Would you like to purchase anything else"
If "%ErrorLevel%"=="1" ClS & GoTo purchase
Please note that I have tried to replicate that which you posted in your question, this assumes that %gp% and %name% are already defined prior to this code section and that the label :main exists elsewhere in your unposted code.
You asked for examples and explanations, but those are readily available under each command's usage information and via web searches, so I will not be pointlessly including such things.
The usage of command set /P is not recommended for a simple choice menu. A simple typing mistake by user of batch file can easily result in a syntax error on further processing of the batch file detected by Windows command processor resulting in an unexpected exit of batch file execution. A user playing this batch file game by double clicking on it will not be happy on typing for example by mistake " instead of 2 and suddenly the console window is closed because cmd.exe exited batch file processing because of a serious syntax error caused by " and not good coded batch file.
See also:
How to stop Windows command interpreter from quitting batch file execution on an incorrect user input?
Safe number comparison in Windows batch file
However, the main problem is the used syntax on all IF commands. The syntax of command IF can be seen by opening a command prompt, running if /? and reading the output help. if %input%== 1 without a command or a command block starting with ( and ending with matching ) to execute on condition is true on same line results in a syntax error on batch file execution. This can be seen on debugging the batch file.
The indentations have no meaning for cmd.exe regarding to process flow. Windows command processor is not Python. Windows command processor executes one command line respectively command block after the other independent on how many leading spaces or tabs are used to indent the command lines.
See also: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
An arithmetic expression is the string after set /A evaluated by cmd.exe on execution of the batch file. The help output on running set /? explains that within an arithmetic expression it is possible to reference the value of an environment variable by writing just its name without % or ! around variable name. That has two advantages:
If the environment variable does not exist at all, Windows command processor uses value 0 for not existing environment variable. Using %NotExistingVariable% in an arithmetic expression results in a syntax error because of this string is replaced by nothing which usually results in a missing operand error on evaluation of the arithmetic expression.
Environment variables can be modified with arithmetic expressions multiple times in a command block without usage of delayed expansion.
For that reason set /a gp= %gp% - 10 is not a recommended syntax to decrement the environment variable gp by 10. Better is using set /A gp=gp - 10 and best set /A gp-=10.
The DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ explains that echo. can fail to print an empty line into console window and that echo/ or echo( is better for this task.
A minimal, complete, and verifiable example for this task is following batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "gp=50"
set "name=B1u3Soul"
:Shop
cls
echo You see a middle aged man behind the counter
echo of the shop as well as a younger man sweeping the floors.
echo "Hello young travelers. Welcome, is there anything
echo I can help you find?"
:Purchase
echo --------------------------------------------------------
echo %name%
echo Gold: %gp%
echo --------------------------------------------------------
echo/
echo 1) Battleaxe 10gp Stats: 1d8(S) Versatile(1d10)
echo 2) Mace 5gp Stats: 1d6(B)
echo 3) L.Crossbow 20gp Stats: 1d8(P) Range 80/320
echo 4) 5 Bolts 1gp Equip with Crossbow
echo 5) Go Back
echo/
%SystemRoot%\System32\choice.exe /C 12345 /N /M "Enter: "
if errorlevel 5 goto Main
if errorlevel 4 set "GoldAmount=1" & goto GoldInPurse
if errorlevel 3 set "GoldAmount=20" & goto GoldInPurse
if errorlevel 2 set "GoldAmount=5" & goto GoldInPurse
set "GoldAmount=10"
:GoldInPurse
if %gp% LSS %GoldAmount% goto NoFunds
set /A gp-=GoldAmount
echo/
%SystemRoot%\System32\choice.exe /C YN /N /M "Would you like to purchase anything else [Y/N]? "
cls
if errorlevel 2 goto Main
goto Purchase
:NoFunds
echo/
echo You don't have enough gold to purchase that item.
pause >nul
goto Shop
:Main
endlocal
See also single line with multiple commands using Windows batch file for an explanation of operator & as used in this batch file.
It would be of course possible to use just choice instead of %SystemRoot%\System32\choice.exe. But the usage of full qualified file name (drive + path + file name + file extension) makes the batch file independent on environment defined outside the batch file. For this batch file it does not matter how PATH and PATHEXT is defined on starting the batch file. It depends only on environment variable SystemRoot defined by Windows and which is not modified by applications or users in general.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
choice /?
cls /?
echo /?
endlocal /?
goto /?
if /?
pause /?
set /?
setlocal /?
See also:
What are the ERRORLEVEL values set by internal cmd.exe commands?
Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?
Why is no string output with 'echo %var%' after using 'set var = text' on command line?
Microsoft article about Using command redirection operators
I am trying to use the choice command in a batch script to take the default input if the user doesn't want to stop kicking off a report. I wrote the below script but instead of waiting 10 seconds and kicking off the report, it is recursively echo-ing the first line of the code over and over until I kill the script. Is there something wrong that I am doing?
My Code:
CHOICE /C YN /N /T 10 /D N /M "Run Report Y or N?"
IF ERRORLEVEL 1 SET REPORT=RunTheReport:
IF ERRORLEVEL 2 SET REPORT=DontRunIt:
ECHO You chose to run %REPORT%
P.S: I replaced the report commands with an echo statement but it is still not working
You have found one of the few instances where the difference between .cmd and .bat is important.
The sample you posted works correctly, when you save that code to a file named script.bat.
script.bat
CHOICE /C YN /N /T 10 /D N /M "Run Report Y or N?"
IF ERRORLEVEL 1 SET REPORT=RunTheReport
IF ERRORLEVEL 2 SET REPORT=DontRunIt
ECHO You chose to run %REPORT%
When the user presses Y the errorlevel is set to 1. The first IF line matches and sets REPORT=RunTheReport. The second IF line does not match, and the end result is Run.
When the user presses N the errorlevel is set to 2. The first IF line matches and sets REPORT=RunTheReport. The second IF line matches and sets REPORT=DontRunIt. The end result is Don't Run.
In a .bat file, the IF ERRORLEVEL ladder will continue and execute every matching SET line. The last matching SET will be the one used.
If you save that same code to a file named script.cmd CMD behaves a little bit differently. One difference is that the SET command now sets ERRORLEVEL to 0 when it successfully sets a variable.
When the user presses Y the errorlevel is set to 1. The first IF line matches and sets REPORT=RunTheReport. The second IF line does not match, and the end result is Run, just like the .bat case.
When the user presses N the errorlevel is set to 2. The first IF line matches, sets REPORT=RunTheReport, and sets ERRORLEVEL to 0. The second IF line then does NOT match. The end result is also Run, which is wrong.
In a .cmd file, only the first matching SET line is run.
Therefore, if your file is named with a .cmd extension, you must reverse the order of the IF ERRORLEVEL lines, so that the correct one is the only one executed.
script.cmd
CHOICE /C YN /N /T 10 /D N /M "Run Report Y or N?"
IF ERRORLEVEL 2 SET REPORT=DontRunIt
IF ERRORLEVEL 1 SET REPORT=RunTheReport
ECHO You chose to run %REPORT%
There is an easy way to avoid this issue and make your code work in both types of file. The IF ERRORLEVEL N syntax is deprecated. It is confusing because it matches as greater-than-or-equal, rather than equal. Instead, use the newer IF %errorlevel% EQU N syntax.
script2.{bat|cmd}
CHOICE /C YN /N /T 10 /D N /M "Run Report Y or N?"
IF %ERRORLEVEL% EQU 1 SET REPORT=RunTheReport
IF %ERRORLEVEL% EQU 2 SET REPORT=DontRunIt
ECHO You chose to run %REPORT%
Save as test.bat and run this script. It works well. Edited to show different approaches possible. It runs a bit faster:
#echo off
CHOICE /C YN /N /T 10 /D N /M "Run Report Y or N?"
IF "%errorlevel%"=="2" (SET "REPORT=DontRunIt:"
) else (SET "REPORT=RunTheReport:")
ECHO You chose to run %REPORT%
timeout 5
exit /b
Is it possible to make an if statement for batch based off a user opening a program?
I tried this:
color c
echo off
cls
:props
if open WINWORD.exe (echo a & pause 10 & exit)
goto props
Doing so will simply post the error 'WINWORD was unexpected at this time.' and kill the command prompt.
What I am trying to achieve is a batch file that will:
Look if anyone is opening WINWORD.exe
Once somebody has opened the file the command prompt will display 'a'
Exit the command prompt after 10 seconds once 'a' was displayed.
You can search for the process using tasklist.exe, together with find to count the instances:
set PROC=winword.exe
tasklist /FI "IMAGENAME eq %PROC%" | find /C /I "%PROC%"
I used a variable %PROC% to specify the process (*.exe) name just for the sake of convenience.
find will also exit with the return code (ErrorLevel) of 1 if no instances have been found. With a simple if statement you can do what you requested:
if not ErrorLevel 1 ((echo a) & timeout /T 10 /NOBREAK & exit)
I replaced the pause 10 by the timeout command because pause waits for the user to press any key (any arguments are ignored). The switch /NOBREAK means to ignore any key presses.
The parenthesis around echo a avoids a trailing space to be echoed as well.
So all together, the following should do what you asked for:
color c
echo off
set PROC=winword.exe
cls
:props
tasklist /FI "IMAGENAME eq %PROC%" | find /C /I "%PROC%" > nul
if not ErrorLevel 1 ((echo a) & timeout /T 10 /NOBREAK & exit)
timeout /T 1 /NOBREAK > nul
goto props
The (optional) > nul portion after find prevents it from displaying the found number of instances.
The second delay time timeout /T 1 has been inserted to avoid massive CPU load within this loop structure.
Writing a batch script. How can I verify '3' NUMERIC digits have been inputted by a user with the "/p" prompt?
Prompt to user:
SET /P SITEID=ENTER SITE # (i.e. 001 - MUST BE 3 DIGITS):
I need to verify 3 digits have been inputted, if good continue with script. If NOT good re-prompt user to message of my choosing.
#echo off
setlocal
set "Input="
:Prompt
set /p "Input=ENTER SITE # (i.e. 001 - MUST BE 3 DIGITS): "
if not defined Input goto Prompt
set "Input=%Input:"=%"
for /f "delims=0123456789" %%A in ("%Input%") do goto Prompt
for /f "tokens=1* delims=0" %%A in ("10%Input%") do set "Input=%%B"
if %Input%0 geq 10000 goto Prompt
set "Input=000%Input%"
set "Input=%Input:~-3%"
echo Success = %Input%
pause
endlocal
exit /b 0
Script Explanation:
Prompt for Input
Validate Input
Remove Poison Quotation Characters
Verify Input is only Numbers
Remove leading 0's for comparison
Verify Input is less than 1000
Add back leading 0's
Display Success
If any validation fails, the user is prompted again
Update:
Fix leading 0 removal
Add example of how to add back leading 0's
:loop
SET /P "SITEID=ENTER SITE # (i.e. 001 - MUST BE 3 DIGITS):"
echo("%SITEID:"= %"|findstr /rbe /c:"""[0-9][0-9][0-9]""" >nul || ( echo FAIL & goto loop )
It takes the %SITEID% variable, removes quotes if present and send the data into findstr to test against a regular expression (/r switch) : at the begining of data (/b switch) initial quote (from the echo command) three numeric characters, a closing quote (from the echo command) and end of string (/e switch). If the findstr does not found a match, errorlevel is set and the code after || is executed, printing a message to console an returning to the :loop label to ask again.