Batch file variable with spaces and parentheses - windows

I've read numerous threads on different approaches to getting the windows batch file parser to properly handle variables that have spaces, parentheses, and other special characters, but none of the recommendations seems to be able to fix the issue I am having.
Here is the script (prior to trying any workarounds), whose goal is to set a value for variable03 based on the values found for variable01 and variable02:
set variable01="C:\Program Files (x86)\SomeProgram\Subfolder"
set variable02="${macro}"
set variable01=%variable01:"=%
set variable02=%variable02:"=%
set variable03=""
if %variable02:~0,1%==$ (
if %variable01:~0,1%==$ (
set variable03=%variable03:"=%
) else (
set variable03=-o '%variable01%'
)
)
...the values of variable01 and variable02 are not known in advance - they are substituted by another program prior to running the script, so the above script is showing an example set of values for variable01 and variable02 after that substitution has been made.
The error I get when this script runs is:
\SomeProgram\Subfolder' was unexpected at this time.
...which corresponds to the last 'set' line in the above script. I assumed that this error was due to the parentheses in the value of variable01.
If I change that line to this:
set "variable03=-o '%variable01%'"
...then I get this error:
Files was unexpected at this time.
...which seems to indicate that it is trying to tokenize on the spaces in variable01, and the parser is still not happy.
If I then add this line at the top of the script:
setlocal enableextensions enableDelayedExpansion
...and change %variable01% to !variable01!, I still get the same error.
Clearly, I do not understand what the batch file parser needs to meet my requirement that the value of variable03 has the following value:
-o 'C:\Program Files (x86)\SomeProgram\Subfolder'
...any suggestions?

