Batch "if" statement not working - batch-file

I seem to have a problem with my "if" statements. Currently after the pause in "start" the file just closes and nothing else happens. Chances are, I have no idea what I'm doing.
#echo off
set startnum=0
goto start
:start
pause
set startnum="!startnum!"+1
if "%startnum%"="%0%" goto fail
if "%startnum%"="%1%" goto success
goto start
:success
cls
echo It worked!
echo "%startnum%"
pause.
exit
:fail
cls
echo Failure
pause.
exit

First problem:
set startnum="!startnum!"+1
Evidently, you wish to add 1 to startnum.
Your set command would set startnum to "!startnum!"+1. Literally. To perform arithmetic, you need set /a.
set /A startnum="!startnum!"+1
well, this won't work as "!startnum! isn't numeric. Had you invoked delayedexpansion beforehand, then the value of startnum would have been substituted for !startnum! yielding set /A startnum="0"+1 which makes more, but still not much sense.
set /A startnum=startnum+1
adds 1 to startnum - see set /? from the prompt for documentation.
set /A startnum+=1
would also add 1 to startnum.
Next problem.
if "%startnum%"="%0%" goto fail
Well, you appear to have found lss and all that gang. Problem is that the simple comparison operator is ==, not =.
if "%startnum%"=="%0%" goto fail
Now - what will that do? It will compare "thecontentsofstartnum" to "thecontentsof0". Since both of these arguments are quoted, batch will perform a string comparison. With a string comparison, 123 is less than 89 because 1 is less than 8.
But - you are attempting an equivalence comparison (equ as the operator may be used instead of ==) so the preceding point is simply AAMOI.
The difficulty is %0% which you may believe attempts to extract the value of the variable 0 but actually it replaces %0 with the value of the 0th parameter to the batchfile, which is the batchfile name itself, so you get "batchfilename%" - probably not what you actually wanted.
if "%startnum%"=="0" goto fail
is the way to implement that test.

The first IF statement is preprocessed by cmd.exe to
if ""!startnum!"+1"="test.bat" goto fail
which is a completely invalid IF condition.
cmd.exe outputs a syntax error message because of "="test.bat"" and exits batch file processing. This can be seen by debugging the batch file.
The solution is using right syntax for
assigning a value to an environment variable,
an arithmetic expression,
and last but not least the IF condition itself.
The batch file code fixed:
#echo off
set "startnum=0"
goto Begin
:Begin
set /A startnum+=1
if "%startnum%" == "0" goto Fail
if "%startnum%" == "1" goto Success
goto Begin
:Success
cls
echo It worked!
echo "%startnum%"
pause
exit /B
:Fail
cls
echo Failure
pause
exit /B
It would be safe here to remove all double quotes on both IF conditions.
One more hint: Don't use the name of a command like START as label. It works, but it should be avoided in case of ever adding to batch file the command START and searching for either command or label.
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.
call /? ... explains how to reference batch file arguments.
cls /?
echo /?
exit /?
goto /?
if /?
pause /?
set /?
Further read the answers on following questions:
Batch file comparison of variable with constant fails
Why is no string output with 'echo %var%' after using 'set var = text' on command line?
Where does GOTO :EOF return to?
Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files

Related

Batch - Call script inside script with vertical bar ("|") causes error

