Why is an environment variable not updated in a loop? - batch-file

I would like my variable res to be updated inside the loop, but it does not update until after the second iteration.
Here is my code:
#echo off
set /a res=10
:loop
if %res% gtr 100 (
goto endLoop
) else (
set /a res=res*10
rem Here the 'res' shouldn't it be 100?
echo the result is : %res%
rem Here the first iteration display 10 not 100.
goto loop
)
:endLoop
pause > nul
Is there an explanation for this?

As an example of usage of delayed expansion here's your modified code:
#Echo Off
SetLocal EnableDelayedExpansion
Set "res=10"
:loop
If %res% Lss 100 (
Set/A res*=10
Echo the result is : !res!
GoTo loop
)
Pause>Nul
There is an alternative without using delayed expansion in this case, but I'd suggest you stick with the former until you are confident enough to understand the order in which things are read and parsed:
#Echo Off
Set "res=10"
:loop
If %res% Lss 100 (
Set/A res*=10
Call Echo the result is : %%res%%
GoTo loop
)
Pause>Nul

Related

an error with set /a in a loop

I have this code:
setlocal enableDelayedExpansion
set count=0
set letter=a,b,c
for %%a in (%letter%) do (
set /a "count+=1"
echo %count%
)
pause
The output is:
0
0
0
I want that the output will be:
1
2
3
I also tried to do it without EnableDelayedExpansion, but I had no luck. What did I do wrong?
you need to
echo !count!
with delayedexpansion
or
call echo %%count%%
%count% will always return the value of count as it stood when the block (parenthesised series of statements) was encountered.

Batch - Decreasing value of a variable in substring is not functioning

I'm trying to reverse "hello" to "olleh". But the output shows "ooooo".
I think !string:~%back%,1! is the problem, because when I use echo to test the value of back is decreasing or not, it works, but it doesn't work in substring, so it always get the last character of the string (-1,1).
#echo off
set string=hello
set temp_string=%string%
set /a string_length=0
:find_length
if defined temp_string (
set temp_string=%temp_string:~1%
set /a string_length+=1
goto :find_length
)
:loop
setlocal enabledelayedexpansion
set /a back=-1
for /l %%a in (1,1,!string_length!) do (
set reverse_string=!string:~%back%,1!!reverse_string!
set /a back-=1
)
echo !reverse_string!
pause >nul
As TripeHound commented, %back% needs to be delayed. What you should do is use the for /L loop value of %%a to in place of %back%. (No sense decrementing a variable manually when one's already being decremented for you as a part of the for /L loop, right?)
for /l %%a in (%string_length%,-1,0) do (
call set "reverse_string=!reverse_string!!string:~%%a,1!"
)
goto loops are not very efficient. If you've got a long string you're going to reverse, there'll be a noticeable pause while you count its length if you goto :label for each character. The fastest way I've found to get the length of a string is based on jeb's answer here:
:length <return_var> <string>
setlocal enabledelayedexpansion
if "%~2"=="" (set ret=0) else set ret=1
set "tmpstr=%~2"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!tmpstr:~%%I,1!"=="" (
set /a ret += %%I
set "tmpstr=!tmpstr:~%%I!"
)
)
endlocal & set "%~1=%ret%"
goto :EOF
Put it all together like this:
#echo off
setlocal
set "string=%*"
call :length string_length "%string%"
setlocal enabledelayedexpansion
for /l %%a in (%string_length%,-1,0) do (
set "reverse_string=!reverse_string!!string:~%%a,1!"
)
echo(!reverse_string!
pause >nul
exit /b 0
:length <return_var> <string>
setlocal enabledelayedexpansion
if "%~2"=="" (set ret=0) else set ret=1
set "tmpstr=%~2"
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!tmpstr:~%%I,1!"=="" (
set /a ret += %%I
set "tmpstr=!tmpstr:~%%I!"
)
)
endlocal & set "%~1=%ret%"
goto :EOF
Example output:
command: test.bat The quick brown fox
result: xof nworb kciuq ehT
The problem is that %back% is being used without delayed expansion, so will always have the value -1. Replacing the end of your code with:
set /a back=-1
set /a count=1
:repeat
if %count% gtr %string_length% goto :report
set reverse_string=!string:~%back%,1!!reverse_string!
set /a back-=1
set /a count+=1
goto :repeat
:report
echo !reverse_string!
Will do the trick.
You cannot just use !back! because you need the contrast of !...! and %...% to have a variable index, so you'll have to go back to an old-fashioned :loop construct so %back% gets updated each time around.
As described at this post:
"To get the value of a substring when the index change inside FOR/IF enclose the substring in double percents and precede the command with call. For example:
for /l %%a in (1,1,!string_length!) do (
call set reverse_string=%%string:~!back!,1%%!reverse_string!
set /a back-=1
)
Another way to achieve previous process is using an additional FOR command to change the delayed expansion of the index by an equivalent replaceable parameter, and then use the delayed expansion for the substring. This method run faster than previous CALL:
for /l %%a in (1,1,!string_length!) do (
for %%b in (!back!) do (
set reverse_string=!string:~%%b,1!!reverse_string!
)
set /a back-=1
)
"
However, it is not efficient to first loop thru the string just to count the characters, and then loop again to reverse they. I think the method below should be the fastest one:
#echo off
setlocal EnableDelayedExpansion
set maxLength=80
set string=hello
set "reverse="
for /L %%i in (1,1,%maxLength%) do (
set "reverse=!string:~0,1!!reverse!"
set "string=!string:~1!"
if not defined string goto break
)
:break
echo %reverse%
Here's another algorithm:
#echo off
call :reverse "The quick brown fox"
echo %output%
pause & exit
:reverse
setlocal enableDelayedExpansion
set string=%~1
set index=0
:loopchar
set char=!string:~%index%,1!
if "!char!"=="" endlocal & set output=%output% & exit /b
set output=!char!!output!
set /a index+=1
goto loopchar
"Tricks":
using %index% inside ! to expand its current value instead of call with %% (#Aacini)
export of !output! from the local scope by reassigning it on the same line as endlocal.

Number isn't added to variable in if statement

Why doesn't this batch script add the value 5 to the variable %counter%?
The script always echo the value 2 with which the variable was initialized.
Outside a if statement the counter works just fine.
:start
set /a counter=2
set /p message=Message:
set "spam=%message%"
echo %spam%
if "%message%"=="%spam%" (
set /a counter=%counter% + 5
echo %counter%
)
pause
goto
start
You need to use delayedexpansion. %counter% is being evaluated outside of the If statement but not inside of it.
#echo off
setlocal enabledelayedexpansion
:start
set /a counter=2
set /p message=Message:
set "spam=%message%" echo %spam%
if "%message%"=="%spam%" ( set /a counter=%counter% + 5
echo !counter! )
pause
goto start

is there any method to do same function as for loop without actually using the forloop?

Sample for loop is given below. How to achieve same function without using for loop.
for /l %x in (1, 1, 100) do echo %x
:loop
set /a count+=1
echo %count%
if %count% neq 100 goto:loop
set /a a=1
:label
echo %a%
set /a a+=1
if %a%' EQU 100' (goto :outOfLoop)
goto :label
:outOfLoop
echo done!

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