I have following code in a batch file:
set \"start=2\"
FOR /L %%a in (1,1,3) DO (
FOR /L %%b in (%start%,1,3) DO (
echo %start% Before Call
Following is output up to first execution of pause
on running the batch file as posted in question from within a command prompt window without @echo off
as usually used at top of a batch file to debug this batch file:
set "start=2"
FOR /L %a in (1 1 3) DO (FOR /L %b in (2 1 3) DO (
echo 2 Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
) )
(FOR /L %b in (2 1 3) DO (
echo 2 Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
) )
(
echo 2 Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
)
2 Before Call
echo 2 Before Set
2 Before Set
set "start=1"
echo 1 After Set
1 After Set
pause
Windows command interpreted cmd.exe
interpreting the batch file and executing the command lines outputs with no echo off
always the command line before execution after preprocessing/parsing it as described at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
It can be seen that the command block starting with (
and ending with matching )
is completely preprocessed by Windows command interpreter before executing the most outer FOR command the first time. Both occurrences of %start%
have been replaced during preprocessing the entire command block by the current value 2
of referenced environment variable. So the entire command block used next several times by FOR does not contain anymore an environment variable reference. Only the loop variable references remain in command block.
The help of command SET output on running set /?
in a command prompt window describes this problematic of all environment variable references done with %variable%
being evaluated already during preprocessing. And help of SET describes also what is the solution for an IF condition and a FOR loop on which current value of environment variable changes within the command block or during loop execution: the usage of delayed expansion.
So let's use the commands SETLOCAL and ENDLOCAL as explained in this answer to enable also delayed environment variable expansion in addition to command extensions which are enabled by default and needed here too.
setlocal EnableExtensions EnableDelayedExpansion
set "start=2"
FOR /L %%a in (1,1,3) DO (
FOR /L %%b in (!start!,1,3) DO (
echo !start! Before Call
call :changestring
FOR /L %%c in (1,1,3) DO (
REM Nothing
)
)
)
endlocal
pause
goto :EOF
:changestring
echo %start% Before Set
set "start=1"
echo %start% After Set
pause
goto :EOF
The environment variable start
is referenced now twice with !start!
in second FOR command line and its command block which means with delayed expansion as being enabled in first line of modified batch file.
The output by Windows command interpreter up to first pause
on running this modified batch file is now:
setlocal EnableExtensions EnableDelayedExpansion
set "start=2"
FOR /L %a in (1 1 3) DO (FOR /L %b in (!start! 1 3) DO (
echo !start! Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
) )
(FOR /L %b in (!start! 1 3) DO (
echo !start! Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
) )
(
echo !start! Before Call
call :changestring
FOR /L %c in (1 1 3) DO (REM Nothing )
)
2 Before Call
echo 2 Before Set
2 Before Set
set "start=1"
echo 1 After Set
1 After Set
pause
Both !start!
remain in command block unmodified after preprocessing. So it really references now always the current value of environment variable start
being modified during execution of the loops.
There is no need to replace %start%
in the subroutine by !start!
as it can be seen because both echo
command lines are not within a command block and so each command line is preprocessed with replacing %start%
by current value of environment variable just before immediately executing the command ECHO.
I hope the difference between immediate and delayed expansion of environment variables during preprocessing/parsing a single command line or an entire command block is clear now.
By the way: The name start
for the variable is not good. start
can be used as environment variable name, but there is also the command START which could be used in same batch file making it more difficult to distinguish between environment variable start
and command start
. Better would be as variable name for example FirstNumber
.
An optimized code on which FirstNumber
is used instead of start
and on which the number is decremented using an arithmetic expression making it possible to reference an environment variable without usage of %
or !
and nevertheless using current value of environment variable during evaluation of the arithmetic expression. The first number starting with 2
should be never decremented to a value lower than 1 being the reason for binary OR it with 1
.
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FirstNumber=2"
FOR /L %%a in (1,1,3) DO (
FOR /L %%b in (!FirstNumber!,1,3) DO (
echo !FirstNumber! before decrement
set /A "FirstNumber=FirstNumber - 1 | 1"
FOR /L %%c in (1,1,3) DO REM Nothing
)
)
endlocal
The output on running this batch file with @echo off
at top is:
2 before decrement
1 before decrement
1 before decrement
1 before decrement
1 before decrement
1 before decrement
1 before decrement
1 before decrement
Of course set /A "FirstNumber=FirstNumber - 1 | 1"
could be here simply a standard string to environment variable assignment, i.e. set "FirstNumber=1"
. But I wanted to demonstrated that environment variables can be referenced by just their names within an arithmetic expression to make use of current values of the environment variables during evaluation of the expression. This behavior is also described by help of command SET.
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 /?
echo /?
endlocal /?
for /?
goto /?
pause /?
rem /?
set /?
setlocal /?
Conclusion: Reading carefully the help or documentation of a command, application, or function to use is always good practice for a programmer or script writer to avoid problems on writing coding.