If a script which should get exited in subroutines without closing the terminal when calling EXIT 1. There for I use this if which calls the script again.
This worked fine until I now discovered some issue with a quoted vertical bar as a parameter "!". I get an error stating that the command is misspelled.
Here is the part of the script that fails:
#ECHO OFF
SETLOCAL DISABLEDELAYEDEXPANSION
IF "%selfWrapped%"=="" (
REM this is necessary so that we can use "exit" to terminate the batch file,
REM and all subroutines, but not the original cmd.exe
SET selfWrapped=true
%ComSpec% /s /c ""%~0" %*"
GOTO :EOF
)
echo %*
ENDLOCAL
EXIT /B 0
Call:
test.cmd "hello world" "|"
Expected Output:
"hello world" "|"
I checked the the value of %* inside the IF but for it seems totally legitimate to use a vertical bar as well as any other quoted string.
So...
Why does the script fails?
How can I fix it?
I do not agree with some of the description in the link.
See exit /? accurate help description.
exit exits the interpreter.
exit 1 exits the interpreter with exitcode 1.
exit /b has similar behavior as goto :eof which exits
the script or called label. Errorlevel is not reset so allows
errorlevel from the previous command to be accessable after
exit of the script or the called label.
exit /b 1 exits the script or the called label with errorlevel 1.
If you oddly use exit /b at a CMD prompt, it is going to exit the interpreter.
Main code:
#ECHO OFF
SETLOCAL DISABLEDELAYEDEXPANSION
SET args=%*
SET "self=%~f0"
IF "%selfWrapped%"=="" (
#REM this is necessary so that we can use "exit" to terminate the batch file,
#REM and all subroutines, but not the original cmd.exe
SET "selfWrapped=true"
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !ComSpec! /s /c ""!self!" !args!"
"!ComSpec!" /s /c ""!self!" !args!"
GOTO :EOF
)
ECHO(%*
EXIT /B 0
Both use of GOTO :EOF and EXIT /B 0 will exit the script.
ENDLOCAL is implied at exit of the script.
Explicit use of ENDLOCAL is for when you want to end the
current local scope and continue the script. As always, being
explicit all the time is a choice.
Setting %* to args keeps the double quoting paired.
Quoting i.e. set "args=%*" can cause issue sometimes
though not using quotes allow code injection i.e.
arguments "arg1" ^& del *.*. If the del *.* is not going
to execute at the set line, then it will probably happen
at the ComSpec line. For this example, I chose not quote.
So, it is a choice.
You are using disabled expansion at start of the script. That
saves the ! arguments which is good. Before you execute
ComSpec though, enable delayed expansion and use !args!
which is now protected from the interpreter now not seeing |
or any other special character which may throw an error.
Your script fails as the | argument is exposed.
C:\Windows\system32\cmd.exe /s /c ""test.cmd" " | ""
The above is echoed evaluation of the ComSpec line with
setting #ECHO ON. Notice the pairing of quotes
i.e. "", " " and "". Notice the extra spacing inserted
around the | character as the interpreter does not consider
it as part of a quoted string.
Compared to updated code changes of echoed evaluation...:
"!ComSpec!" /s /c ""!self!" !args!"
The string between the quotes remain intact. No extra spacing
inserted into the string. The echoed evalution looks good and
executes good.
Disclaimer:
Expressing the workings of CMD is like walking a tight rope.
Just when you think you know, fall off the rope you go.
I don't see the necessity to append the parameter to your %ComSpec% /s /c ""%~0" %*" at all.
As you already use a variable (selfWrapped) to detect, if the wrapper call is necessary, you could also put your arguments into a variable.
set args=%*
Then you can simply use !args! in your child instance.
#ECHO OFF
setlocal DisableDelayedExpansion
IF "%selfWrapped%"=="" (
#REM this is necessary so that we can use "exit" to terminate the batch file,
#REM and all subroutines, but not the original cmd.exe
SET "selfWrapped=true"
SET ^"args=%*"
"%ComSpec%" /s /c ""%~f0""
GOTO :EOF
)
:Main
setlocal EnableDelayedExpansion
ECHO(!args!
EXIT /B 0
Now the only problem left, is the set args=%*.
If you can't control the content, then there is no way to access %* in a simple safe way.
Think of this batch invokations
myBatch.bat "abc|"
myBatch.bat abc^|
myBatch.bat abc^|--"|"
But you could use How to receive even the strangest command line parameters?
or Get arguments without temporary file
Btw. You could spare your child process, you can also exit from a function
Look at Exit batch script from inside a function
One correction to above answers.
Yes, ENDLOCAL is implied at the end of the script, but there's a catch.
I've found that with nested scripts, if you don't ENDLOCAL before you EXIT /B 1 you will not get your return code of 1 at the next level out script.
If you only ever EXIT /B 0, then this will not matter as the default return code is 0.

Why does Windows command interpreter output the error message: ) was unexpected

I'm making a Minecraft modding tool using a batch file. But on execution of the batch file the Windows command interpreter outputs the syntax error message:
) was unexpected
I can't figure out why.
This is my code:
#echo off
cd mods
setlocal enabledelayedexpansion
set "selected=1"
call:print 1
call:print 2
:menu
choice /c wse>nul
if "%errorlevel%"=="2" (
if not !selected! GEQ !a! (
set /a "selected+=1"
cls
call:print 1
call:print 2
)
)
if "%errorlevel%"=="1" (
if not !selected!==1 (
set /a "selected-=1"
cls
call:print 1
call:print 2
)
)
if "%errorlevel%"=="3" (
)
goto menu
:print
if "%1"=="1"set a=0
echo.
if "%1"=="1" (
echo Uninstalled:
) else (
echo Installed:
)
echo.
for %%f in (*.jar) do (
if "%1"=="1" (
if NOT EXIST
"C:/Users/Coornhert/AppData/Roaming/.minecraft/mods/%%~nf.jar" (
set /a "a+=1"
if "!a!"=="!selected!" (
echo -%%~nf
) else (
echo %%~nf
)
set "b=!a!"
)
) else (
if EXIST "C:/Users/Coornhert/AppData/Roaming/.minecraft/mods/%%~nf.jar" (
set /a "a+=1"
if "!a!"=="!selected!" (
echo -%%~nf
) else (
echo %%~nf
)
set "b=!a!"
)
)
)
goto :eof
And it works, but when I hit s, execution terminates with the error message.
Folder structure of folder containing the batch file:
mods
Foo.jar
Foo2.jar
Folder structure of target folder:
C:\Users\Coornhert\AppData\Roaming\.minecraft\mods
Foo.jar
I partly do not understand what this batch file should do, but here is the batch file rewritten with several improvements.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem cd /D "%~dp0mods"
pushd "%~dp0mods"
set "a=0"
set "selected=1"
call :PrintIt 1
call :PrintIt 2
:Menu
choice /C wse /N
if errorlevel 3 popd & endlocal & goto :EOF
if errorlevel 2 goto AddOne
if %selected% == 1 goto Menu
set /A selected-=1
cls
call :PrintIt 1
call :PrintIt 2
goto Menu
:AddOne
if %selected% GEQ %a% goto Menu
set /A selected+=1
cls
call :PrintIt 1
call :PrintIt 2
goto Menu
:PrintIt
if %1 == 1 set "a=0"
echo/
if %1 == 1 (echo Uninstalled:) else echo Installed:
echo/
for %%I in (*.jar) do (
if %1 == 1 (
if not exist "%APPDATA%\.minecraft\mods\%%~nI.jar" (
set /A a+=1
if !a! == %selected% (echo -%%~nI) else echo %%~nI
set "b=!a!"
)
) else (
if exist "%APPDATA%\.minecraft\mods\%%~nI.jar" (
set /A a+=1
if !a! == %selected% (echo -%%~nI) else echo %%~nI
set "b=!a!"
)
)
)
goto :EOF
It does nothing useful as is, but batch code in question is also not useful at all.
The applied improvements are:
The command SETLOCAL is moved to top of file. The reason is:
It pushes path of current directory on stack.
It pushes state of command extensions on stack.
It pushes state of delayed expansion on stack.
It pushes the memory address of the current environment variables table on stack.
It creates a copy of the current environment variables table in memory and makes this new environment variables table active.
It sets command extensions and delayed expansion according to the specified parameters if the command is called with parameters at all.
The command ENDLOCAL is executed before leaving batch file. The reason is:
It deletes the current environment table which means no environment variable defined in this batch file exists anymore after ENDLOCAL except it existed already before execution of command SETLOCAL.
It pops memory address of previous environment table from stack and uses this address resulting in restoring initial environment variables.
It pops state of delayed expansion from stack and disables/enables delayed expansion accordingly.
It pops state of command extensions from stack and disables/enables command extensions accordingly.
It pops previous current directory path from stack and sets current directory to this path to restore the current directory.
So the entire command process environment is restored on exit of this batch file to exactly the same environment as it was on starting the batch file.
This makes it possible to call this batch file from within another batch file or from within a command prompt window with no impact on calling batch file or command process.
The command CD could be extended to include drive and path of argument 0 which is the full path of the batch file ending with a backslash because the subdirectory mods is most likely always expected in directory of the batch file and it should not matter what is the current directory on running the batch file.
But cd /D "%~dp0mods" could fail if the batch file is located on a network share accessed using UNC path and therefore command PUSHD is used instead working with enabled command extensions also for UNC paths.
In all programming and scripting languages it is required that variables are defined and initialized with a value before being used the first time. For that reason the environment variables a and selected are defined at top of the batch file with default values. By the way: a is a very bad name for a variable. Why? Search for a in batch file. It is quite often found on not using special find features like whole word only, isn't it.
PRINT is a command as it can be seen on running in a command prompt window print /?. While it is possible to use command names as labels or as names for subroutines, it is not advisable to do so as it could be confusing for readers of the batch file.
The command CHOICE has the option /N to hide the list of choices in the prompt. It is better to use this option than redirecting the output of CHOICE to device NUL.
The very old but still valid Microsoft support article Testing for a Specific Error Level in Batch Files explains that if errorlevel X means that the condition is true if the exit code of previous command or application is greater or equal X. The command CHOICE with 3 choices exits always with 1, 2 or 3 as exit code. So it is best to use:
if errorlevel 3 rem Do something on third choice avoiding fall through to next line.
if errorlevel 2 rem Do something on second choice avoiding fall through to next line.
Do something on first choice.
The advantage of using this method is that it even works with CHOICE within a command block on which if %ERRORLEVEL% == X fails because of delayed expansion would be required and usage of if !ERRORLEVEL! == X.
The integer comparison if %selected% GEQ %a% would not work if the two arguments would be enclosed in double quotes as the double quotes are also interpreted as part of the arguments to compare. For that reason using if "%selected%" GEQ "%a%" would result in running a string comparison instead of an integer comparison. For more information about comparing values with IF look on answer on Exit a for loop in batch.
It is safe here to omit the double quotes also on the other IF conditions with == operator running string comparisons because the environment variables selected and a must be both defined before running this IF condition and therefore both variables are defined at top of the batch file.
The answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? explains why set "variable=value" should be always used to assign a value to an environment variable or delete an environment variable on omitting the value. And this answer also explains why on set /A variable=expression the double quotes can be usually omitted as whitespace characters are interpreted completely different within an arithmetic expression. The exception is usage of set /A with 1 or more commands on same command line on which double quotes around variable=expression would be also needed.
The batch file should be exited when the batch file user enters e or E to take third choice. This could be done with just goto :EOF, or with exit /B which is an alias for goto :EOF, or with just exit which always exits entire command process independent on calling hierarchy which is not recommended. Windows command interpreter would implicitly restore the initial stack before finishing batch file execution. But it is nevertheless good coding practice to pop from stack with code which was pushed on stack before with code. For that reason there is used popd & endlocal & goto :EOF. See answer on Single line with multiple commands using Windows batch file for more information about usage of multiple commands on one command line.
The list of predefined environment variables of used user account is output on running in a command prompt window the command set. One predefined Windows environment variable is APPDATA with path to application data of current user account. This environment variable should be used instead of a fixed path to application data directory of user account.
And the directory separator on Windows is the backslash character \ and not slash character / as on Unix and Mac.
The usage of f as loop variable is not recommended as this is also a loop variable modifier. %%~f can be interpreted by Windows command interpreter as value of loop variable f without surrounding double quotes or as incomplete loop variable reference because of missing loop variable after %%~f which could be also interpreted as full file name of ?. So it is better to use # or $ as loop variable or upper case letters to avoid such a confusion on interpreting the loop variable reference. Loop variables are case-sensitive.
I prefer for IF conditions with an ELSE branch the coding style
if condition (
command
) else (
command
)
But here in this batch file with command being just a short ECHO command the code is better readable on being more compact with using:
if condition (echo short message) else echo other short message
Delayed expansion for an environment variable referenced within a command block started with ( and ending with matching ) is only needed if the environment variable is also modified in same command block. Therefore environment variable a must be referenced in body of FOR with usage of delayed expansion while environment variable selected can be referenced as usual because of not modified within this command block at all.
It is better to use echo/ to output an empty line instead of echo.. For the reason read the DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/
For a basic understanding of the used commands, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
choice /?
cls /?
echo /?
endlocal /?
for /?
goto /?
if /?
popd /?
pushd /?
rem /?
set /?
setlocal /?

