The code is,
set VAR=before
if "%VAR%" == "before" (
set VAR=after;
echo %VAR%
)
What will the preceding Windows .bat file code segment display? Why? (i.e. why doesn't it behave as you might first think)?
Obviously, you'd think the output would be "after", given that we reset the env variable inside the loop.
But the output will actually be "before". The reason is that variable substitution is done in .bat files by the interpreter when a command is read, rather than when it's executed. So, for the compound statement, the variables in the body are evaluated when the if statement is first encountered.
You can make this work by using delayed environment variable expansion (need to enable it). If it's enabled, you can then do:
set VAR=before
if "%VAR%" == "before" (
set VAR=after;
echo !VAR!
)
You can enable delayed environment variable expansion using the /v option when starting cmd.exe.
[Backstory--many of us still use legacy .bat files to drive things like make procedures, etc. Obviously there are better scripting tools, but not always an option to use them. I ran into this issue a while back and recently found two other people who had pulled their hair out over the same thing. So it's useful to understand how the interpreter does variable substitution].
The substitution for %VAR% occurs before the execution of the command. Even though there are several commands spread over several lines, the grouping of them in parens (...) causes the cmd.exe parser to read the whole thing in as a single command. So what gets executed looks like the following to the interpreter.
set VAR=before
if "before" == "before" (
set VAR=after;
echo before
)
This is one of the many things that make batch file processing rather painful hen trying to do anything more than simple stuff.
Related
Is there someone to explain exactly what is delayed environment variable expansion in simple terms or point to a post that can understand. Here is my questions
What can't I achieve without this
In which practical situations uses this
Any alternative
%Var% is expanded when a line is read. As lines can have multiple commands, !var! is expanded when used. In MS-DOS !var! (accessed in script by %!var!%) is a legal variable name so you have to turn on a special mode to access. This is so MS-DOS batch files can run in CMD without editing.
I need to call a batch file from inside CYGWIN however one of it's parameters is a path-like string containing semicolons. Normally in windows command line one could enclose that parameter in quotes (which would need to be trimmed later on). However this approach doesn't wok in cygwin
Example batch (echoes first 3 parameters)
echo %1
echo %2
echo %3
Windows cmd call
file.bat "a;b" c
Ouput
"a;b"
c
empty
Cygwin call
./file.bat "a;b" c
Output
a
b
c
Including space anywhere inside quotes will ensure that parameter with semicolon or comma is passed correctly. Although I have to admit that I do not understand this behavior whatsoever, it seems to be working flawlessly.
./file.bat "a;b " c
Output
"a;b"
c
As #jeb mentioned in his comment, enclosing quotes can be trimmed by accessing parameter variable like this
%~1
Recent battles with quoting led me to another technique.
Create a temporary batch file and pass it to cmd. (I used "filex.bat" in this example).
echo 'call file.bat "a;b" c' > filex.bat ; cmd /c filex.bat ; rm filex.bat
I have a batch file that can be run locally or on the build server to do some kind of interesting job. On the local machine, I want #ECHO OFF so that your console isn't full of debug strings. On the build server, I'd rather have #ECHO ON so that failures can be investigated.
The build server context can be determined if a special environment variable exists (TEAMCITY_PROJECT_NAME). So, I thought I could do something like
IF DEFINED TEAMCITY_PROJECT_NAME #ECHO ON ELSE #ECHO OFF
But, that's not working, I get the IF statement echoed along with everything else... any thoughts?
The command is not actually named #echo. The # is an optional flag valid at the beginning of a statement to indicate that echo is suppressed for that statement. So using the # on each reference to the echo command will hide the echo command itself, but not the if command that precedes it.
But the issue that is actually causing your command to fail to operated as expected is that the first echo command will slurp up the rest of the tokens on the line as its arguments, so the else echo off part will not be interpreted by CMD. Since CMD.EXE echoes by default it prints the if command, and then either executes a single echo command or nothing. Since the # does have meaning at the start of the statement that is the body of the if, neither echo command would be printed.
In general, the solution to that is to use parenthesis to delimit the command boundaries. However, since echo is on by default and you really only want to suppress it if TEAMCITY_PROJECT is not define, we can say that directly.
#IF NOT DEFINED TEAMCITY_PROJECT_NAME ECHO OFF
I've left a single # at the beginning to suppress echo of this line, and as a result this line will not appear in your server's logs.
Related note
The echo state also applies to an interactive session. Typing echo off at an interactive CMD session prompt will cause it to stop showing the prompt. Which is a tad disconcerting, if not expected. Typing echo on will restore normal behavior.
More on Parsing
(I've edited the earlier section for clarity, and added this section to attempt to document what is really happening.)
The CMD.EXE program that interprets .BAT and .CMD scripts, and provides the interactive command prompt in modern Windows is surprisingly well documented while effectively being undocumented. My attempts to search for an official document explaining that the at-sign has this effect and exactly where it can be used have been largely unsuccessful.
It is clear from experimentation that the at-sign is parsed and interpreted if it appears as the first non whitespace character of a command. I've tried to express that by using the phrase "beginning of a statement" above.
As far as I can tell there is no formal grammar published for CMD's language. But we do know from the documentation for CDM itself (type cmd /? to a prompt) as well as if /? and the built-in help text for other "interesting" built-in commands that there are rules that are followed when CMD parses its source text.
The start of a command appears to be at the beginning of a line, or after the conditional of an if, after the else, or after an open parenthesis (. Once the at-sign has been recognized, it applies to (most) of the balance of that command.
Try the following batch file yourself, and play with moving the at signs around and you'll quickly get the sense that the rules are hard to state precisely:
rem this remark will echo
#rem this one will not
# rem neither will this
#rem nor this one
#rem the if command (and else) will echo, but not either echo command
if exist q17241089.bat ( # echo saw q17241089.bat ) else # echo foo
if not exist q17241089.bat ( # echo no q17241089.bat ) else # echo bar
# rem none of the following command is echoed
# if exist q17241089.bat ( # echo saw q17241089.bat ) else # echo spam
When run on my Win 7 Pro I see:
C...>
C...>q17241089.bat
C...>rem this remark will echo
C...>if exist q17241089.bat () else
saw q17241089.bat
C...>if not exist q17241089.bat () else
bar
saw q17241089.bat
C...>
As with most of the fine details of BAT files, there is a mystery underneath any surface you scratch.
set parentheses (see docu...):
IF DEFINED TEAMCITY_PROJECT_NAME (#ECHO ON) ELSE #ECHO OFF
Well, it seems that ECHO is on by default, so you'll get the IF statement output anyway you test that conditional. You should start with
#ECHO OFF
And add your conditional as another statement to evaluate
#ECHO OFF && IF DEFINED TEAMCITY_PROJECT_NAME #ECHO ON
As noted in earlier responses, ECHO is on by default. An alternative way of achieving the same behavior is:
#if "%TEAMCITY_PROJECT_NAME%" == "" #echo off
I'm trying to use Jenkins (Global) environment variables in my xcopy script.
${WORKSPACE} doesn't work
"${WORKSPACE}" doesn't work
'${WORKSPACE}' doesn't work
I know nothing about Jenkins, but it looks like you are trying to access environment variables using some form of unix syntax - that won't work.
If the name of the variable is WORKSPACE, then the value is expanded in Windows batch using
%WORKSPACE%. That form of expansion is performed at parse time. For example, this will print to screen the value of WORKSPACE
echo %WORKSPACE%
If you need the value at execution time, then you need to use delayed expansion !WORKSPACE!. Delayed expansion is not normally enabled by default. Use SETLOCAL EnableDelayedExpansion to enable it. Delayed expansion is often needed because blocks of code within parentheses and/or multiple commands concatenated by &, &&, or || are parsed all at once, so a value assigned within the block cannot be read later within the same block unless you use delayed expansion.
setlocal enableDelayedExpansion
set WORKSPACE=BEFORE
(
set WORKSPACE=AFTER
echo Normal Expansion = %WORKSPACE%
echo Delayed Expansion = !WORKSPACE!
)
The output of the above is
Normal Expansion = BEFORE
Delayed Expansion = AFTER
Use HELP SET or SET /? from the command line to get more information about Windows environment variables and the various expansion options. For example, it explains how to do search/replace and substring operations.
In windows you should use %WORKSPACE%.
I should this On Windows, environment variable expansion is %BUILD_NUMBER%
In a Windows batch file, when you do the following:
set myvar="c:\my music & videos"
the variable myvar is stored with the quotes included. Honestly I find that very stupid. The quotes are just to tell where the string begins and ends, not to be stored as part of the value itself.
How can I prevent this from happening?
Thanks.
set "myvar=c:\my music & videos"
Notice the quotes start before myvar. It's actually that simple.
Side note: myvar can't be echoed afterwards unless it's wrapped in quotes because & will be read as a command separator, but it'll still work as a path.
http://ss64.com/nt/set.html under "Variable names can include Spaces"
This is the correct way to do it:
set "myvar=c:\my music & videos"
The quotes will not be included in the variable value.
It depends on how you want to use the variable. If you just want to use the value of the variable without the quotes you can use either delayed expansion and string substitution, or the for command:
#echo OFF
SETLOCAL enabledelayedexpansion
set myvar="C:\my music & videos"
As andynormancx states, the quotes are needed since the string contains the &. Or you can escape it with the ^, but I think the quotes are a little cleaner.
If you use delayed expansion with string substitution, you get the value of the variable without the quotes:
#echo !myvar:"=!
>>> C:\my music & videos
You can also use the for command:
for /f "tokens=* delims=" %%P in (%myvar%) do (
#echo %%P
)
>>> C:\my music & videos
However, if you want to use the variable in a command, you must use the quoted value or enclose the value of the variable in quotes:
Using string substitution and delayed expansion to use value of the variable without quotes, but use the variable in a command:
#echo OFF
SETLOCAL enabledelayedexpansion
set myvar="C:\my music & videos"
md %myvar%
#echo !myvar:"=! created.
Using the for command to use the value of the variable without quotes, but you'll have to surround the variable with quotes when using it in commands:
#echo OFF
set myvar="C:\my music & videos"
for /f "tokens=* delims=" %%P in (%myvar%) do (
md "%%P"
#echo %%P created.
)
Long story short, there's really no clean way to use a path or filename that contains embedded spaces and/or &s in a batch file.
Use jscript.
Many moons ago (i.e. about 8 years give or take) I was working on a large C++/VB6 project, and I had various bits of Batch Script to do parts of the build.
Then someone pointed me at the Joel Test, I was particularly enamoured of point 2, and set about bringing all my little build scripts into one single build script . . .
and it nearly broke my heart, getting all those little scripts working together, on different machines, with slightly different setups, ye Gods it was dreadful - particularly setting variables and parameter passing. It was really brittle, the slightest thing would break it and require 30 minutes of tweaking to get going again.
Eventually - I can be stubborn me - I chucked the whole lot in and in about a day re-wrote it all in JavaScript, running it from the command prompt with CScript.
I haven't looked back. Although these days it's MSBuild and Cruise Control, if I need to do something even slightly involved with a batch script, I use jscript.
The Windows command interpreter allows you to use the quotes around the entire set command (valid in every version of windows NT from NT 4.0 to Windows 2012 R2)
Your script should just be written as follows:
#echo OFF
set "myvar=C:\my music & videos"
Then you may put quotes around the variables as needed.
Working with the CMD prompt can seem esoteric at times, but the command interpreter actually behaves pretty solidly in obeying it's internal logic, you just need to re-think things.
In fact, the set command does not require you to use quotes at all, but both the way you are doing your variable assignment and the way the ,method of using no quotes can cause you to have extra spaces around your variable which are hard to notice when debugging your script.
e.g. Both of the below are technically Valid, but you can have trailing spaces, so it's not a good practice:
set myvar=some text
set myvar="some text"
e.g. Both of the below are good methods for setting variables in Windows Command interpreter, however the double quote method is superior:
set "myvar=Some text"
(set myvar=Some value)
Both of these leave nothing to interpretation the variable will have exactly the data you are looking for.
strong text However, for your purposes, only the quoted method will work validly because you are using a reserved character
Thus, you would use:
set "myvar=c:\my music & videos"
However, even though the variable IS correctly set to this string, when you ECHO the sting the command interpreter will interpret the ampersand as the keyword to indicate another statement follows.
SO if you want to echo the string from the variable the CMD interpreter still needs to be told it's a text string, or if you do not want the quotes to show you have to do one of the following:
echo the variable WITH Quotes:
Echo."%myvar%"
echo the variable WITHOUT Quotes:
Echo.%myvar:&=^&%
<nul SET /P="%myvar%"
In the above two scenarios you can echo the string with no quotes just fine. Example output below:
C:\Admin> Echo.%myvar:&=^&%
C:\my music & videos
C:\Admin> <nul SET /P="%myvar%"
C:\my music & videos
C:\Admin>
Try using the escape character '^', e.g.
set myvar=c:\my music ^& videos
You'll have you be careful when you expand myvar because the shell might not treat the & as a literal. If the above doesn't work, try inserting a caret into the string too:
set myvar=c:\my music ^^^& videos
Two solutions:
Don't use spaces or other characters that are special to the command interpreter in path names (directory or file names). If you use only letters, numbers, underscores, and hyphens (and a period before the extension to identify the file type), your scripting life will become immeasurably simpler.
I have written and otherwise collected a plethora of tools over the years, including a DOS utility that will rename files. (It began as something that just removed spaces from filenames, but morphed into something that will replace characters or strings within the filenames, even recursively.) If anyone's interested, I will get my long-neglected web site up and running and post this and others.
That said, variables aren't just for holding pathnames, so...
As others have already pointed out, SET "myvar=c:\my music & videos" is the correct workaround for such a variable value. (Yes, I said workaround. I agree that your initial inclination to just quote the value ("my music & videos") is far more intuitive, but it is what it is, as they say.