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
Related
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.
I'm writing a Windows script in batch. I have a problem with whitespaces in variables. When the user types in a space, the script breaks.
Here's the part of my script:
:package
SET /P packageName="Set package name:"
IF [%packageName%] EQU [] (
ECHO Empty package name.
goto package
) ELSE (
set "packageName=%packageName: =%"
echo %packageName%
pause
)
This schould work:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
:package
SET /P packageName="Set package name:"
IF "%packageName%"=="" (
ECHO Empty package name.
goto package
) ELSE (
set packageName=%packageName: =%
echo !packageName!
pause
)
There are two modifications to your script:
[%packageName%] EQU [] was replaced with "%packageName%"==""
I've added SETLOCAL ENABLEDELAYEDEXPANSION and changes echo %packageName% with echo !packageName!
The second point is because you are changing the value of a variable inside an IF-construction. As the interpreter doesn't know what the new value will be at "compile" time, you have to evaluate the variable at run time. That's why you need SETLOCAL ENABLEDELAYEDEXPANSION and !...! instead of %...%. This forces the expansion at run time.
I suggest to use this code:
#echo off
setlocal EnableDelayedExpansion
:package
rem Predefine variable packageName with a single double quote as value.
rem This value is kept if the user just hits RETURN or ENTER on prompt.
rem The single double quote is removed 2 command lines below if the user
rem does not enter anything or it is overwritten by user entered string.
set "packageName=""
set /P "packageName=Set package name: "
rem Remove double quotes from entered string. This is necessary to
rem avoid a syntax error on next command line with the IF condition.
set "packageName=!packageName:"=!"
if "!packageName!" == "" (
echo Empty package name.
goto package
) else (
set "packageName=%packageName: =%"
echo Package name with spaces: %packageName%
echo Package name without spaces: !packageName!
pause
)
endlocal
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.
if /?
set /?
setlocal /?
Especially the help pages output on execution of if /? should be read carefully and completely as this helps explains delayed expansion as it must be used here on examples.
See also the output of the 2 echo lines in ELSE branch in code above to understand what is the difference between referencing a variable with percent signs or with exclamation marks in blocks defined with ( ... ).
Your script is almost correct except for "variable search/replace" which its position is to be before "IF"
#echo off
:package
set /p packagename="set package name:"
set packagename=%packagename: =%
if [%packagename%] equ [] (
echo empty package name &goto package
) else (echo %packagename%)
How do you use setlocal in a batch file? I am just learning scripting and would like it explained to me in very simple terms.
I have a script that stops and says < was unexpected at this time it may have something to do with not having any setlocal statements in the script.
You make the first line SETLOCAL. This example is from the linked article below:
rem *******Begin Comment**************
rem This program starts the superapp batch program on the network,
rem directs the output to a file, and displays the file
rem in Notepad.
rem *******End Comment**************
#echo off
setlocal
path=g:\programs\superapp;%path%
call superapp>c:\superapp.out
endlocal
start notepad c:\superapp.out
The most frequent use of SETLOCAL is to turn on command extensions and allow delayed expansion of variables:
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
For more info on SETLOCAL see the Command Line Reference at Microsoft TechNet.
Direct link to Setlocal
Suppose this code:
If "%getOption%" equ "yes" (
set /P option=Enter option:
echo Option read: %option%
)
Previous code will NOT work becase %option% value is replaced just one time when the IF command is parsed (before it is executed). You need to "delay" variable value expansion until SET /P command had modified variable value:
setlocal EnableDelayedExpansion
If "%getOption%" equ "yes" (
set /P option=Enter option:
echo Option read: !option!
)
Check this:
set var=Before
set var=After & echo Normal: %var% Delayed: !var!
Guess what the output is...
Try this:
SET PATH=%PATH%;%~dp0;
This will get your local folder your are running the batch from and add it to the current path.
example: if your are running a .bat or .cmd from d:\tools\mybatch.bat
it will add d:\tools to the current path so that it may find additional files on that folder.
FOR /L %%i IN (1,1,100) DO (
choice
echo %ErrorLevel%
)
%ErrorLevel% is always 0 no matter what choice you enter.
You are checking the errorlevel the wrong way.
Variables and commands inside a bracket pair like this...
(
command1
command2
command3
)
...act like they were run on a single line, like this command1 & command2 & command3.
Try this at the command line.
choice & echo %errorlevel%
If you execute the above command more than once, you will see that the previous errorlevel is echoed, not the current one.
Or try this on the command line:
set x=yes
( echo %x%
set x=no
echo %x%
)
Your output will be:
yes
yes
Just as if you'd entered echo %x% & set x=no& echo %x%
I like to think of it as the system doesn't have the time to update the variables. (Though it's more accurate to say that the variables only get updated after the entire line is executed.) This is true with all variables, not just the errorlevel.
To make variables in for loops work normally you need to call an internal label in your batch file (or an external batch file) like this.
#echo off
FOR /L %%i IN (1,1,100) DO call :dostuff %%i
goto :eof
:dostuff
choice /m "Question #%1"
echo %ErrorLevel%
==================================== Solution To Question Below
Alternatively, Microsoft has created a method for accessing the current value of variables inside of a bracket pair, they call it 'Delayed Expansion' because the line of code is interpreted twice.
To activate this mode you use the setlocal command with the enableDelayedExpansion switch, and access the variables with the ! character like this. FYI endlocal turns off the effects.
#echo off
setlocal enableDelayedExpansion
for /L %%i in (1,1,100) do (
choice /m "Question #%%i"
echo !ErrorLevel!
)
endlocal
As you can see my first example is easier to code, but my second example is easier to read. Whichever method you use will depend upon your needs and re-usability.
The setlocal command also has the effect of creating temporary variables that die after the endlocal command. This means you don't need to delete them when your batch file ends, and reverts any variables you changed during execution back to their original values. This is nice because you don't have to worry about 'stepping on' any preexisting variables.
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#, ...)