Missing Operator Error In Batch Game

:teattack1
set /a health-=!monsterdmg! # It said missing operator here
pause
set /a monsterhealth-=!playerdmg! # And Here
pause
if "!monsterhealth!" == lss 0 goto tewin
pause
goto testencounter
pause
goto encountermenu
I keep getting Missing Operator
I serched how to fix it but did not find anything
You need to add:
Setlocal EnableDelayedExpansion
to your script to allow the use of !
Check here for an overview of what this does and why its needed
Open a command prompt window, enter set /? and read all output help pages carefully.
It is explained that when using set for an arithmetic expression, i.e. set /A, then variable names can be specified directly without expansion. Windows command processor interprets automatically each string not being a number or an operator as name of a variable and access the current variable value on evaluating the arithmetic expression.
Next run in command prompt window if /? and read again all output help pages carefully.
The line
if "!monsterhealth!" == lss 0 goto tewin
is definitely invalid as it contains the operator == and the operator lss which of course can't work.
This improved code is with all coding mistakes fixed.
:teattack1
set /a health-=monsterdmg
pause
set /a monsterhealth-=playerdmg
pause
if %monsterhealth% LEQ 0 goto tewin
pause
goto testencounter
pause
goto encountermenu
However, the environment variables health, monsterdmg, monsterhealth and playerdmg should all exist and have an integer value assigned as otherwise Windows command interpreter uses value 0 for each variable making the code not really useful.

