Windows cmd shell: if-then-else weirdness for block statements - windows

Trying to setup a simple build script that will expand the path based on other environment variables. This little script works fine:
echo off
call c:\vstudio\vc\bin\vcvars32.bat
set _ISGIT=1
echo current path is %PATH%
if defined _ISGIT set PATH=c:\git\bin;%PATH%
But if I want to do execute multiple lines based on the existence of the _ISGIT variable, then I thought this would work
echo off
call c:\vstudio\vc\bin\vcvars32.bat
set _ISGIT=1
echo current path is %PATH%
if defined _ISGIT (
set PATH=c:\git\bin;%PATH%
set PATH=c:\foo;%PATH%
)
But that yields the following output:
D:\>test.cmd
D:\>echo off
current path is C:\vstudio\Common7\IDE\CommonExtensions\Microsoft\TestWindow;C:\Program Files (x86)\MSBuild\14.0\bin;C:\
vstudio\Common7\IDE\;C:\vstudio\VC\BIN;C:\vstudio\Common7\Tools;C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319;C:\vstudio
\VC\VCPackages;C:\Program Files (x86)\HTML Help Workshop;C:\vstudio\Team Tools\Performance Tools;C:\Program Files (x86)\
Windows Kits\10\bin\x86;C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\;C:\ProgramData\Oracl
e\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\U
sers\jselbie\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files (x86)\Windows Kits\10\Windows Performance To
olkit\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\nodejs\;C:\Program Files (x86)\Skype\Phone\;C:\WINDOWS\s
ystem32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\NVIDIA Co
rporation\PhysX\Common;C:\Users\jselbie\.dnx\bin;C:\Users\jselbie\AppData\Roaming\npm;%USERPROFILE%\AppData\Local\Micros
oft\WindowsApps;
MSBuild\14.0\bin was unexpected at this time.
The MSBuild\14.0\bin was unexpected is likely a side effect of the original path containing a directory with a space. The presence of a space in the expanded command with the () seems to throw the script off.
How do I workaround this without having to have independent if defined statements?

There are two things wrong. First, one of the directories in the path contains parentheses. Variable expansion is performed before parsing, so the closing parenthesis from PATH is taken as the closing parenthesis of the IF block. To fix this, you need to put your assignment in quotes: set "PATH=...".
Secondly, inside a block (denoted by parentheses) all environment variables in the whole block are first expanded at once. Then the block is parsed. This means the expanded path in the second line is the same as it is on the first line. It is not changed by the first line. To fix this, you should either change the path entirely in one line:
if defined _ISGIT (
set "PATH=c:\foo;c:\git\bin;%PATH%"
)
or use delayed expansion:
setlocal enabledelayedexpansion
if defined _ISGIT (
set "PATH=c:\git\bin;!PATH!"
set "PATH=c:\foo;!PATH!"
)
Delayed expansion works by expanding a variable at a later stage, i.e. after the variable has been assigned by the first line. It has to be enabled with the setlocal command before it can be used.

set "PATH=c:\git\bin;%PATH%"
set "PATH=c:\foo;%PATH%"
using the quotes makes cmd interpret the quoted-string as a single token, so it doesn't see the ) within the variable [ie path] which is closing the if defined ... (

A contribution to nearly exhaustive Klitos Kyriacou's answer.
There is an implicit endlocal command at the end of a batch
file.
Hence, your test.cmd should be as follows (read ENDLOCAL):
#echo off
call c:\vstudio\vc\bin\vcvars32.bat
set _ISGIT=1
echo current path is %PATH%
setlocal enabledelayedexpansion
if defined _ISGIT (
set "PATH=c:\git\bin;!PATH!"
set "PATH=c:\foo;!PATH!"
)
endlocal&(
set "PATH=%PATH%"
)
or as follows (read CALL, note doubled percent signs):
#echo off
call c:\vstudio\vc\bin\vcvars32.bat
set _ISGIT=1
echo current path is %PATH%
if defined _ISGIT (
call set "PATH=c:\git\bin;%%PATH%%"
call set "PATH=c:\foo;%%PATH%%"
)

Related

Unable to append to environment variable in batch file

