Batch '+ unexpected' error - batch-file

I'm trying to code a collatz conjecture tester in batch (it's math stuff). The point of the file is to test whether a number is even, and if it is divide by two. If the number is odd, it's supposed to multiply by three and add one. This should loop over and over. Every time I try and run this file, it allows me to input the number, then it says '+ was unexpected at this time.' What is my error here?
#echo off
color f0
title Collatz Conjecture Tester
echo/
echo Enter the number you want to test.
echo/
echo/
set /p number=
:start
set /a test=%number% %% 2
if %test% EQU 0 (
set /a number=%number% * 1/2
) else (
set /a number=(%number% * 3) + 1
)
echo/
echo Result: %number%
timeout /t 1 >nul
goto start

When you have parentheses inside of if statements, for loops, or other code blocks, it's important that you escape any other inner closing parentheses so that the batch interpreter knows you aren't done yet.
Right now, batch is reading your if statement like this:
else (
set /a number=(%number% * 3
)
+ 1
And so it thinks you're terminating the else early and throwing a + 1 in there for some reason. To get around this, you can escape the inner close parenthesis with a ^.
if %test% EQU 0 (
set /a number=%number% * 1/2
) else (
set /a number=(%number% * 3^) + 1
)

SomethingDark already found the root cause of the problem and showed a proper solution in their answer.
An alternative way to avoid trouble with parentheses and also some operators like &, |, ^, <<, >>, etc. is to enclose the entire expression of set /A within a pair of quotation marks:
if %test% EQU 0 (
set /A "number=%number% * 1/2"
) else (
set /A "number=(%number% * 3) + 1"
)
If you do that consequently, you do not have to think of proper escaping any more.
Note that the % operator always needs to be escaped like %% in a batch file.
In case delayed expansion is enabled, the ! operator needs to be escaped like ^! in case the expression is placed within "", and by ^^! otherwise.

Related

batch if variable is less than variable doesn't work

Have I done something wrong or why this isn't working? I am quite new with batch. It says "The syntax of the command is incorrect."
if %nm1% lss %nm2% (
echo voitit:%voitat%
set /p "tupla=Voitonmaksu.1 tuplaus.2 (1/2)."
)
if %nm1%==%nm2% (
set /a voitat=%voitat% / 2
echo voitit:%voitat%
set /a voitot=%voitot% + %voitat%
pause
goto peli
)
if %nm2% lss %nm1%(
echo voitit:0
pause
goto peli
)
if %tupla%==1 (
set /a voitot=%voitot% + %voitat%
pause
goto peli
)
if %tupla%==2 goto tuplaus
set /a voitat=%voitat% / 2
set /a voitot=%voitot% + %voitat%
These commands won't work as expected because of delayedexpansion (many many SO articles on this - use the search facility in the top bar)
BUT since you are using set/a - the syntax allows the variables to be expressed "nude" - without the % delimiters, when the delayedexpansion quirk becomes irrelevant (but you should read up on it anyway - to obviate the inevitable follow-up question.)
if %nm2% lss %nm1%(
There must be a space between %nm1% and (
If either argument is non-numeric (probably not, given their names) then the arguments must be "quoted" (applies to any if where the arguments may contain spaces)

An If statement in my .bat script isn't working as expected

What is supposed to happen is that you input a number between 1 and 1,048,567. The program checks if your input is actually a number between 1 and 1,048,567. If your input is a valid number then it will continue onto the next bit of code. If the input is invalid then it will display a message saying it is invalid then loop back and ask you for input again.
When I run this however, I input anything and it says invalid input even if I did input a number between 1 and 1,048,567.
Code:
:setup
#echo off
title Pokemon Shiny Sim
set delay = nul
set count = 0
set chance = 4096
:settings
:setChance
cls
echo Set shiny chance (1 in x). Range: (1-1,048,567)
echo Leave blank for 1 in 4096.
set /p chance = Input:
set /a chance = %chance%+0
if %chance% GEQ 1 (
if %chance% LEQ 1048567 (
goto setDelay
)
)
echo Invalid Input.
pause
goto setChance
:setDelay
cls
echo Set delay between attempts in seconds. Range: (1-60).
echo Leave blank for no delay.
set /p delay = Input:
set /a delay = %delay%+0
if %delay% == nul (
goto loopStart
)
if %delay% GEQ 1 (
if %delay% LEQ 60 (
cls
goto loopStart
)
)
echo Invalid Input.
pause
goto settings
:loopStart
set /a count = %count%+1
set /a rand = %random% %% %chance%+1
if %rand% == 1 (
echo Attempt: %count% | Shiny: Yes!
pause
)
else (
echo Attempt: %count% | Shiny: No
)
goto loopStart
I suggest to read first debugging a batch file and second the answer onWhy is no string output with 'echo %var%' after using 'set var = text' on command line?
Next look on your rewritten code below:
:setup
#echo off
title Pokemon Shiny Sim
set "delay=0"
set "count=0"
set "chance=4096"
:settings
:setChance
cls
echo Set shiny chance (1 in x). Range: (1-1,048,567)
echo Leave blank for 1 in 4096.
set /P "chance=Input: "
set /A chance+=0
if %chance% GEQ 1 if %chance% LEQ 1048567 goto setDelay
echo Invalid input.
pause
goto setChance
:setDelay
cls
echo Set delay between attempts in seconds. Range: (1-60).
echo Leave blank for no delay.
set /P "delay=Input: "
set /A delay+=0
if %delay% == 0 goto loopStart
if %delay% GEQ 1 if %delay% LEQ 60 cls & goto loopStart
echo Invalid input.
pause
goto settings
:loopStart
set /A count+=1
set /A rand=%random% %% chance + 1
if %rand% == 1 (
echo Attempt: %count% ^| Shiny: Yes!
pause
) else (
echo Attempt: %count% ^| Shiny: No
)
goto loopStart
All spaces around the equal signs are removed in this batch code.
The command line set "delay = nul" is modified to set "delay=0" because the condition if %delay% == nul is never true after execution of set /a delay = %delay%+0 resulting in execution of set /a delay = nul + 0 which results in assigning value 0 to environment variable delay on nul not existing as environment variable with that name having an integer value. The result of a valid arithmetic expression is always a number assigned as string to the environment variable and never a string like nul.
set /a chance = %chance%+0 is modified to set /A chance+=0 and set /a delay = %delay%+0 is modified to set /A delay+=0 because otherwise the input check is insecure as the user has for example the freedom to enter | for variable chance resulting in execution of command line set /a chance = |+0 which cause an unexpected exit of batch file execution.
Never use %variable% or !variable! in an arithmetic expression as not needed in general.
The help output on several pages on running set /? in a command prompt window explains in chapter about usage of set /A that each string which can't be interpreted as number or operator isĀ interpreted automatically as name of an environment variable whose current value should be converted to an integer on evaluation of the expression. If the environment variable is not defined at all or its value can't be successfully converted to a 32-bit signed integer, it is replaced in the expression by integer value 0.
There are exceptions like the usage of a random number in an arithmetic expression which requires %random% or !random! or when a variable name contains a space character or a character which would be interpreted as operator. In such cases it is necessary that the Windows command interpreter replaces the environment variable name already in preprocessing state or immediately before execution of the command set by random value respectively value of the environment variable.
set /a chance = %chance%+0 makes it also possible that the user of this batch file enters for example PROCESSOR_LEVEL or PROCESSOR_REVISION and this input although not being a number at all would be handled as valid because those two strings are the names of environment variables having numbers as values. PROCESSOR_REVISION has by default a hexadecimal number assigned which can be processed nevertheless completely or partly as number by command set.
Another syntax error is in block
if %rand% == 1 (
echo Attempt: %count% | Shiny: Yes!
pause
)
else (
echo Attempt: %count% | Shiny: No
)
The keyword else must be on same line as the closing ) of true branch of the IF condition separated from ) with a space character.
And redirection operator | must be escaped with caret character ^ to be interpreted as literal character to output into console window.
Note: set /A chance+=0 makes it still possible to enter for example 170 percent or 170X which results in chance having value 170 and therefore input is valid although in real the entered string is not a number.
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.
cls /?
echo /?
goto /?
if /?
pause /?
set /?
title /?
I haven't tested the code yet, but I found some fatal issues in the code.
Mis-setting variable
set /a count = %count% + 1
This sets a variable count (Note the space!). Remove the space! Also, this can be shortened to set /a count+=1.
ECHOing special characters
| is one of the special characters reserved for redirection in batch. To properly echo it, use echo string ^| string instead.
Poor IF statement practice
if %rand% == 1 (
only works when %rand% is alphanumeric. If %rand% is space, the cmd.exe sees:
if == 1 (
which is incorrect.
To correct it, do
if "%rand%"=="1" (
Alternatively, use EQU for numeric comparison, and == for string comparison.

I'm not able to create 10 files with following batch code

Following is the code in which I want to create 10 files if the counter reach 10 but the comparison is not working am I missing something or am I doing something wrong? It creates only one file and prints as following in that one file
10 == 0 set
#echo off
set limit=10
set count=0
:start
set count = %count% + 1
echo %limit% == %count% set > YouAreAnIdiot%random%.txt
if %count%==%limit%
exit 0
else
goto start
two errors in one line: set count = %count% + 1:
a) the space between count and = is part of your variable name. (It would be %count %)
b) to calculate with set, you need the /a parameter:
set /a count=%count% + 1
Surprisingly, set /a doesn't care for the additional space, but get used to the syntax without spaces around the = - this keeps life simple.
set /a doesn't need the percent signs with variables, so set /a count=count+1 also works.
There is a short form to do that:
set /a count+=1
Also your if statement will not work. The complete construct has to be on one (logical) line:
if %count%==%limit% (
exit 0
) else (
goto start
)
(note the spaces around the parantheses - they are critical)

Calling an IF statement | Batch

Error Code:
If was unexpected at this time
The code sets the stats for each player then it calls the function to determine the amount of damage based on the stats and other variables
What I want to do is call a function with two IF statements within it
Example:
#echo off
set /a level=5
set /a opponentLevel=5
set /a movePower=50
set moveType=physical
goto player
:damageCalculator
if %moveType%==physical (
pause
set /a damage=(((( 2 * %level% / 5 + 2) * %attackStat% * %movePower% / %opponentDefenceStat%) / 50) + 2)
set /a opponentHealth-=%damage%
) else if %moveType%==special (
set /a damage=(((( 2 * %level% / 5 + 2) * %spAttackStat% * %movePower% / %opponentSpDefenceStat%) / 50) + 2)
set /a opponentHealth-=%damage%
)
goto :eof
:player
set type=fire
set/a health=19
set/a attackStat=10
set/a defenceStat=10
set/a spAttackStat=11
set/a spDefenceStat=10
set/a speedStat=12
:opponent
set /a opponentHealth=18
set /a opponentAttackStat=10
set /a opponentDefenceStat=9
set /a opponentSpAttackStat=8
set /a opponentSpDefenceStat=9
set /a opponentSpeedStat=13
:attack
pause
cls
call :damageCalculator
echo It did %damage% Damage!!
pause>nul
Is this just one of those things that batch can't do?
update per edited question
Your script has a few small issues. Firstly, I'll point out that it's often helpful to rem out #echo off when trying to track down the cause of a problem. Doing so in this case shows that the line causing your error is your first set /a line.
The reason your if statements are failing is because the parentheses within your math are being treated as the end of your code block. As soon as the closing parenthesis of ...5 + 2) is encountered, the batch interpreter treats that as the end of your if statement, and therefore gets confused when there's more stuff on the line. You need to quote your set /a statements to prevent this.
set /a "damage=(((( 2 * level / 5 + 2) * attackStat * movePower / opponentDefenceStat) / 50) + 2)"
See how I did set /a "variable=value" there? You could also escape the closing parentheses with a caret -- e.g. ^), but quoting the "var=value" is a little easier to read I think. The quotation marks keep the contents within the context of the command. They're basically saying, "This is a single token. It's a distinct part of the set command, not the code block as a whole." As a bonus, you can also see that the % signs aren't needed to retrieve variable values within a set /a command. Neat, huh?
You've got another problem. Since you're setting the %damage% variable within the same parenthetical code block as you're retrieving it, %damage% is going to be evaluated too early. You could setlocal enabledelayedexpansion and retrieve it as !damage!, and that would certainly work. But there's a simpler fix. Just put it outside the if statements. You're doing set /a opponentHealth-=damage regardless of whether the move type is physical or special, anyway, right?
:damageCalculator
if "%moveType%"=="physical" (
set /a "damage=(((( 2 * level / 5 + 2) * attackStat * movePower / opponentDefenceStat) / 50) + 2)"
) else if "%moveType%"=="special" (
set /a "damage=(((( 2 * level / 5 + 2) * spAttackStat * movePower / opponentSpDefenceStat) / 50) + 2)"
)
set /a opponentHealth-=damage
goto :eof
But still, you should include setlocal just below #echo off to keep from junking up your environment with variables that have no meaning outside the scope of this script.
Here's another tip. You can combine many statements within a single set /a line.
Before:
set /a level=5
set /a opponentLevel=5
set /a movePower=50
set "moveType=physical"
After:
set /a level=5, opponentLevel=5, movePower=50
set "moveType=physical"
original answer
Change your second if to else if or add a carriage return before it (after the preceding parenthesis). Also, move your goto :EOF to the next line after the closing parenthesis.
The explanation for this is that the cmd interpreter treats a parenthetical code block as a single command. So in essense,
if [true condition] (
action
) second command
is being evaluated as
if [true condition] (action) second command
which results in an error because there's no line break or other separator between the first command (the if) and the second. Here's an example of a valid compound command:
#echo off & setlocal
Compound command need:
unconditional AND (& -- right side always executes after the left)
example: endlocal & goto :EOF
logical AND (&& -- right side executes only if the preceding command exited zero)
example: find /i "text" "textfile.txt" >NUL && echo Text was found
pipe (| -- makes the command on the right do something with the output generated by the command on the left)
example: dir /s /b | more
logical OR (|| -- right side executes only if the preceding command exited non-zero)
example: tasklist | find /i "iexplore.exe" >NUL || echo Internet Explorer not running
... or in the case of an if statement, else.
if condition 1 (
action 1
) else if condition 2 (
action 2
) else (
action 3
)
Or if you want to check that two conditions are true:
if %var% leq 10 if %var% geq 5 (
echo Variable is between 5 and 10.
)
Of course, you don't have to use compound statements. There's no reason why a function can't have multiple if statements that aren't compound.
:fn <str>
if "%~1"=="fish" (echo Bloop.)
if "%~1"=="cats" (echo Meow.)
if "%~1"=="dogs" (echo Grrrr.)
goto :EOF
... is perfectly valid.

