Batch file doesn't work, how can I to fix it? - batch-file

I want to write a game using a batch script, but there is an error and I don't know what to do.
#echo on
goto log
:inicio
set "esquerda=caverna"
set "esquerda2=6"
set /p "run=Digite a acao: "
if %run%==help (
goto help) else (
goto comands)
:carvena
echo oi>"%userprofile%\desktop\oi.txt"
pause>nul
:comands
echo %run% >"%tmp%\reino_de_merlock\personagem\comands.txt"
cd "%tmp%\reino_de_merlock\personagem"
type comands.txt | find /i "andar_esquerda" && echo andar>andar_esquerda.txt
if exist andar_esquerda.txt (
set "andar=esquerda1"
del /q andar_esquerda.txt
goto andar1)
:log
for /f %%a in ('type "%tmp%\reino_de_merlock\personagem\agilidade.txt"') do (
set "agilidade=%%a" & goto inicio)
:andar1
if "%andar%" == "esquerda1" (
set /a andar = agilidade - esquerda2
if %andar% LEQ 0 (
goto %esquerda%)) 1>nul 2>nul
pause>nul
The error is in set /a andar = agilidade - esquerda2, the variable becomes esquerda1 and the right it's -2, what is the error?

Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
In your case,
if %andar% LEQ 0 (
goto %esquerda%)
is contained within the block that starts
if "%andar%" == "esquerda1" (
and consequently uses the values of andar and esquerda1 as they were when the if "%andar%" == "esquerda1" was reached, not on the new value of andar calculated within the loop.
Look for many SO entries on 'delayed expansion'

Related

Batch file: Where is the phantom line break coming from in this code?

I am trying to append a backslash to the end of a string (a folder name), and instead of getting a backslash, I seem to be getting a line break, 13 spaces, then finally the backslash. Where is this coming from?
Here is the code I wrote:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: After the user chooses a folder from a menu, the choice is stored in ChosenFolder
:: A typical value for ChosenFolder would be "3 .\Folder1"
set "ParentFolder=!ChosenFolder:~2!^\"
echo ParentFolder is !ParentFolder!
The output I get is:
ParentFolder is .\Folder1
\
If I just manually set the ChosenFolder variable to "3 .\Folder", this error doesn't happen, so here is the code that ultimately generates the faulty value of ChosenFolder (a slightly modified version of Magoo's code that answered this question, bless his heart!)
SET "choicenames=z0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy"
:: remove variables starting #
FOR /F "delims==" %%e In ('set # 2^>Nul') DO SET "%%e="
FOR /L %%e IN (1,1,%pagewidth%) DO SET "#spaces=!#spaces! "
:: Read dirnames to #nn, count to #entries
FOR /d %%e IN ("%~1\*.") DO SET /a #entries+=1&SET "#!#entries!=%%e"
SET /a #entries+=1&SET "#!#entries!=z Quit."
SET /a #columns=(#entries + pagesize - 1) / pagesize
SET /a #rows=(#entries + #columns - 1)/#columns
SET /a #columnwidth=(pagewidth/#columns) - 3
SET "#choices=z"
FOR /L %%e IN (1,1,%#entries%) DO (
rem column contents - max length + terminal spaces
IF %%e neq %#entries% (
SET "#%%e=!#%%e:~-%#columnwidth%!%#spaces%"
SET "#%%e=!choicenames:~%%e,1! !#%%e:~0,%#columnwidth%!"
SET "#choices=!#choices!!choicenames:~%%e,1!"
)
)
FOR /L %%e IN (1,1,%#rows%) DO (
SET /a cols=%%e + %#rows%
SET /a #line=%%e + (%#rows% * 2^)
SET "cols=!cols! !#line!"
SET "#line=!#%%e!"
FOR %%y IN (!cols!) DO IF DEFINED #%%y SET "#line=!#line! !#%%y!"
ECHO !#line!
)
IF %#entries% gtr 36 (
choice /cs /c %#choices%
) ELSE (
choice /c %#choices%
)
IF ERRORLEVEL 2 (
ECHO ERRORLEVEL is %ERRORLEVEL%
SET /a #choices=%ERRORLEVEL%-1
CALL SET "ChosenFolder=%%#!#choices!%%"
ECHO choice made : !ChosenFolder:~2!
CHOICE /C snc /N /M "(S)elect, (N)avigate or (C)ancel? "
IF errorlevel 3 goto begin
IF errorlevel 2 CALL :SelectFolder !ChosenFolder:~2!
IF errorlevel 1 EXIT /B 0
) ELSE GOTO begin
#? contains the name as, eg : 3 u:\Folder1 . The spaces are added to lay out the menu appropriately, so they would need to be deleted (hence the series of spaces when manipulated and the need to lop off the leading 2 characters).
\ does not need to be escaped if simply appended to the string.
Note however - 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.
FOR /d %%e IN ("%~1\*.") DO SET /a #entries+=1&SET "#!#entries!=%%e"&SET "#originalnames!#entries!=%%e"
To establish a parallel list of names, #originalnames?.
IF ERRORLEVEL 2 (
ECHO ERRORLEVEL is %ERRORLEVEL%
SET /a #choices=%ERRORLEVEL%-1
CALL SET "ChosenFolder=%%#originalnames!#choices!%%"
ECHO choice made : !ChosenFolder!
CHOICE /C snc /N /M "(S)elect, (N)avigate or (C)ancel? "
IF errorlevel 3 goto begin
IF errorlevel 2 CALL :SelectFolder
rem IF errorlevel 1 EXIT /B 0
) ELSE GOTO begin
GOTO :EOF
:selectfolder
ECHO +%chosenfolder%+
FOR /f "delims=" %%e in ("%chosenfolder%") do SET "Parentfolder=%%~dpe"
echo ParentFolder of %chosenfolder% is +%ParentFolder%+
GOTO :eof
(Note + on either end of the names to show absence of spaces.)
This assigns the original name (not the displayed name which may have been truncated) to chosenfolder.
Since chosenfolder is not within (...) (ie. a code block) you can use %chosenfolder% to access its contents. !chosenfolder! will also work, but it implies that the value of chosenfolder may vary because it's within a code block - but within the subroutine :SelectFolder there are no code blocks.
chosenfolder is of the form u:\folder 1. so the for assigns this to %%e and the parser then interprets u:\folder 1 as drive=u:, path=\ and filename=folder 1

