Batch File EnableDelayedExpansion and Variable Setting - batch-file

I'm editing a batch file given to me and I'm not sure what the following line of code does:
set allKeys=%allKeys% !currentKey!
thanks!

It appends the run-time value of the variable currentkey after a space to the parse-time value of allkeys and assigns the result as the run-time value of allkeys - provided delayedexpansion is invoked. If delayedexpansion is not invoked, it appends the string !currentKey!, not the value of the variable currentkey.
Without any context information, we're guessing beyond that...

Here is an example in code to try.
allkeys is set outside the for in do loop.
Inside the for in do loop it is changed but as it uses %allkeys% the changes are not cumulative.
#echo off
setlocal enabledelayedexpansion
set allkeys=one
for %%a in (two three four five six) do (
set currentkey=%%a
set allKeys=%allKeys% !currentKey!
echo allkeys is now "!allkeys!"
)
echo allkeys is now "%allkeys%" outside the loop
pause
Change this line
set allKeys=%allKeys% !currentKey!
to this and run it to see the difference.
set allKeys=!allKeys! !currentKey!

For a decription of what delayed expansion is, type SET /?.

Related

Why does echo use a different value of loop-variable than set?

I am attempting to use a simple for-loop to create a series of strings followed by a number. However, when I try to add the iterator-variable to the end of the strings, the value which is added is instead the endpoint of the loop.
A simplified version of the script is:
#echo off
set STARTSTRING="Test-
for /l %%x in (1, 1, 26) do (
echo %%x
set CONCATENATED_STRING=%STARTSTRING%%%x"
echo %CONCATENATED_STRING%
)
pause
Which gives the output:
Can someone please explain to me where I am going wrong?
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.
So -
#echo off
setlocal enabledelayedexpansion
set "STARTSTRING=Test-"
for /l %%x in (1, 1, 26) do (
echo %%x
set "CONCATENATED_STRING=!STARTSTRING!%%x"
echo !CONCATENATED_STRING!
)
pause
Note the positioning of the quotes (so that stray spaces on the ends of lines are not allocated to the variable.)
!var! accesses the run-time value of the variable in delayedexpansion mode - %var% the parse-time value
The setlocal also ensures that when the batch ends, any changes made to the environment variables is discarded. Withou it, as you have seen, the variable retains its value as set on the previous batch-run.

How to access to the value of an environment variable in other variable in batch file

this is my code :
SET JAVA_VERSION=%1
REM here lets assume the user enter a JAVA_7_HOME as an argument
#ECHO %JAVA_VERSION%
REM this print me JAVA_7_HOME because JAVA_7_HOME is STRING
REM Now I want to access to the value of environment variable JAVA_7_HOME so I done this :
if %%%JAVA_7_HOME%%% ==[]GOTO INDEFINED_VARIABLE
REM here I concatinate JAVA_7_HOME with % % at the left and at the right but doesn't works
I Do not Know how to access to the value of the environment variable JAVA_7_HOME.
Thank you at all.
CALL SET JAVA_VERSION=%%%1%%
The line will be parsed to
CALL SET JAVA_VERSION=%JAVA_7_HOME%
Now, the call command is executed and the line reparsed, and the final command executed is
SET JAVA_VERSION=C:\SomeWhere
Also, you can enable delayed expansion and do
SETLOCAL EnableDelayedExpansion
SET JAVA_VERSION=!%1!
I backed to this subject :), So your solution it works fine without a loop for but now I changed the concept and I have a variable called CONTEXT_VARAIABLE which contains some others names of environnements variables for example in my case is:
CONTEXT_VARAIABLE=JAVA_HOME;JBOSS_HOME
so now I loop from this variable CONTEXT_VARAIABLE to get each variable and do some treatement with it so my code here is:
setlocal EnableDelayedExpansion
FOR %%L IN (%SOLIFE_VERSION%) DO (
SET JAVA_JBOSS_HOME=%%%%L%%
#ECHO !JAVA_JBOSS_HOME!
)
here each time of the loop it prints me:
%JAVA_HOME%
%JBOSS_HOME%
and now my problem I want to access to these variables and if I use CALL SET it makes an error because I think we are not allowed to use CALL SET in loop for.
So please if you have an idea about this problem.
Thank you.

Batch file if statement weirdness with variable assignment and expansion

I have a batch file and I'm trying to work with some variables in an if statement. For example I set a variable either from command line parameter 1, or if there is no parameter 1 then I have a default value. Then, depending on whether the value is the default or an argument from the command line I perform some other function.
What I notice is that the variable I've assigned in the if statement isn't available after the if statement is over. Can someone explain why that is?
For example this will fail to echo what's in TEST1:
#echo off
setlocal ENABLEEXTENSIONS
if .%1==. (
set TEST1=NOTFOUND
echo %TEST1%
) else (
set TEST1=%1
echo %TEST1%
)
If I run that batch file with or without an argument it returns ECHO is off.. It thinks the variable is still empty.
If I attempt to get the variable after the if statement, it works:
#echo off
setlocal ENABLEEXTENSIONS
if .%1==. (
set TEST1=NOTFOUND
) else (
set TEST1=%1
)
echo %TEST1%
I'm sure this has to do with variable expansion but it really threw me. It looks like I will assign another variable in the if statement like set USING_DEFAULT_TEST1=TRUE and set USING_DEFAULT_TEST1=FALSE or compare to the default value or something.
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.
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.