Troubles with goto(cmd)

#echo off
:WriteAgain
set x=
set /p Variables=Write your expression
set /a x=%Variables%
if %errorlevel% neq 0 goto ErrorOccured
echo %x%
goto :eof
:ErrorOccured
echo.Your expression is not valid
goto WriteAgain
:eof
Greeting, It is supposed to be a simple calc, but for some reasons, when "if" works(for 1/0) it looks like "goto" doesnt(I may be mistaken here). Could you help me to solve this problem? Also I am thinking about typing error in any txt: should I use 2>txt_name.txt after neq 0 or what?
goto :eof is a built-in construction to return from a subroutine (call :subroutine). It exits current batch file when used not in a subroutine.
Rename the label to end, for example.
Or use exit instead of goto to the end of batch file.
For output redirection examples and syntax see http://ss64.com/nt/syntax-redirection.html so in your case echo prints to standard output thus > must be used:
echo Your expression is not valid >errlog.txt
Some utilities indeed print the errors to STDERR and the standard > won't catch the messages, so command 2>errlog.txt should be used.
You don't put
goto :eof
Try using
goto eof
And also I am not sure but maybe the name eof is no good (Is used by CMD itself) so keep things simple and use any other name like "exit, problem, fail, etc..."

batch "While" loop doesn't work, but why?

