In batch scripting I can do something like
echo off
setlocal EnableDelayedExpansion
set /a jjj = 0
echo Want _1: & set /a jjj+=1 & echo _%jjj%
REM batch output _0 bad
REM CLI output _0 bad
echo Checking: jjj is %jjj%
echo Want _2: & set /a jjj+=1 & echo _!jjj!
REM batch output _2 good
REM CLI output _!jjj! bad
echo Checking: jjj is %jjj%
echo Want _3: & set /a jjj+=1 & call echo _%jjj%
REM batch output _2 bad
REM CLI output _2 bad
echo Checking: jjj is %jjj%
echo Want _4: & set /a jjj+=1 & call echo _!jjj!
REM batch output _4 good
REM CLI output _!jjj! bad
echo Checking: jjj is %jjj%
echo Want _5: & set /a jjj+=1 & call echo _%%jjj%%
REM batch output _5 good
REM CLI output _%4% bad
echo Checking: jjj is %jjj%
echo Want _6: & set /a jjj+=1 & call echo _!!jjj!!
REM batch output _6 good
REM CLI output _!!jjj!! bad
echo Checking: jjj is %jjj%
endlocal
The echo Checking: jjj is %jjj%
statements produce the correct output in every instance (1, 2, 3, 4, 5 and 6) when run as a batch file, and also when run at the command prompt.
Now notice the set
and echo
commands paired together on the same line in each case. These are the problem.
Having set
and echo
on a single line within the batch file seems to mean that they are treated like a block arising within a for
loop or an if
statement, so for a batch file this can be handled with either delayed expansion (using !
instead of %
) or an explicit call
statement (see ss64 and Reddit) ... or even both of those together (albeit redundantly).
However, when run directly in the command prompt (CLI) under Windows 10, I cannot find any way to access the most recent value of the variable if the set
and echo
are on the same line, separated by an ampersand (&
).
Is there a way to do this, or is it a known limitation?
On the CLI, the setlocal
command has no effect, but you can enable delayed expansion by using cmd /V:ON
echo Want _7: & set /a jjj+=1 > NUL & cmd /v:on /c echo _!jjj!
The cmd /v:on
creates a new cmd.exe instance, therefore it can't modify variables of the parent process. It's a bit like the setlocal EnableDelayedExpansion
command.
Mentioned by Aacini in his comment:
You can still use CALL
, but you have to add some carets in the variable name, because you cannot escape a percent sign on the CLI.
Even the carets don't escape anything, but they create an undefined variable name which is not expanded.
echo Want _8: & set /a jjj+=1 > NUL & call echo %^jj^j%
Use the set /a
output itself.
On the CLI, set /a
always outputs the result without newline, to avoid this use redirect to >NUL
.
echo Want _9: & set /a jjj+=1 > NUL & set /a jjj & echo:
set /a var+=1 > NUL & for /F "tokens=1-31" %%= in ("-") do @call echo var is %%=var%%=
This is an absolutely safe variant and cannot be broken by any crafted variable.
It creates 31 for-meta-variables, the first is %%, but the 25th is %= and contains nothing.
The trick is how %%=VAR%%=
expands.
In the first percent expansion phase %%=
just stays as it.
In the FOR meta variable expansion phase the %=
expands to nothing, after this phase the line looks like var is %var%
.
In the end the CALL
can expand the %var%
without problems.
And because every percent sign is followed by an equal sign, and because variable names can't start with an equal sign, it's safe.