Sum in batch script

How can I make the sum of the imputed numbers given by the user from the keyboard ? I tried a for loop but is endless. I cannot figure out a break statement.
::#ECHO OFF
setlocal EnableDelayedExpansion
set /p X=
SET /A suma=0
:Loop
FOR %%G IN (%X%) DO (
IF %%G GTR 0 (
SET /A suma=%suma%+%%G
)
)
SHIFT
GOTO Loop
:completed
echo %suma%
What i want to do is to make the program more user friendyl like echo somthing for the user to know it is supposed to write somthing. I know the correct code for this question, but the numbers are given as parameters in the command (syntax: script.bat parameter1 parameter2 parameter3).
::#ECHO OFF
SET /A suma=0
:Loop
IF "%1"=="" GOTO completed
FOR %%F IN (%1) DO (
IF %1 GTR 0 (
SET /A suma=%suma%+%1
)
)
SHIFT
GOTO Loop
:completed
echo %suma%
Please give me any idea for the breaking statement in the first example or how can I modify the code to take the numbers from the users imput variable and not as paramaters.
Thank you.
SET /A suma=0
set "X=%*"
if not defined X set /p "X=No parameters detected "
if not defined X ECHO No user-input detected&goto completed
FOR %%G IN (%X%) DO (
IF %%G GTR 0 (
SET /A suma=suma+%%G
)
)
:completed
echo %suma%
%* means the command-line tail. If no tail, then ask user for input. If still no data, issue message and show result.
shift moves the command line tail one position, so %2 becomes %1, etc. You haven't provided a test to exit the routine - and the command-line tail is presumably empty, so you are shifting it (which does nothing) and looping.
The for...%%G.. processes the entire list %X%, so there's no point in the shift/loop - the data has already been processed.

Strange behavior inside IF block in a batch file