Nested variable name using EnableDelayedExpansion

I am working on a script to get max lengths of each column, I'm trying to store lengths of max length in _c1...n vars. number of columns unknown.
I was able to get length for each column, create variables to store each with set _c!i! = !n!, n is the length
but in order to set the max length for a particular column I need to compare current with max and use something like !_c!!i!! which doesn't work, any ideas how to refer a variable which part of it's name coming from another variable?
Thanks...
I assume that you are using the delayed expansion character because you are working inside a set of brackets "()". Doing that makes your process harder. I know that method is easier to read, but it is harder to code for.
Inside brackets, I know of only one method to access a variable that was 'built' out of one or more variables. That is to use the call function to cause the assembled variable to 'activate'. This method works both inside and outside of brackets.
Here is a small example:
#echo off
setlocal enabledelayedexpansion
(
set i=10
set _c!i!=something
:: below is equivalent to echo !_c10!
call echo %%_c!i!%%
)
endlocal
Output:
something
You can do almost everything using a CALL in front of it that you can without it, though in XP or earlier you cannot call internal commands like if and can only call 'external' programs like FIND.EXE.
If you can work outside of a set of brackets by possibly using a call :label statement, you can simply access the variable like this:
#echo off
setlocal enabledelayedexpansion
set i=10
set _c!i!=something
:: The below 2 statements are equivalent to `echo %_c10%`
echo !_c%i%!
call echo %%_c!i!%%
endlocal
Output:
something
something
The CALL technique suggested by James K will work, but it is relatively slow and can be unsafe, depending on the content of the variable.
The following looks more complicated, but it is significantly faster and more reliable:
for %%A in (!i!) do echo !_c%%A!
In your case there could be a third solution be possible, if your variables contains only numbers.
#echo off
setlocal enabledelayedexpansion
(
set i=10
set _c!i!=4711
set /a tmp=_c!i!
echo !tmp!
)
This works, as SET /A can access the content of a variable without the nedd of explicitly expansion charaters.

How do SETLOCAL and ENABLEDELAYEDEXPANSION work?

I notice in most scripts, the two are usually in the same line as so:
SETLOCAL ENABLEDELAYEDEXPANSION
Are the two in fact separate commands and can be written on separate lines?
Will setting ENABLEDELAYEDEXPANSION have an adverse effect on a script if it is set on the first lines of the script and not disabled until the end of the script?
I think you should understand what delayed expansion is. The existing answers don't explain it (sufficiently) IMHO.
Typing SET /? explains the thing reasonably well:
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%
Another example is this batch file:
#echo off
setlocal enabledelayedexpansion
set b=z1
for %%a in (x1 y1) do (
set b=%%a
echo !b:1=2!
)
This prints x2 and y2: every 1 gets replaced by a 2.
Without setlocal enabledelayedexpansion, exclamation marks are just that, so it will echo !b:1=2! twice.
Because normal environment variables are expanded when a (block) statement is read, expanding %b:1=2% uses the value b has before the loop: z2 (but y2 when not set).
ENABLEDELAYEDEXPANSION is a parameter passed to the SETLOCAL command (look at setlocal /?)
Its effect lives for the duration of the script, or an ENDLOCAL:
When the end of a batch script is reached, an implied ENDLOCAL is
executed for any outstanding SETLOCAL commands issued by that batch
script.
In particular, this means that if you use SETLOCAL ENABLEDELAYEDEXPANSION in a script, any environment variable changes are lost at the end of it unless you take special measures.
The ENABLEDELAYEDEXPANSION part is REQUIRED in certain programs that use delayed expansion, that is, that takes the value of variables that were modified inside IF or FOR commands by enclosing their names in exclamation-marks.
If you enable this expansion in a script that does not require it, the script behaves different only if it contains names enclosed in exclamation-marks !LIKE! !THESE!. Usually the name is just erased, but if a variable with the same name exist by chance, then the result is unpredictable and depends on the value of such variable and the place where it appears.
The SETLOCAL part is REQUIRED in just a few specialized (recursive) programs, but is commonly used when you want to be sure to not modify any existent variable with the same name by chance or if you want to automatically delete all the variables used in your program. However, because there is not a separate command to enable the delayed expansion, programs that require this must also include the SETLOCAL part.
A real problem often exists because any variables set inside will not be exported when that batch file finishes. So its not possible to export, which caused us issues. As a result, I just set the registry to ALWAYS used delayed expansion (I don't know why it's not the default, could be speed or legacy compatibility issue.)

Resources