As Nate wrote, the problem in this case are the brackets, but the complete code is still unstable.
It's always better to use delayed expansion, as this is safe against any special characters.
And you should use the extended syntax of SET set "variable=content" to enclose the complete expression with quotes, then it's nearly safe, and the quotes are not added to the content.
And you don't need to remove the quotes later.
This should work with any content in var1 and var2
setlocal EnableDelayedExpansion
set "variable01=C:\Program Files (x86)\SomeProgram\Subfolder"
set "variable02=${macro}"
set "variable03="
if "!variable02:~0,1!"=="$" (
if "!variable01:~0,1!"=="$" (
rem
) else (
set "variable03=-o '!variable01!'"
)
)
echo(!variable03!

The problem is the parentheses in variable01's value. Since it's being expanded in an if condition, those parentheses are being interpreted as flow control. Fix by always putting it in double quotes.
set variable01="C:\Program Files (x86)\SomeProgram\Subfolder"
set variable02="${macro}"
set variable01=%variable01:"=%
set variable02=%variable02:"=%
set variable03=""
if "%variable02:~0,1%"=="$" (
if "%variable01:~0,1%"=="$" (
set variable03=%variable03:"=%
) else (
call :set3 "%variable01%"
)
)
goto :eof
REM If it is important to have single quotes instead of double then
REM I found I had to call a subroutine. Otherwise the set could be
REM left up in the else.
:set3
set variable03=-o '%~1'
goto :eof

Related

ren won't use my variable from one lina above

I've got a strange problem. I want to rename multiple files in a folder.
So far, so easy - in theory. I use this script:
cd C:\Test
for %%i in (*(7*) do (
set name="%%i"
ren "%name%" "%name:~0,-15%.txt"
)
pause
The strange thing is that he seems to not use the variable "name" I declared
one line above the ren command as you can see in what the console prints:
C:\Test>(
set name="ttttt(7xAAdoc) .txt"
ren "" "~0,-15.txt"
)
What am I missing here? I am running Windows 7, if thats important.
Thanks for any help.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
In your case,
cd C:\Test
setlocal enabledelayedexpansion
for %%i in (*(7*) do (
set "name=%%i"
ren "%name%" "!name:~0,-15!.txt"
)
note the positioning of the quotes in the first set. The set "var=value" syntax ensures that any trailing spaces on the batch line are not included in the value assigned to var. As you had it, name would be assigned a "quoted" value and the ren command (had it worked) would have been `ren ""filename"" ""firstpartoffilename".txt"

Windows batch file syntax using exclamation mark

While checking the details of the axis2server.bat file in Axis2 binary distribution I see one of the line containing text something like:
FOR %%c in ("%AXIS2_HOME%\lib\*.jar") DO set AXIS2_CLASS_PATH=!AXIS2_CLASS_PATH!;%%c
What does the part below with 2 exclamation marks mean?
!AXIS2_CLASS_PATH!
Names with in % mean variables, not sure what ! mark mean in a batch file.
When you enable delayed expansion and change or set a variable within a loop then the !variable! syntax allows you to use the variable within the loop.
A drawback is that ! becomes a poison character for delayed expansion.
As foxidrive mentioned, this is related to delayed expansion. You can find more information by running help set in a cmd prompt, which has the following explanation:
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%
I wanted to hand over a string containing a "!" as parameter (for imageMagick) and the ! was of course interpreted as syntax which broke my script. The solution was for me, change my string from
"Hello World!"
to (just added a ^ before the !):
"Hello World^!"
I found this trick by reading here: https://www.robvanderwoude.com/escapechars.php

Problems reading simple numeric values from files in Windows batch files

My colleague and I have been pulling our hair out all day over this.
We have a simple Windows batch file. We want it to read from a text file whose file path we are generating programmatically, take the single numeric value in this file, and compare it to a local variable. But we're getting completely inexplicable behavior.
The file contains a single scalar number, such as the number 2. Here's the code:
ThisAppFlagFileName=foo.txt
if not exist "%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%" (
ECHO do something here
) else (
SET /P InstalledVersion=<"%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%"
ECHO We think the file contains: %InstalledVersion%
IF %InstalledVersion% GEQ %ThisVersionInstallDataNum% (
ECHO Version %ThisVersion% of the %ThisAppVisibleName% has already been installed for this user; exiting.
GOTO TheEnd
)
)
:TheEnd
Echo END
We keep getting an error reading 2 was unexpected at this time. So we inserted some trace message and, just in case the else was problematic, stuck to two different if statements:
ThisAppFlagFileName=foo.txt
if not exist "%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%" (
ECHO do something here
)
ECHO Trace Message 1 before IF
if exist "%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%" (
ECHO Trace Message 2 after IF before CD
SET /P InstalledVersion=<%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%"
ECHO We think the file contains: %InstalledVersion%
IF %InstalledVersion2% GEQ %ThisVersionInstallDataNum% (
ECHO Version %ThisVersion% of the %ThisAppVisibleName% has already been installed for this user; exiting.
GOTO TheEnd
)
)
:TheEnd
Echo END
And we see only the first trace message (before the if statement), and not the second trace message. So our conclusion is that somehow the content of the file is being interpolated into the line if exist "%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%", but of course we don't understand why the first if not exist works but the second doesn't.
Can anyone spot the mistake, please? Environment is Windows 7 cmd.exe window, but we are hoping to deploy to both Windows 7 and Windows XP.
The issue here is that the entire IF expression is evaluated before the SET /P statement within it can be executed. InstalledVersion is not set yet, and so this invalid expression is evaluated:
IF GEQ 2 (
Nothing inside of the IF expression executes because it cannot be completely evaluated.
A solution is to enable delayed expansion and replace %InstalledVersion% with !InstalledVersion!, as described in this post.
You can also restructure the code so the GEQ comparison happens after the IF expression.
Your code have several errors. The first line:
ThisAppFlagFileName=foo.txt
missed a set command, so it is tryed to be executed as ThisAppFlagFileName command. This mean that ThisAppFlagFileName variable is NOT defined in your program, so InstalledVersion variable is never read from the file.
All references to InstalledVersion variable must use Delayed Expansion, that is, enclose they between exclamation marks instead percents and include setlocal EnableDelayedExpansion command at beginning of your program.
setlocal EnableDelayedExpansion
set ThisAppFlagFileName=foo.txt
if not exist "%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%" (
ECHO do something here
) else (
SET /P InstalledVersion=<"%HOMEPATH%\ourcompanyname\%ThisAppFlagFileName%"
ECHO We think the file contains: !InstalledVersion!
IF !InstalledVersion! GEQ %ThisVersionInstallDataNum% (
ECHO Version %ThisVersion% of the %ThisAppVisibleName% has already been installed for this user; exiting.
GOTO TheEnd
)
)
:TheEnd
Echo END
You must be aware that all variables that are modified inside parentheses must also be enclosed in exclamation marks instead percent signs. Search for "delayed expansion" for details.

Check whether a sub-string contains string

I am attempting to check whether a sub-string occurs within a string. If it does then I dont want to perform the 'if' conditions.
My Problem: My code that checks whether a sub-string occurs within a string is not working. It always thinks that a sub-string does NOT occur within a string when it actually does.
How can I check whether a sub-string occurs within a string in batch?
SET filePath="c:/users/abc/dir1/subdir"
SET excludeDir1="c:/users/abc/dir1"
SET excludeDir2="c:/users/abc/dir2"
REM // If the string excludeDir1 does not occur in filePath AND If the string excludeDir2 does not occur in filePath: continue
if /i NOT x%filePath:%excludeDir1%=%==x%filePath% if /i NOT x%filePath:%excludeDir2%=%==x%filePath% (
REM // Do stuff
)
You almost have it. Just remember that the parsing of the lines in a Batch file is performed from left to right, so there is no chance to nest two %variable% expansions. The way to solve it is combining one %normal% expansion and one !delayed! expansion:
REM Next command is required in order to use Delayed !variable! Expansion
SETLOCAL EnableDelayedExpansion
SET filePath="c:/users/abc/dir1/subdir"
SET excludeDir1="c:/users/abc/dir1"
SET excludeDir2="c:/users/abc/dir2"
REM // If the string excludeDir1 does not occur in filePath AND If the string excludeDir2 does not occur in filePath: continue
if /i NOT "!filePath:%excludeDir1%=!" == "%filePath%" if /i NOT "!filePath:%excludeDir2%=!" == "%filePath%" (
REM // Do stuff
)
Use powershell or install Cygwin and use a real POSIX/UNIX/LINUX shell like bash. You will have much better success testing strings and file paths with BASENAME and FILENAME and utilities like 'grep' and 'find' than what is available to you with CMD.EXE. You will also find plenty of stackoverflow examples from 10+ years ago on how to do all of that stuff in a proper shell.

How to keep the value of a variable outside a Windows batch script which uses "delayed expansion local" mode?

Context: I need to call a Windows batch script which would update my PATH by adding another path 'xxx' at the end of it, but:
without any duplicate
(if I add 'xxx' to a PATH like 'aaa;xxx;bbb', I need an updated PATH like 'aaa;bbb;xxx')
without any aggregation
(I can call the script repeatedly without ending up with 'aaa;bbb;xxx;xxx;xxx;...')
What I have tried:
The following function takes care of any duplicate and does the job
:cleanAddPath -- remove %~1 from PATH, add it at the end of PATH
SETLOCAL ENABLEDELAYEDEXPANSION
set PATH=!PATH:%~2=!
set PATH=!PATH:;;=;!
set PATH=%PATH%;%~2
set P=!P:;;=;!
echo %PATH%
echo -------------
ENDLOCAL
exit /b
But, it needs delayed expansion local mode, which means: at the end of the script (or here, at the end of the function cleanAddPath), whatever has been set for %PATH% is thrown away.
I could ask the users (for which I write the script) to launch their cmd with a cmd /V:ON option (activating the delayed expansion, otherwise off by default), but that is not practical.
How can I modify the PATH variable the way I described above, and still have it updated in my current DOS session after calling said script?
The page "DOS - Function Collection" gives great example on how a function can return a value in DOS, even when using delayed expansion mode:
The following function will update any variable you want with an addition PATH:
:cleanAddPath -- remove %~2 from %~1, add it at the end of %~1
SETLOCAL ENABLEDELAYEDEXPANSION
set P=!%~1!
set P=!P:%~2=!
set P=!P:;;=;!
set P=!P!;%~2
set P=!P:;;=;!
(ENDLOCAL & REM.-- RETURN VALUES
SET "%~1=%P%"
)
exit /b
Note the concatenation of paths using. As jeb comments:
The line set P=%P%;%~2 is critical if your path contains ampersands like in C:\Documents&Settings.
Better change to set "P=!P!;%~2".
The SET "%~1=%P%" is the part which allows to memorize (in the variable represented by %~1) the value you have set using delayed expansion features.
I initially used SET "%~1=%P%" !, but jeb comments:
The command SET "%~1=%P%" ! could be simplified to SET "%~1=%P%" as the trailing exclamation mark has only a (good) effect in delayed expansion mode and if you prepared %P% before.
To update your PATH variable, you would call your function with:
call :cleanAddPath PATH "C:\my\path\to\add"
And it will persists after leaving that script, for your current DOS session.
dbenham's answer points to a more robust answer (upvoted), but in my case this script is enough.
The problem isn't as simple as you think. There are a number of issues that can break your code before it ever gets to the end where it needs to return the updated value across the ENDLOCAL barrier.
I already answered this question as an extension to an answer I provided for a similar question. See How to check if directory exists in %PATH%?. In that answer I provide a large list of issues that complicate the problem.
The code at the bottom of the linked answer shows how to reliably add a path if it does not exist in PATH already, and it also demonstrates how to reliably return the value across the ENDLOCAL barrier.
The following edits are from VonC in an attempt to actually put the answer here instead of just a link to the answer. I'll preserve the edit, but I find it difficult to follow without the context of the full linked answer.
[The answer demonstrates how to reliably return the value] using the set "%~1=%var%" ! trick (with the trailing '!')
That thread includes:
That's not clear to me. How can an exclamation mark behind the last quote influence the variable content?
The simple rule for delayed expansion is:
For each character in the line do:
If it is a caret (^) the next character has no special meaning, the caret itself is removed
If it is an exclamation mark, search for the next exclamation mark (carets are not observed here), then expands to the content of the variable
If no exclamation mark is found in this phase, the result is discarded, the result of the phase before is used instead (important for the carets)
So, at this point the difference should be clear, the carets are removed even if the exclamation mark have no other effect in a line.
Example:
#echo off
setlocal EnableDelayedExpansion
echo one caret^^
echo none caret^^ !
set "var1=one caret^"
set "var2=none caret^" !
echo !var1!
echo !var2!
----- OUTPUT ----
one caret^
none caret
one caret^
one caret
Yay! Finally got this working with the following test code:
#echo off
Setlocal enabledelayedexpansion
Set p="hello world"
( endlocal & rem return
Set "a1=%p%"
)
Set a1
This outputs:
a1="hello world"
The reason I used delayed expansion in the test without using any !'s is because it still effects how set works and the batchs I'm testing this for all have delayed expansion.
Thanks for the help guys :o)
PS I tried using the same variable name for both local and external environments but this broke the code. Hence the 2 names used.

Resources