echo off
set /a a=0
:start
if %a% LEQ 21(
echo test
set /a a=%a%+1
goto start
)
I don't get it...
I only get a Syntax-error when the loop starts.
The code you posted isn't a valid bash script it's a batch script to echo test 20 times in a bash script using a for loop like this:
#/bin/bash
for i in {1..20}; do
echo test
done
Much clearer and readable than batch scripts, welcome to bash!
Besides the error already explained by Bali C, you should note that your code have a potential problem. In this command: set /a a=%a%+1 the %a% value is expanded just one time when the if command is executed. In this case your code run by chance because the goto command cause the if be executed again in each loop, but if your program would be larger and more complex, and this set command would be executed several times inside parentheses, the code would fail because %a% will be expanded just one time to the value that a variable had before enter the parentheses.
The way to solve this problem is easy in this case: just eliminate the percent signs, because set /a a=a+1 command can directly take the values of the variables. However, there is much more involved here! For further details, type set /? and pay attention to "delayed variable expansion" description.
You are missing a space between 21 and ( which will make it compare the number in the loop to 21(, also breaking the if statement, the latter being why you are getting a syntax error.
Add a space and it works fine.
echo off
set /a a=0
:start
if %a% LEQ 21 (
echo test
set /a a=%a%+1
goto start
)

Resources