ECHO strange behavior when inside IF block - batch-file

I have a Windows Batch script named test.bat as follows:
#ECHO OFF
SETLOCAL
SET name=Dan
SET greeting=Hi %name%, how are you?
ECHO %greeting%
When executed I get the following output:
Hi Dan, how are you?
This is what I would expected. I changed the script to the following:
#ECHO OFF
SETLOCAL
IF EXIST test.bat (
SET name=Dan
SET greeting=Hi %name%, how are you
ECHO %greeting%
) ELSE (
ECHO Nofile
)
I would expect to get the same output. Instead I get the following:
ECHO is off.
Please can someone help me understand why.

you need delayed expansion:
#ECHO OFF
SETLOCAL enableDelayedExpansion
IF EXIST test.bat (
SET name=Dan
SET greeting=Hi !name!, how are you
ECHO !greeting!
) ELSE (
ECHO Nofile
)
The batch files have two phases of reading the script - execution and parsing. During parsing phase all variables enclosed with % are substituted and during execution phase the commands are executed. With the delayed expansion turned on the variables enclosed with ! will be expanded during execution phase (i.e. later)
Brackets on the other side (as well as &) puts the commands in a block taken as one single command so all variables with % are substituted during the first phase and when you are setting variables you'll need delayed expansion.

Related

Batch file not loading into variable [duplicate]

