I'm writing a simple .bat file and I've run into some weird behavior. There are a couple places where I have to do a simple if/else, but the code inside the blocks don't seem to be working correctly.
Here's a simple case that demonstrates the error:
@echo off
set MODE=FOOBAR
if "%~1"=="" (
set MODE=all
echo mode: %MODE%
) else (
set MODE=%~1
echo mode: %MODE%
)
echo mode: %MODE%
The output I'm getting is:
C:\>test.bat test
mode: FOOBAR
mode: test
Why is the echo inside the code block not getting the new value of the variable? In the actual code I'm writing I need to build a few variables and reference them within the scope of the if/else. I could switch this to use labels and gotos instead of an if/else, but that doesn't seem nearly as clean.
What causes this behavior? Is there some kind of limit on variables within code blocks?
You are running into the problem of cmd's static variable expansion. The MODE variable is only evaluated once. You can see this if you omit the @echo off line.
From the set /? documentation:
Finally, support for delayed environment variable expansion has
been added. This support is always
disabled by default, but may be
enabled/disabled via the /V command
line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around
the limitations of the current
expansion which happens when a line of
text is read, not when it is executed.
The following example demonstrates the
problem with immediate variable
expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" @echo If you see this, it worked
)
would never display the message, since
the %VAR% in BOTH IF statements is
substituted when the first IF
statement is read, since it logically
includes the body of the IF, which is
a compound statement. So the IF
inside the compound statement is
really comparing "before" with "after"
which will never be equal. Similarly,
the following example will not work as
expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of
files in the current directory, but
instead will just set the LIST
variable to the last file found.
Again, this is because the %LIST% is
expanded just once when the FOR
statement is read, and at that time
the LIST variable is empty. So the
actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the
last file found.
Delayed environment variable expansion
allows you to use a different
character (the exclamation mark) to
expand environment variables at
execution time. If delayed variable
expansion is enabled, the above
examples could be written as follows
to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" @echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
setlocal EnableDelayedExpansion
will enable the /v flag
Looks like the read and write use different scoping rules.
If you eliminate this line
set MODE=FOOBAR
it will work as expected. So you'll probably need to have a complex series if if/elses to get the variables populated as you'd like.