I'm trying to append four directories to %pythonpath%.
The directories are:
C:\src\tensorflow\models\research
C:\src\tensorflow\models\research\object_detection
..\utils
..\mail
When user is User42, %pythonpath% is always set to:
;C:\src\tensorflow\models\research\object_detection;..\utils;..\mail
Why is the first path ignored/overwritten?
#echo off
if "%username%"=="User42" (
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research
set pythonpath=%pythonpath%;C:\src\tensorflow\models\research\object_detection
) else (
:: Other path
)
:: This is common to all users
set pythonpath=%pythonpath%;..\utils;..\mail
echo %pythonpath%
Windows command processor replaces all environment variable references with syntax %VariableName% in a command block starting with ( and ending with matching ) during parsing phase of the next command line to execute on which a command block begins. In this case this means all %pythonpath% in both branches of the IF condition are substituted already by the current value of environment variable pythonpath before the IF condition is executed at all. This behavior can be seen by running the batch file without #echo off from within a command prompt window as in this case Windows command processor outputs the command lines after being parsed before execution.
The solution is using delayed expansion as also explained by help of command SET output on running in a command prompt window set /? on an IF and a FOR example, or avoiding the definition or modification of an environment variable and referencing it once again in same command block.
Here is a solution which works without usage of delayed expansion:
#echo off
set "Separator="
if defined pythonpath if not "%pythonpath:~-1%" == ";" set "Separator=;"
if /I "%username%" == "User42" (
set "pythonpath=%pythonpath%%Separator%C:\src\tensorflow\models\research;C:\src\tensorflow\models\research\object_detection"
) else (
rem Other path is added here to environment variable pythonpath.
)
rem This is common to all users. Variable pythonpath is defined definitely now.
for %%I in ("%CD%") do set "ParentPath=%%~dpI"
set "pythonpath=%pythonpath%;%ParentPath%utils;%ParentPath%mail"
echo %pythonpath%
It additionally makes sure there is not ;; in value of pythonpath in case of there is already a semicolon at end. And it makes sure pythonpath is not defined with a ; at beginning if this environment variable does not exist at all before first IF condition.
Further there are no relative paths added to pythonpath because of determining absolute path for ..\utils and ..\mails before appending them to pythonpath.
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.
echo /?
if /?
rem /?
set /?
BTW: Invalid labels :: as comments should not be used in command blocks. That can result in undefined behavior on execution. It is safer to use command REM for comments.
%pythonpath% used twice between parentheses will be evaluated on read before execution begins.
This is why %pythonpath% has the same value on the 2nd set.
You can use call set to force evaluation on the variable with doubled %s.
This has a similar effect to enabledelayedexpansion seen in setlocal /?
and if /?.
#echo off
if "%username%"=="User42" (
set "pythonpath=%pythonpath%;C:\src\tensorflow\models\research"
call set "pythonpath=%%pythonpath%%;C:\src\tensorflow\models\research\object_detection"
) else (
:: Other path
)
:: This is common to all users
set "pythonpath=%pythonpath%;..\utils;..\mail"
echo %pythonpath%

Splitting file path in batch using for loop and variable substitution