Can someone give me an example of where a batch script would act differently with or without delayed expansion? Are there any situations where you would NOT want to use delayed expansion? Thanks.
Look at the following examples...
Example 1: The following code DOESN'T use delayed expansion, so the variables in the for loop are expanded only one time. This means that %Count% will always expand to 0 in each iteration of the loop, no matter what we do to it with the set command:
#echo off
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=%COUNT% + 1
echo Count = %COUNT%
)
pause
So this script will output:
Count = 0
Count = 0
Count = 0
Count = 0
This is not how this loop is supposed to work.
Example 2: On the other hand, if we use delayed expansion, we have the following script, which will run as expected.
setlocal ENABLEDELAYEDEXPANSION
set COUNT=0
for %%v in (1 2 3 4) do (
set /A COUNT=!COUNT! + 1
echo Count = !COUNT!
)
pause
and, as expected, it will output:
Count = 1
Count = 2
Count = 3
Count = 4
When you use the ENABLEDELAYEDEXPANSION, and expand a variable using ! instead of %, the variable is re-expanded each time, and everything works as it's supposed to.
I wanted to add a great example on how "EnableDelayedExpansion" (EDE) can be useful outside of the ubiquitous FOR loop examples.
Here is a line of earthquake data that I wish to parse (I call it it 1line.txt)
ak_11574812 2015.04.29.193822 62.9525 -148.8849 1.0 9.5 1 49km S of Cantwell, Alaska
The problem I ran into was that last segment of this line does not always start at the same column number. So I needed to create a flexible SET command that will accurately pluck out the last segment of this line.
ECHO OFF
setlocal enableDelayedExpansion
set where=72
set /p line=<1line.txt
set locate=!line:~%where%,28!
echo %locate%
EDE allows me to place a variable (where) inside another variable (line). EDE will translate the variable bracketed by % first, then process the variable bracketed by ! and (in this case) push out the results into the "locate" variable.
Max's answer gives an example of where a batch script would act differently with or without delayed expansion.
For the sake of completeness, let's answer another part of the question and show a situation where you would NOT want to use delayed expansion when your data contain an exclamation mark ! (and show two ways of processing such data):
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_auxFile=%temp%\%~n0.txt"
rem create multiline sample file
>"%_auxFile%" ( for /L %%G in (1,1,3) do echo line %%G is 100%% valid! Sure! Hurrah!)
rem create one-line sample file
>"%_auxFile%" echo this line is 100%% valid! Sure! Hurrah!
echo(
echo --- file content
type "%_auxFile%"
echo(
SETLOCAL EnableDelayedExpansion
echo --- enabled delayed expansion chokes down unescaped exclamation marks^^^! "^!"
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%~G"
echo loop var=%%~G
echo _auxLine=!_auxLine!
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- toggled delayed expansion works although might be laborious!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
SETLOCAL EnableDelayedExpansion
echo _auxLine=!_auxLine!
ENDLOCAL
)
ENDLOCAL
echo(
SETLOCAL DisableDelayedExpansion
echo --- keep delayed expansion DISABLED: use CALL command!
for /F "usebackq delims=" %%G in ("%_auxFile%") do (
set "_auxLine=%%G"
echo loop var=%%G
call :ProcessVar
)
ENDLOCAL
rem delete the sample file
del "%_auxFile%"
ENDLOCAL
goto :eof
:ProcessVar
echo _auxLine=%_auxLine%
echo WARNING: neither !_auxLine! nor %%G loop variable is available here!
goto :eof
Note that above script shows proper ways of escaping
% percent sign by %% doubling it (delayed expansion does not matter), and
! exclamation mark if delayed expansion is enabled:
"^!" if enclosed in a pair of double quotes, then use the cmd and batch-script general escape character ^ caret;
^^^! otherwise, use three ^ carets.
Output:
==> D:\bat\SO\10558316.bat
--- file content
this line is 100% valid! Sure! Hurrah!
--- enabled delayed expansion chokes down unescaped exclamation marks! "!"
loop var=this line is 100% valid Hurrah
_auxLine=this line is 100% valid Hurrah
--- toggled delayed expansion works although might be laborious!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
--- keep delayed expansion DISABLED: use CALL command!
loop var=this line is 100% valid! Sure! Hurrah!
_auxLine=this line is 100% valid! Sure! Hurrah!
WARNING: !_auxLine! as well as %G loop variables are not available here!
==>
As pointed in the answer the main usage of the delayed expansion is the setting and accessing variables in brackets context.
Though it can be useful in another situations too.
Parametrizing substring and string substitution:
#echo off
setlocal enableDelayedExpansion
set "string=test string value"
set start=5
set get_next=6
echo #!string:~%start%,%get_next%!#
set "search_for=string"
set "replace_with=text"
echo #!string:%search_for%=%replace_with%!#
the output will be:
#string#
#test text value#
though this can be achieved with additional call this way is more performant
Using shift command within brackets parameterized argument access
#echo off
echo first attempt:
(
echo "%~1"
shift
echo "%~1"
)
::now the shift command will take effect
setlocal enableDelayedExpansion
echo second attempt:
(
set /a argument=1
call echo %%!argument!
shift
call echo %%!argument!
)
the output will be:
first attempt:
"first argument"
"first argument"
second attempt:
"second argument"
"third argument"
As you can see parameterized argument access can be done only with delayed expansion.
Using for tokens (or function arguments) for parameterization
One more approach for mixing !s and %s this could be useful for nested loops:
#echo off
setlocal enabledelayedexpansion
set begin=2
set end=2
set string=12345
for /f "tokens=1,2" %%A in ("!begin! !end!") do set "string2=!string:~%%A,%%B!"
echo !string2!
endlocal
as you can see now the for command tokens are used as parameters.
Several answers here answer the "How to use delayed expansion?" question or what happen if you don't use delayed expansion. However, the second question is "Are there any situations where you would NOT want to use delayed expansion?" and a couple answers take this question as "how to avoid the problems caused by using delayed expansion?"
My answer answers the question as I understand it: "In which situations is better to NOT use delayed expansion (instead of use it)?"
If you want to exchange the contents of two variables, the simplest way to perform this is using the %standard% variable expansion:
set "var1=%var2%" & set "var2=%var1%"
The way that the %standard% expansion works makes possible to achieve this replacemenmt without using any auxiliary variable. As far as I know, the Batch-file "programming language" is the only one that allows to perform this exchange in this way, that is, making good use of a language "feature" (not via a specialized "exchange" instruction/statement).

Batch - error when loading from a file

I have been having problems when loading from a file, I cant seem to find the problem since it is working everywhere else in my code.
Anyways here is the code
set /p viewsave=
if exist save%viewsave%.txt (
set /p level=<save%viewsave%.txt
echo save%viewsave%
echo you are on level %level%
pause
it can find save%viewsave% and I know that it knows that level = 0 because when using echo it says
set /p level= 0<save1.txt
but it does not echo it when I echo
you are on level %level%
it just does not display anything
here is an example of where it somehow did work
set /p saves=<savesnr.txt
echo you have %saves% saves
You need to use enabledelayedexpansion and replace:
echo you are on level %level%
With:
echo you are on level !level!
EnableDelayedExpansion
Delayed Expansion will cause variables within a batch file to be
expanded at execution time rather than at parse time, this option is
turned on with the SETLOCAL EnableDelayedExpansion command.
When delayed expansion is in effect variables can be referenced using
!variable_name! (in addition to the normal %variable_name% )
When a batch file is executed, the command processor (CMD.exe) will
parse complete lines and complete compound commands. Variables are
replaced by their values just once, BEFORE the commands of the line
are executed.
Source enabledelayedexpansion
Further Reading
An A-Z Index of the Windows CMD command line
A categorized list of Windows CMD commands
enabledelayedexpansion - Delayed Expansion will cause variables to be expanded at execution time rather than at parse time.

error with nested if condition on a batch script that sets a variable that disappears when removing it

i have a batch script like this:
#echo off
setlocal
some commands to set variable %equal%
if %equal%==no (
some commands to set variable %equall% (including a call to a separate batch file)
if %equall%==no (
other commands (including a call to a separate batch file)
)
) else (
echo nothing new
)
endlocal
exit /b
But i getting this error (translated from spanish, so it can be innacurate): "(" was not expected at this moment on the first if sentence.
However, if i remove the inner if condition, it runs fine....
What am i missing?
you've got a delayed expansion problem. The variable equall gets defined inside a code block, so %equall% is still not defined. That makes your line
if %equall%==no (
to be parsed as
if ==no (
which leads to the mentioned error "(" is unexpected at this time"
To correct it, enable delayed expansion with the setlocal enabledelayedexpansion command and use delayed expansion with ! syntax instead of % syntax:
setlocal enabledelayedexpansion
if 1==1 (
set "var=hello"
echo !var!
)
Also you should get used to use a better syntax with if (quoting both sides of the comparison):
if "%var%" == "value" ...
(or with delayed expansion: if "!var!% == "value") Even if the variable is empty, this gets parsed as:
if "" == "value" ...
which is perfectly valid syntax.
I used a workaround, that is to send everything inside the outer if to a label:
#echo off
setlocal
some commands to set variable %equal%
if %equal%==no (
goto :label
) else (
echo nothing new
)
endlocal
exit /b
:label
some commands to set variable %equall% (including a call to a separate batch file)
if %equall%==no (
other commands (including a call to a separate batch file)
)

Unexpected handling of "variables" in cmd.exe batch file

I have a xyz.bat file with this content:
#set val=one
#if "%val%" equ "one" (
#set val=yes
echo val after setting it: %val%
) else (
#set val=no
)
#echo %val%
Running it in cmd.exe prints
val after setting it: one
yes
but I expected
val after setting it: yes
yes
Why is this? And is there a way to make it behave as I think it should?
Everything between ( and ) is parsed as a single line (joining commands with &) – and the shell expands %var% variables at parse time
The output of set /? explains this further.
In recent versions:
#echo off
setlocal enabledelayedexpansion
set var=one
if a equ a (
set var=two
echo immediate expansion: %var%
echo delayed expansion: !var!
)
echo after: %var%
The Old New Thing: Environment variable expansion occurs when the command is read
...but that way lies madness. For example, if you want to echo a ! in delayed expansion mode, you have to escape it twice:
echo hi^^!
(This gets parsed twice, reduced to echo hi^! the first time.) And don't even get me started on using ^ inside variables.
The Old New Thing: What this batch file needs is more escape characters
Consider a less kludgy language (Perl, Python, PowerShell, PHP, JScript, C#, ...)

Batch file fails to set environment variable within conditional statement

Why does the following Windows Batch File output Foo followedby Bar, rather than Baz?
#echo off
setlocal
set _=Foo
echo %_%
set _=Bar
if 1==1 (
set _=Baz
echo %_%
)
The output on my system (Microsoft Windows XP [Version 5.1.2600]) is:
Foo
Bar
If I remove the conditional statement, the expected output of Foo and Baz is observed.
What's happening is that variable substitution is done when a line is read. What you're failing to take into account is the fact that:
if 1==1 (
set _=Baz
echo %_%
)
is one "line", despite what you may think. The expansion of "%_%" is done before the set statement.
What you need is delayed expansion. Just about every single one of my command scripts starts with "setlocal enableextensions enabledelayedexpansion" so as to use the full power of cmd.exe.
So my version of the script would be:
#echo off
setlocal enableextensions enabledelayedexpansion
set _=Foo
echo !_!
set _=Bar
if 1==1 (
set _=Baz
echo !_!
)
endlocal
This generates the correct "Foo", "Baz" rather than "Foo", "Bar".
The answer to this is the same as the answer to:Weird scope issue in batch file. See there for more details. Basically variable expansion is done at line read time, not at execution time.
try this
#echo off
setlocal
set _=Foo
echo %_%
set _=Bar
if "1" NEQ "2" goto end
set _=Baz
echo %_%
:end

Resources