I have this batch file here and it doesn't run well. It says "( was unexpected at this time". Do you know why?
#ECHO OFF
SET A[0]=HOLA
SET A[1]=HELLO
SET I=0
:LOOP
IF DEFINED A[%I%] (
ECHO %I%
CALL SET B=%%A[%I%]%%
CALL ECHO %%B%%
IF %B% == HOLA (
ECHO ES
) ELSE (
ECHO EN
)
PAUSE
SET /A I+=1
GOTO :LOOP )
My expected output should be:
0
HOLA
ES
1
HELLO
EN
I don't understand why inside IF everything works different. Thanks in advance.
#ECHO OFF
SETLOCAL
SET "A[0]=HOLA"
SET "A[1]=HELLO"
SET /a I=0
:LOOP
IF DEFINED A[%I%] (
ECHO %I%
CALL SET "B=%%A[%I%]%%"
CALL ECHO %%B%%
FOR /f "delims==" %%b IN ('set B') DO IF /i "%%b"=="B" (IF "%%c"=="HOLA" (SET "Same=Y") ELSE SET "Same=")
IF DEFINED Same (
ECHO ES
) ELSE (
ECHO EN
)
rem PAUSE
SET /A I+=1
GOTO LOOP
)
GOTO :EOF
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign a terminal \, Space or " - 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.
Your set syntax sets the value of the variable to the rest of the line.
Hence, the line including the label loop would be assigned to i.
if %B%... substitutes the value of B at the time the block was parsed. AT that time, B was not defined, so nothing is substituted, so the result is if == HOLA (... and cmd does not expect ( at this point and objects.
The complex line I've shown performs a set command on all variables that start b and assigns %%b to the variable name, %%c to its current value. There may be many variables that start B, so we need to select exactly B, and if B's value in %%c is HOLA then set Same, otherwise set Same to nothing, which means that Same will either be assigned, or not assigned respectively.
if defined operates on the current value of the variable, so same can be used safely to do the if processing.
BUT The clean way to do this is by using delayedexpansion.
Stephan's DELAYEDEXPANSION link

switch case with the user given array elements - batch

I need the solution to the following problem in batch file,
The user gives the input with a space in between every next input element.
The inputs need to be stored in an array as array elements.
The input has to be taken as the case name and the particular case need to be executed.
Hence every case with the name of the array element need to be executed
The array size is not predetermined. It varies as the user may give any number of inputs
The algorithm needs to be like this,
User input numbers are 1 2 4 6 which are stored in the array a[i]
a[i] = {1,2,4,6}
for i = 1 to len(a[i])
CALL :CASE_%a[i]% # jump to :CASE_1, :CASE_2, etc.
:CASE_1
Echo “am in case1”
:: go to the for loop
:CASE_2
Echo “am in case2”
:: go to the for loop
:CASE_3
Echo “am in case3”
:: go to the for loop
:CASE_4
Echo “am in case4”
:: go to the for loop
:CASE_5
Echo “am in case5”
:: go to the for loop
:CASE_6
Echo “am in case6”
:: go to the for loop
:CASE_7
Echo “am in case7”
:: go to the for loop
:CASE_8
Echo “am in case8”
:: go to the for loop
End for
Case_1, Case_2, Case_4, Case_6 only need to be executed as the input is 1 2 4 6.
Is this possible in batch file?
I will split the problem in three parts. Putting the data into the array, getting the data from the array and simulating a case instruction, not present in batch.
#echo off
setlocal enableextensions enabledelayedexpansion
set /p "data=Input data:"
Let's assume the user inputs only numeric values (to avoid all the necessary check and look only to the problem) as indicated, space separated. Put them into a "array". No, arrays are not present in batch scripts, but can be simulated giving to variables the proper names.
set "arrayLength=0"
for %%e in (%data%) do (
set /a "arrayLength+=1"
set "a[!arrayLength!]=%%e"
)
This iterates over the input data, and for each element, a counter is incremented and a variable named a[+counter value+] is created. No, it is not an array, just a variable with a name to allow us simulate the array sintax. As the counter (arrayLength) is beeing changed inside the loop, delayed expansion is needed to read this changed value, and sintax for variable read changes from %var% to !var!
Now, data is in the "array". We are going to iterate over the "array", checking its contents.
for /l %%i in (1 1 %arrayLength%) do (
echo element %%i is : !a[%%i]!
)
The sintax to read the content of each of the elements requires delayed expansion too. In this case we are not changing the value inside the block, but the name of the readed variable is being generated dinamically in each iteration. Remember, no arrays, just variables with an adecuated name. And we generate the name using the for variable.
How to test for the values of the "array"?
echo Test IF
for /l %%i in (1 1 %arrayLength%) do (
if "!a[%%i]!"=="1" (
echo Case 1
) else if "!a[%%i]!"=="2" (
echo Case 2
) else if "!a[%%i]!"=="3" (
echo Case 3
) else if "!a[%%i]!"=="4" (
echo Case 4
) else if "!a[%%i]!"=="5" (
echo Case 5
) else if "!a[%%i]!"=="6" (
echo Case 6
) else (
echo NO CASE
)
)
This iterates over the "array" and uses a cascaded IF sintax to check for the allowed values. It's easy to code, fast but not the most comfortable to maintain if there are frequent changes to the cases.
An alternative to the IF cascade is the usage of subroutines. Depending on the value of the "array" element, call one label or other. Just name the labels adequately.
Then you can test if the label/subroutine exists and then call it.
Or you can call it directly without previous check and test for errors in the call that will indicate the label does not exist.
Or a table of routines can be defined.
For a sample, if the following labels are defined
:case[1]
echo Case 1
goto :eof
:case[2]
echo Case 2
goto :eof
:case[3]
echo Case 3
goto :eof
:case[4]
echo Case 4
goto :eof
:case[5]
echo Case 5
goto :eof
:case[6]
echo Case 6
goto :eof
the code to call this subroutines with a previous check of existence in batch file (%~f0 is the current batch file with full path) could be
rem Use labels in batch with previous test of existence
for /l %%i in (1 1 %arrayLength%) do (
findstr /l /c:":case[!a[%%i]!]" "%~f0" > nul 2>nul
if not errorlevel 1 (
call :case[!a[%%i]!]
) else (
echo NO CASE
)
)
This uses findstr to read the current batch file searching for the :case[n] label. On no error, the label has been found and the call is made. But having to read the batch file to test for the label existence is not the fastest operation.
To not check for label existence and directly do the call, this code can be used
rem Use labels in batch without check
for /l %%i in (1 1 %arrayLength%) do (
ver>nul
call :case[!a[%%i]!] 2>nul
if errorlevel 1 echo NO CASE
)
The 'ver>nul` line symply ensures errorlevel is cleared before calling the subroutine. The call is made and errorlevel is checked. But there is no way of knowing if the errorlevel comes from the call instruction or from the inside of the called subroutine.
Other option is to have a table of rutines to call for each of the posible values of the elements in the array. So if a table for each of the allowed elements is created, pointing the the labels of the subroutines
set "func[1]=:case[1]"
set "func[2]=:case[2]"
set "func[3]=:case[3]"
set "func[4]=:case[4]"
set "func[5]=:case[5]"
set "func[6]=:case[6]"
The following code can call the adecuated subroutine, cheching if the function for the value is defined
for /l %%i in (1 1 %arrayLength%) do (
for %%v in (!a[%%i]!) do if defined func[%%v] (
call !func[%%v]!
) else (
echo NO CASE
)
)
The inner for is needed to get a reference to the value inside the "array" to be used to access the name of the function inside the func "array".
And ..... anything better anyone could imagine.
After all this, Which code should be used? What works for you. It all depends of the real problem to solve
The IF cascade is fast and easy but for a problem with frequent changing cases, is harder to maintain.
The findstr is time consuming.
The call without check can be a real nigntmare.
The function table is in the middle. Is not as fast as having all the code in the block as in the IF solution, but is faster than the findstr while checking for subroutine definition. It is not hard to code, but for small problems is innecesary.
For simple cases, IF constructs are all you need. For complex problems, if the code is long and cases needs to be changed, added, ... the function table makes things easier.
So, it can result in
#echo off
setlocal enableextensions enabledelayedexpansion
set /p "data=Input data:"
set "arrayLength=0"
for %%e in (%data%) do (
set /a "arrayLength+=1"
set "a[!arrayLength!]=%%e"
)
rem func[ allowedValue ] = label to call
set "func[1]=:handleCase_1"
set "func[2]=:handleCase_2"
set "func[3]=:handleCase_3"
set "func[4]=:handleCase_4"
set "func[5]=:handleCase_5"
set "func[6]=:handleCase_6"
for /l %%i in (1 1 %arrayLength%) do (
for %%v in (!a[%%i]!) do if defined func[%%v] (
call !func[%%v]!
) else (
echo NO CASE
)
)
for /l %%i in (1 1 %arrayLength%) do (
if "!a[%%i]!"=="1" (
echo Case 1
rem Or, we can
rem call :handleCase_1
) else if "!a[%%i]!"=="2" (
echo Case 2
) else if "!a[%%i]!"=="3" (
echo Case 3
) else if "!a[%%i]!"=="4" (
echo Case 4
) else if "!a[%%i]!"=="5" (
echo Case 5
) else if "!a[%%i]!"=="6" (
echo Case 6
) else (
echo NO CASE
)
)
endlocal
exit /b
:handleCase_1
echo Case 1
goto :eof
:handleCase_2
echo Case 2
goto :eof
:handleCase_3
echo Case 3
goto :eof
:handleCase_4
echo Case 4
goto :eof
:handleCase_5
echo Case 5
goto :eof
:handleCase_6
echo Case 6
goto :eof
Windows batch does not have true support for arrays, but they can be emulated via clever use of variable names. For example, a[1] and a.len are ordinary variable names, the script does not know of any object named a. I tend to use dot notation instead: a.1, a.2, ... a.len. You are free to develop your own style.
#echo off
setlocal enableDelayedExpansion
set a[1]=1
set a[2]=2
set a[3]=4
set a[4]=6
set a.len=4
:: You can also do multiple numerical assignments on one line using SET /A
:: set /a "a[1]=1, a[2]=2, a[3]=4, a[4]=6, a.len=4"
for /l %%i in (1 1 %a.len%) do (
call :CASE_!a[%%i]!
)
:: You must exit the script so you don't fall into the subroutines after the loop finishes
exit /b
:CASE_1
Echo am in case1
exit /b
:CASE_2
Echo am in case2
exit /b
:CASE_3
Echo am in case3
exit /b
:CASE_4
Echo am in case4
exit /b
:CASE_5
Echo am in case5
exit /b
:CASE_6
Echo am in case6
exit /b
:CASE_7
Echo am in case7
exit /b
:CASE_8
Echo am in case8
exit /b
I reviewed your problem and, in my opinion, it does not require the use of an array. I think you are talking about a list. This way, the stated problem may be solved using a list this way:
#echo off
rem The user gives the input with a space in between every next input element.
set /P input=
rem The input has to be taken as the case name and the particular case need to be executed.
rem Hence every case with the name of the array element need to be executed.
for %%i in (%input%) do call :CASE_%%i
goto :EOF
:CASE_1
Echo am in case1
exit /B
:CASE_2
Echo am in case2
exit /B
:CASE_3
Echo am in case3
exit /B
:CASE_4
Echo am in case4
exit /B
:CASE_5
Echo am in case5
exit /B
:CASE_6
Echo am in case6
exit /B
For example:
C:\> test
1 2 4 6
am in case1
am in case2
am in case4
am in case6
Any solution that is forced to use an unnecessary array would be more complicated...

length of each line using batch file

I want to read a CSV file line by line and echo something different if the length of the line is 7999.
I manage to do something as below, which reads each line and checks the number of character for each line, but the issue is that I am getting no value in %result% and echo(%result% prints a blank value. Any idea what am I doing wrong here? Thanks
#echo off
setlocal
for /f "tokens=* delims= " %%a in (REPORTS.csv) do (
set "line=%%a"
call :strlen result line
echo(%result%
if %result% EQU 7999 (
echo %%a
echo(short=%result%
) else (
echo %%a
echo(long=%result%
)
pause
)
:strlen <resultVar> <stringVar>
(
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
Put this section into another subroutine, similar to :strlen
echo(%result%
if %result% EQU 7999 (
echo %%a
echo(short=%result%
) else (
echo %%a
echo(long=%result%
)
Note also that your main routine will continue into your subroutine when finished, so at end-of-file(reports.csv) the batch will execute :strlen one final time and exit through the EXIT
I'd recommend adding a
GOTO :EOF
Immediately before the :strlen label. This is understood by the processor to go to end-of-physiacl-file (the colon is required)
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
You should use DelayedExpansion in if and for loops and take care of the brackets:
#echo off
setlocal enabledelayedexpansion
for /f "tokens=* delims= " %%a in (REPORTS.csv) do (
set "line=%%a"
call :strlen result line
echo.!result!
if !result! EQU 7999 (
echo.%%a
echo.short=!result!
) else (
echo.%%a
echo.long=!result!
)
)
pause
goto:eof
:strlen <resultVar> <stringVar>
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
endlocal &set "%~1=%len%"
exit /b
Your code doesn't ever work in many areas.

Resources