I am trying to figure out what the following expression means in a bat file I am working with:
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
!modified! >> %outvar%
This is declared for execution right after a delimiting if statement for "_" and obtain arguments passed to the execution of a script then separate them as strings and write the strings in a file.
More specifically: what do the exclamations (!*!) do in this situation?
I've searched for like an hour but to no result. Can anyone give a hint?
The ! marks are the delayed expansion notation for variables (see SETLOCAL /? for documentation on this). Essentially, the ! marks tell the processor to evaluate the variable at the time the line is executed instead of when it is parsed, which is the behavior of %.
Take this simple example:
SET MyValue=This
IF "%MyValue%"=="This" (
SET NewValue=That
SET MyValue=%NewValue%
)
ECHO NewValue = %NewValue%
ECHO MyValue = %MyValue%
REM Outputs:
REM NewValue = That
REM MyValue =
MyValue does not have a value because when the IF statement was parsed, %NewValue% did not have a value assigned yet (because the SET line had not yet been processed).
Now consider this:
SETLOCAL EnableDelayedExpansion
SET MyValue=This
IF "%MyValue%"=="This" (
SET NewValue=That
SET MyValue=!NewValue!
)
ECHO NewValue = %NewValue%
ECHO MyValue = %MyValue%
REM Outputs:
REM NewValue = That
REM MyValue = That
ENDLOCAL
This works because the delayed expansion notation !, tells the processor to evaluate !NewValue! when the respective line is executed.
Additionally in your case, the SET line doing the replacement allows for variables to be used as the replacement parameters. Delayed expansion notation is much easier to work with instead of having to break it out into CALL statements.
The exclamation marks are used for delayed variable expansions, and are used in the same way as %-signs are for variables. This is mainly used inside parenthesis. This is because of the way batch executes loops and if-statements. consider this code:
#echo off
set "i=0"
if %i% equ 0 (
set "i=1"
echo %i%
)
pause
When executed, this doesn't echo 1, like expected, but echoes 0. That is because %i% get's replaced with 0 before the statements inside the if are executed. However, if you use !i! and setlocal EnableDelayedExpansion, like this:
#echo off
setlocal EnableDelayedExpansion
set "i=0"
if %i% equ 0 (
set "i=1"
echo !i!
)
echo %i%
pause
It will correctly echo 1. Also note that ! is only needed inside parenthesis, not outside of them.
Related
In order to return parameter values i need to change a string which requires to replace exclamation marks with an expression so that these exclamation marks are escaped later in the process. The current solution doesn't work inside an if. The following working script shows the issue quite well:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET string="World^! wasserschutzpolizei^!"
REM REM Works quite well here!
REM SET "return1=!string:"=""!"
REM SET "return1=%return1:!=^^^!%"
REM SET "return1=!return1:""="!"
REM ECHO !return1!
IF NOT "!string!"=="" (
SET "return1=!string:"=""!"
REM This line doesn't work because of the % % inside the if...
REM Because it is one logical line.
SET "return1=%return1:!=^^^!%"
SET "return1=!return1:""="!"
ECHO !return1!
)
EXIT /B 0
ENDLOCAL
I had one non-working approach which uses CALL SET:
CALL SET "return1=%%return1:!=^^^!%%" but this doesn't work.
For an answer consider that I need delayed expension for other stuff too.
So, how do I need to change the code, espacially line SET "return1=%return1:!=^^^!%" so that it works inside the if?
Avoid using multi-line statements in batch scripts. It's not just difficult to get them right, they are hard to debug while developing the code. Delayed expansion is most useful when you need to use variables within variable replacement expressions. Here's your code the simple way:
#setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
#set prompt=$G
set input="World^! wasserschutzpolizei^!"
#rem Works quite well here!
#set "return1=!input:"=""!"
#set "return1=%return1:!=^^^!%"
#set "return1=!return1:""="!"
#echo %return1%
#if not "!input!"=="" call :DoIt
#echo %return1%
#exit /b 0
:DoIt
#set "return1=!input:"=""!"
#set "return1=%return1:!=^^^!%"
#set "return1=!return1:""="!"
#exit
Use subroutines when a short simple expression can't be used for if/loop bodies.
If you reversed your thinking and instead of using IF NOT …(, used IF …, you wouldn't have that problem as you wouldn't be setting variables within a block:
#ECHO OFF
SET "string=World! wasserschutzpolizei!"
IF "%string%"=="" EXIT /B 1
SET "return1=%string:"=""%"
SET "return1=%return1:!=^^^!%"
SET "return1=%return1:""="%"
ECHO %return1%
PAUSE
EXIT /B 0
The string will now have pre-escaped exclamation marks ready for your later process.
Like the answer of Compo, but this one works also for more than one return variable.
IF defined string (
SET "return1=!string:"=""!"
)
IF defined string (
SET "return1=%return1:!=^^^!%"
SET "return1=!return1:""="!"
)
It splits the code into two seperate blocks, therefore the percent expansion will work
In the following script I call a subroutine with a string, the maximal length of a substring of that string and a 3rd non-existing variable to get the substring back.
The script should check if the next character after the string with the maximal substrings length is a space and if yes, then cut the string by that space (delete space to) and returning the substring and change the passed string by cutting the substring part. Example:
string: "Hello World Bla"
substringLength: 5
CALL :get_substring string substringLength substring
=> string: "World Bla" (no space), substringLength: 5 (no change), substring: "Hello"
This works fine without if clause but it doesn't when i use an if clause, even if i use delayed expansion.
Here is the working code without if statement:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET string=Hello World wasserschutzpolizei
SET /A substringLength=5
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
EXIT /B 0
ENDLOCAL
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET nextChar=!string:~%substringLength%,1!
REM IF "!nextChar!"==" " (
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
REM ) ELSE (
REM Some other case
REM )
EXIT /B 0
This doesn't work when I comment in the if statement:
IF "!nextChar!"==" " (
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
) ELSE (
REM Some other case
)
Why does the script doesn't work with an if-statement?
How can fix it?
Be aware that my routine should also include an else statement which I cutted out because the problem is the same.
The problem is this line:
SET string=!string:~%cutSpaceCount%!
When this line is placed inside the IF command, then the value of cutSpaceCount is changed inside the code block (parentheses) of the IF, and hence it must be expanded via !cutSpaceCount! delayed expansion, not via a %cutSpaceCount% standard expansion.
You should use something like a "double delayed expansion", that is, similar to this construct:
SET string=!string:~!cutSpaceCount!!
Of course, this don't work, so the trick is use a for command to get the value of the first delayed expansion, and then use the FOR parameter to complete the second delayed expansion:
for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"
A similar problem happen when the final values in the subroutine are returned to the calling program. This is the final working code:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET string=Hello World wasserschutzpolizei
SET /A substringLength=5
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
EXIT /B 0
ENDLOCAL
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET "nextChar=!string:~%substringLength%,1!"
IF "!nextChar!"==" " (
SET "substring=!string:~0,%substringLength%!"
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
for /F %%c in ("!cutSpaceCount!") do SET "string=!string:~%%c!"
ECHO !string!
for /F "delims=" %%s in ("!string!") do for /F "delims=" %%b in ("!substring!") do (
ENDLOCAL & SET "%1=%%s" & SET "%3=%%b" & EXIT /B 0
)
) ELSE (
ECHO Some other case
)
PS - You don't need to expand variable values in SET /A command. Instead of:
SET /A cutSpaceCount=!substringLength!+1
you may simply use:
SET /A cutSpaceCount=substringLength+1
You seem to be very confused about the sequence of operations that occurs when delayed expansion has been invoked.
First, the value of var is substituted for %var%.
Then !var! is evaluated using the results.
The scope of this sequence of operations is one logical line, which may be one physical line or any number of physical lines continued with a terminal ^ or more commonly using a parenthesised sequence of lines.
In your mainline then,
CALL :get_substring string substringLength substring
ECHO !string!
ECHO !substring!
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0
Since these statements are not within the same logical line, they will be individually evaluated, so !var!==%var%.
Within your subroutine (non-IF version),
SET substring=!string:~0,%substringLength%!
ECHO !substring!
SET /A cutSpaceCount=!substringLength!+1
SET string=!string:~%cutSpaceCount%!
ECHO !string!
again are individual statements. The first set will first substitute for substringlength, and then execute SET substring=!string:~0,5! as a second operation.
Each of the echoes is a stand-alone statement, and the ! could (and preferably should) be replaced by %.
The set /a statement - well, set /a allows the current value of a variable to be used undecorated, so SET /A cutSpaceCount=substringLength+1 or SET /A cutSpaceCount=%substringLength%+1could be used here with no logical effect.
ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will be evaluated according to the values established by the previous code-sequence.
However when you add the if, the code is parenthesised and thus becomes one logical statement and acts differently.
The echoes then require ! because you want to display the modified values within the code-block. Since cutSpaceCount is not set at the start of the code-block, SET string=!string:~%cutSpaceCount%! will be evaluated as SET string=!string:~!
and then ENDLOCAL & SET %1=%string% & SET %3=%substring% & EXIT /B 0 will duly substitute the values of the variables as they stood when the IFwas encountered
So, a replacement routine might be
:get_substring
SETLOCAL ENABLEDELAYEDEXPANSION
SET "string=!%~1!"
SET "substringLength=!%2!"
SET "substring=!string:~0,%substringLength%!"
SET "string=!string:~%substringLength%!"
IF "%string:~0,1%"==" " SET "string=%string:~1%"
ENDLOCAL & SET "%1=%string%" & SET "%3=%substring%"
EXIT /B 0
As others already explained, the problem is the line:
SET string=!string:~%cutSpaceCount%!
because you use immediate expansion (%) for variable cutSpaceCount which is changed in the same logical line/block of code.
A possible solution is to use call like this:
call set "string=%%string:~!cutSpaceCount!%%"
call introduces another variable expansion phase, so the sequence goes as follows:
immediate expansion phase where %% becomes %:
call set "string=%string:~!cutSpaceCount!%"
then delayed expansion occurs (let us assume a sample value of 5):
call set "string=%string:~5%"
another immediate expansion phase introduced by call to finally get %string:~5%.
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).
I know the question is a little confusing.
This is my script. (easy version)
I've set up those variables
p1 = 1
p2 = 0
p3 = 1
and it goes on and on.
I want to show a list of all of those P values. Then,
I setup a variable "i"
set /a i = 0
:loop
set /a i = %i% + 1
echo P%i% = %p%i%%
goto loop
but then I realized that %p%i%% is not working...
It won't go to %p1% and show me "1",
it just shows me %p1%. and in my full script, it shows me something crazy... SO CAN ANYONE HELP ME OUT HERE?? THANKS A LOT!!
Two simple ways.
The first is to use delayed expansion.
First expand i with %i% and later the expression !p1!
Or use CALL to parse a line a second time.
The line call echo P%i% = %%p%i%%% after the first parse looks like
call echo P1 = %p1%
The second parse will expand %p1% to the content.
setlocal EnableDelayedExpansion
set p1=1
set p2=0
set p3=1
set /a i = 0
:loop
set /a i = %i% + 1
echo P%i% = !p%i%!
call echo P%i% = %%p%i%%%
if %i% == 3 exit /b
goto loop
Or another solution using a for loop
for /L %%i in (1 1 3) do (
echo P%%i = !p%%i!
)
I offer two more solutions in addition to the solutions in answer of jeb not using delayed expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "MyP1=1"
set "MyP2=0"
set "MyP3=5"
set "Count=0"
for /F "tokens=1* delims==" %%I in ('set MyP 2^>nul') do echo Variable %%I has value %%J & set /A Count+=1
echo Variables count is: %Count%
echo/
set "Index=1"
:Loop
if not defined MyP%Index% goto ExitLoop
set /A Value=MyP%Index%
echo Variable MyP%Index% has value %Value%
set /A Index+=1
goto Loop
:ExitLoop
set /A Index-=1
echo Variables count is: %Count%
endlocal
echo/
pause
The environment variables with undetermined number start all with the string MyP. The first approach runs set MyP in a separate command process in background which outputs all environment variables starting with MyP with their values sorted alphabetically, i.e. the output is for this batch code:
MyP1=1
MyP2=0
MyP3=5
This output is processed line by line by FOR which splits each line up into two substrings because of delims== with first substring being name of the environment variable assigned to loop variable I and everything after first equal sign in the line assigned to next loop variable J according to ASCII table which is the value of the environment variable because of tokens=1*.
The names of the environment variables and their values are output with counting the number of variables starting with MyP which is also output after the FOR loop.
The number of the environment variables must not be consecutive increasing for the first solution.
The second solution uses a loop without FOR. This loop requires that the environment variables have an incrementing number in their names. The loop exits if there is no more environment variable with current index number in name.
The value of the current environment variable is assigned to environment variable Value using an arithmetic expression because in this case a string like MyP1 in the arithmetic expression is interpreted as name of an environment variable whose value should be converted to an integer for evaluation of the expression. The integer result of the arithmetic expression is converted back to a string and assigned to the environment variable left to the equal sign.
The answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? might be helpful to understand the environment variables usage techniques as used here.
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 /?
endlocal /?
for /?
goto /?
if /?
pause /?
setlocal /?
See also Single line with multiple commands using Windows batch file for an explanation of & operator used on FOR command line to avoid the need of a command block.
I have a variable that apparently is not being correctly evaluated in a condition.
The scenario is the following: I've made a batch file that is set with SetLocal EnableExtensions, then it has a main routine that is set with SetLocal EnableExtensions EnableDelayedExpansion. The main routine calls a subroutine from within a nested if statement passing two parameters, and it gets a variable as a result. The subroutine being called is set with SetLocal EnableExtensions.
Problem is that when evaluating the variable that returns the subroutine, apparently the variable is not being correctly evaluated: a variable that one line before is echoed and confirmed to be equal to zero makes an if !variable! EQU 0 statement inexplicably return FALSE.
More explanations and details after the code below.
Sample code (Proof of Concept)
#echo off
SetLocal EnableExtensions
rem Initialize variables
rem -------------------------------------------------------------------------------
set ErrorStatus=0
set PreConditionOne=1
set PreConditionTwo=1
set ValueOne=50
set ValueTwo=50
rem - Main Routine
rem -------------------------------------------------------------------------------
SetLocal EnableExtensions EnableDelayedExpansion
rem Setting initial values for flag variables, value can be: 0=NO, 1=YES, 2=Not Checked.
set PreCheckFail=2
set ParamsNotEqual=2
rem Prechecks: Preconditions must be met in order to check variables.
echo Checking PreConditions.
if %PreConditionOne%==1 (
echo OK: First PreCondition is met.
echo Checking Second PreCondition.
if %PreConditionTwo%==1 (
echo OK: Second PreCondition is met.
echo Calling subroutine to check if values match.
call :Subroutine %ValueOne% %ValueTwo%
echo Back from subroutine.
echo InLoop: ErrorLevel: %ErrorLevel%, ErrorStatus: %ErrorStatus%, ParamsNotEqual: %ParamsNotEqual%
echo InLoopDelayed: ErrorLevel: !ErrorLevel!, ErrorStatus: !ErrorStatus!, ParamsNotEqual: !ParamsNotEqual!
rem This condition fails to correctly check ParamsNotEqual, it works fine with ErrorLevel.
if !ParamsNotEqual! EQU 0 (
echo OK Parameteres verified successfully.
set PreCheckFail=0
) else (
echo ERROR Parameter verification failed.
set PreCheckFail=1
)
) else (
echo ERROR: Second PreCondition is not met.
set PreCheckFail=1
)
) else (
echo ERROR: First PreCondition is not met.
set PreCheckFail=1
)
echo OutLoop: ErrorLevel: %ErrorLevel%, ErrorStatus: %ErrorStatus%, ParamsNotEqual: %ParamsNotEqual%
echo PreCheckFail: %PreCheckFail%
if %PreCheckFail% EQU 0 (echo "PreCheckFail IS ZERO") else (echo "PreCheckFail NOT ZERO")
endlocal
goto :Finish
:Subroutine
rem Subroutine
rem -------------------------------------------------------------------------------
SetLocal EnableExtensions
set ParamOne=%1
set ParamTwo=%2
echo This is the subroutine.
echo The first parameter passed is: %ParamOne%
echo The second parameter passed is: %ParamTwo%
if "%ParamOne%"=="%ParamTwo%" (
echo OK^! Both variables are equal.
set ParamsNotEqual=0
set ErrorStatus=0
) else (
echo ERROR^! Both variables are different.
set ParamsNotEqual=1
set ErrorStatus=1
)
echo EndSub: ErrorLevel: %ErrorLevel%, ErrorStatus: %ErrorStatus%, ParamsNotEqual: %ParamsNotEqual%
endlocal & set ParamsNotEqual=%ParamsNotEqual% & exit /b %ErrorStatus%
:Finish
endlocal
If you run this script, you will see that the subroutine correctly returns the ParamsNotEqual variable as 0, then the echo lines correctly show the values for both the normally parsed variables and the delayed ones:
InLoop: ErrorLevel: 0, ErrorStatus: 0, ParamsNotEqual: 2
InLoopDelayed: ErrorLevel: 0, ErrorStatus: 0, ParamsNotEqual: 0
But when the condition if !ParamsNotEqual! EQU 0 is evaluated, it just fails. Setting #echo on does not clarify anything, as the delayed variables are expanded on execution time so I cannot see what is really being evaluated there.
Test I've done so far
Things that have no effect so it keeps failing:
Set SetLocal EnableExtensions EnableDelayedExpansion for the whole script
Set SetLocal EnableExtensions EnableDelayedExpansion for the subroutine too.
Not setting previously the ParamsNotEqualvariable.
Evaluating the condition surrounding it with quotes, or brackets, or using == instead of EQU.
Evaluating if !ParamsNotEqual! EQU 2 (which is the initial value of the variable) also returns FALSE
Possible workarounds:
Using !ErrorLevel! instead of !ParamsNotEqual! works fine. This looks logic, as ErrorLevel is a system variable, not a user-defined one.
Not setting SetLocal for the subroutine, hence having the subroutine variables not isolated from the rest of the script, makes the main routine automagically work fine as it is. This has no sense to me but makes me think that this error may be due to some kind of restriction either when calling a subroutine from within a nested if when EnableDelayedExpansion is set, or with using nested SetLocal statements.
I would like to keep using the value of ParamsNotEqual instead of ErrorLevel, and also, I would like to keep the variables within the subroutine isolated with SetLocal.
So, my question: Is this strange behaviour due to something am I doing wrong or is this a restriction in the way the command processor handles variables when EnableDelayedExpansion is set?
Any hint will be greatly appreciated. If you need any further tests or clarification on the above don't hesitate on asking. A lot of thanks in advance.
The result is correct as ParamsNotEqual isn't equal to 0, as you set it to 0<space>.
This line contains the problem.
endlocal & set ParamsNotEqual=%ParamsNotEqual% & exit /b %ErrorStatus%
You should always use the extended syntax of SET with surrounding quotes
endlocal & set "ParamsNotEqual=%ParamsNotEqual%" & ...