I have searched around for quite a while without any luck in getting my script working. I feel like I'm pretty close, but need a little help. I am attempting to use a FOR loop to recursively scan "srcdir" (set at the beginning of my script), then once the loop returns files/paths (%%f), then I can substitute part of the file path with something else (eg; C:\rootpath\src for C:\rootpath\des).
I am able to do something just like this by using a script like this one:
set subdir=C:\rootpath\src
set subdir=%subdir:src=des%
echo %subdir%
However, what makes this difficult is that the root path of my "srcdir" may change (eg; C:\roothpath) and everything recursively after the "srcdir" may change (eg. anything after folder "src" in C:\rootpath\src). The only constant paths the folder src and the folder des (located in the same directory where I am running my batch file from).
So, by using the same technique in the previous example, I want to use a FOR loop to recursively find the full path of the files in "srcdir" (%%f) and substitute the folder "src" with the folder "des" in the path string. Therefore, I am trying to set "%%f" as a variable (subdir) and replace the folders using variable substitution.
Here is my current non-working script:
set srcdir=C:\rootpath\src
for /r "%srcdir%" %%f in (*.txt) do (
set subdir=%%f
set subdir=%subdir:src=des%
echo %subdir%
)
Any help would be greatly appreciated! Thanks!
You need to enable delayed expansion since you are assigning and reading variables within a block of code like a for loop:
setlocal EnableDelayedExpansion
set "srcdir=C:\rootpath\src"
for /R "%srcdir%" %%F in ("*.txt") do (
set "subdir=%%~fF"
set "subdir=!subdir:\src\=\des\!"
echo(!subdir!
)
endlocal
The setlocal EnableDelayedExpansion command enables delayed expansion; it also localises the environment, meaning that changes to environment variables are available only before endlocal is executed or the batch file is terminated.
To actually use delayed expansion, you need to replace the percent signs by exclamation marks, so %subdir% becomes !subdir!.

Windows command line tilde operators for full environment variables?

In cmd, you can use the tilde "operator" to do some cool tricks with arguments passed in. For example, %~dp0 returns the pathname of the current script.
Can you do that for any environment variable? For example:
set foo=1234.exe
echo %~nfoo%
Is there a way to accomplish this?
You can also filter your variable through a for loop instead of a subroutine:
setlocal
set foo=1234.exe
for %%I in ("%foo%") do echo %%~nI
Yes, sort of.
In the following file (I named test1.cmd) there is an example of passing a file path and name to a subroutine in the same .cmd file and getting the drive letter and path back. By setting more environment variables on the last line of the subroutine you could return more combinations of drive letter, path, file name, attributes, etc.
That last line is the important part. The Windows command processor evaluates a line at a time, so that line first expands environment variables to their text values and then proccesses the line. That expanded line destroys the scope of the subroutine, returning to the outer scope (endlocal), sets a new variable (mytest2) to the value of the subroutine's (expanded) %dp1, then executes a goto :eof, which returns to the calling line.
setlocal
set mytest=c:\windows\a file with spaces in name.txt
call :mytest2 "%mytest%"
echo %mytest2%
:ender
endlocal
goto :eof
:mytest2
setlocal
echo %~dp1
endlocal && set mytest2=%~dp1 && goto :eof

Concatenate file paths to an environment variable in batch script

I have made a bat script that should copy the list of folders to a variable but I don't get anything in the variable. In other words, when I echo the variable after my for loop I get the expected output, but in the shell outside after executing the script, I don't see anything set in my variable. How can I get all the variables to copy correctly?
I am using Windows 7.
Batch FIle (script.bat):
#echo off
setlocal enabledelayedexpansion enableextensions
for /r /D %%x in (*) do (
SET PATH_VALUE=%%x;!PATH_VALUE!
)
echo %PATH_VALUE%
Output of windows cmd utility
C:\test> script.bat
C:\test\1;C:\test\2
C:\test> echo %PATH_VALUE%
%PATH_VALUE%
How do I get the %PATH_VALUE% as an environment variable? I found a similar question here but it doesn't quite answer my case.
That is because of your SETLOCAL command that you use to enable delayed expansion. Yes it provides the delayed expansion you need, but it also localizes environment changes. As soon as your batch script ends, there is an implicit ENDLOCAL, and the old environment is restored.
You can pass the value across the ENDLOCAL barrier by adding the following to the end of your script:
endlocal&set "PATH_VALUE=%PATH_VALUE%"
or you could write it like:
(
endlocal
set "PATH_VALUE=%PATH_VALUE%"
)
Both of the above work because the blocks of code are expanded and parsed prior to the ENDLOCAL executing, but the SET statement with the expanded value is executed after the ENDLOCAL.

Parenthesis in Windows cmd-script variable values not allowed?

Why gives the following Windows 7 .cmd command script:
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
if 3==3 (
set JAVA_HOME=%SUN_JAVA_HOME%
)
echo ready
The following error message instead of printing "ready"
\Java\jdk1.6.0_17 was unexpected at this time.
The error message disapears, if I remove the "(x86)" in the path name.
on the command prompt, enter the following commands
C:
CD\
dir /ogen /x
This will show you the 8 character name for Program Files (x86)
Use that name (probably "Progra~2")
The problem is the parentheses grouping after the if 3==3 part.
While parsing the set JAVA_HOME=%SUN_JAVA_HOME% command, the interpreter immediately replaces the %SUN_JAVA_HOME% variable and that causes an early match of the closing parenthesis in (386).
This can be avoided if you enable delayed expansion and replace %SUN_JAVA_HOME% with !SUN_JAVA_HOME!:
setlocal enabledelayedexpansion
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
if 3==3 (
set JAVA_HOME=!SUN_JAVA_HOME!
)
echo ready
you have to enclose the set command by double quotes
replace
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
by
set SUN_JAVA_HOME="C:\Program Files (x86)\Java\jdk1.6.0_17"
because there's a space in the path
I've written about this a while ago (slightly outdated by now).
As an alternative, if you need grouping the commands, then use a subroutine:
if 3==3 call :foo
...
goto :eof
:foo
...
goto :eof
Previous answer is ok. I just want clarify it with simple example. It's about detecting Program Files directory for 32-bit application on x86 and x64 systems. There similar problem with "(x86)".
IF DEFINED ProgramFiles(x86) (GOTO x64) ELSE (GOTO x86)
:x64
SET AppDir=%ProgramFiles(x86)%\SomeFolder
GOTO next
:x86
SET AppDir=%ProgramFiles%\SomeFolder
:next
ECHO %AppDir%

Resources