Create a random number and check if it is odd or even

I am looking to create a batch file that, when ran, creates a random integer from 0 to 100 and then runs it through an if statement that checks whether it is odd or even.
I've been having some trouble, so here is what I have so far.
#echo off
set /a num=%random% %%100 +1
if ( num % 2 == 0 ) {
//even
}
else
{
//odd
}
The error I get is - "num was unexpected at this time"
#ECHO OFF
SETLOCAL
set /a num=%random% %%100 +1
SET /a nummod2=num %% 2
IF %nummod2% == 0 (ECHO %num% is even) ELSE (ECHO %num% is odd)
GOTO :EOF
Conventional IF syntax is if [not] operand1==operand2 somethingtodo where operand1 and operand2 are both strings. If the strings contain separators like Space,Tab or other characters that have a special meaning to batch then the string must be "enclosed in quotes".
Fundamentally, if compares strings. The operator must be one of a fixed set of operators [== equ neq gtr geq lss leq] hence cmd was objecting to num where it expected an operator.
A calculation cannot be performed within a if statement's parameters.
%% is required to perform the mod operation, since % is a special character that itself needs to be escaped by a %.
Note that { and } are ordinary characters with no special meaning to batch and remarks must follow
rem remark string
There is a commonly-used exploit which is
::comment
actually, a broken label, which can have unforeseen side-effects (like ending a code-block)
The following prints a random number between 0 and 99, and prints whether it's even or odd:
#echo off
set /a num = %random% %% 100
echo %num%
set /a odd = num %% 2
if %odd% EQU 1 (echo odd) else (echo even)

Resources