Setting a variable in a "setlocal enabledelayedexpansion" works with
set HASGCC=0
for /f "delims=" %%i in (...) do (
setlocal enabledelayedexpansion
set HASGCC=1
endlocal
)
but sadly a echo after doesn't result in the correct value (always 0).
echo Finished %HASGCC%
Afterwards
if !HASGCC! == 0 >>"%PREFS_F...
is evaluated correct.
How to print correct value.
echo Finished !HASGCC!
results in
Finished !HASGCC!
setlocal with or without EnableDelayedExpansion creates a new scope for variables.
It makes a copy of all variables and then all changes are made to these copies.
An endlocal leaves this scope and discard the copy and all changes are dsicarded.
For some variable manipulations you have to enable delayed expansion for some it's better to use disabled delayed expansion, but the only way to switch between them is to use setlocal.
But in your case it seems, that you don't need it at all.
Solved it by removing
setlocal enabledelayedexpansion
but don't know why.
Related
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 am fighting with little piece of code for last two days.
In this I am not able to set variable in a for loop.
I want to assign a filename to a variable for string manipulation.
echo off
for /f %%a IN ('dir /b *_ah.ttf') DO (
set /a fName=%%~na
echo %fName%
)
When I echo fName variable I get only last filename repeatedly number of times for for loop count.
(I want to pass this variable as an argument to some batch file as follows
ttfhnt --strong-stem-width=D -i %%a %fName:~0,-3%.ttf
but its failing due to above problem)
Can somebody help me please?
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
And set /A is only used for arithmetic operations
setlocal enabledelayedexpansion
for /f "delims=" %%a IN ('dir /b *_ah.ttf') DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
edited to adapt to comments
While for command is able to execute a command (in the OP code, the dir...), retrieve its output and then iterate over the lines in this output, the original reason for the command is to iterate over a set of files. In this form, the code can be written as
setlocal enabledelayedexpansion
for %%a IN ("*_ah.ttf") DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
Now, the for command replaceable parameter will iterate over the indicated set of files. (execute for /? for a list of all the command options).
But as foxidrive points, the problem with delayed expansion are the exclamation signs. Without delayed expansion, they are another normal character, but with delayed expansion they frequently become a problem when a value containig them is assigned/echoed.
A quick test
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
echo normal : %test%
for /f "delims=" %%a in ("!test!") do echo for : %%a
Will show
---------------------
test=this is a test!
---------------------
delayed : this is a test!
normal : this is a test
for : this is a test
Obviously when the value is a file name, this behaviour will make the code find or not the file.
Depending on the case different solutions can be used, but usually it involves the activation / desactivation of the delayed expansion behaviour (beware, the endlocal removes any change in environment variables from the previous setlocal).
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
rem Commuted to no delayed expansion
setlocal disabledelayedexpansion
echo normal : %test%
endlocal
rem Cancelled the initial enable delayed expansion
for /f "delims=" %%a in ("!test!") do endlocal & echo for : %%a
rem The last endlocal has removed the changes to the variable
echo no data : [%test%]
How to get selected character
for %%A in (controls\vbalSGrid6.ocx) do (
SET TEXT=%A%
SET SUBSTRING=%TEXT:~9%
echo %SUBSTRING%
)
this is giving echo is off but i only need vbalsgrid6.ocx.
The direct way
set "text=controls\vbalscrid6.ocx"
set "substring=%text:~9%"
No need for the for command, unless you are iterating over a set of files or you don't want to use substring operations to get file names
The easy way to get the name and extension of the file
for %%a in (controls\vbalsgrid6.ocx) do set "fileName=%%~nxa"
%%a hold a reference to the file, and %%~nxa is the file name and extension of the referenced file
A direct translation/corrected version of your code (in this case, iterating over the list of files, but not needed)
#echo off
setlocal enableextensions enabledelayedexpansion
for %%a in (controls\*.ocx) do (
set "text=%%a"
set "substring=!text:~9!"
echo !substring!
)
When the batch parser reaches a line/block of code (code inside parenthesis), the full line/block is checked searching the places where a variable will be readed. All this reads are replaced with the value stored in the variable at parse time, before the line/block is executed. That means that if a variable changes its value inside a block, this changed value can not be accessed from inside the same block as the read operation on the variable was previously replaced with the initial value stored inside it.
To handle this case, delayed expansion is used. When delayed expansion is enabled, it is possible to change (where needed) the syntax to read a variable, from %var% to !var!, indicating to the parser that this read operation should be delayed until the command is executed.
The included code will work while there is no ! in the name of the files. As delayed expansion is active, the parser will try to interpret any !, giving non expected results in some cases. It can be handled but sometimes it can be a bit tricky.
#echo off
setlocal enableextensions disabledelayedexpansion
for %%a in (controls\*.ocx) do (
rem Retrieve the initial text. No problem as delayed expansion is disabled
set "text=%%a"
rem Enable delayed expansion to read the value in %text%. And ensure
rem it is disabled at the moment of the assignment to the substring var
setlocal enabledelayedexpansion
set "substring="
for /f "delims=" %%b in ("!text:~9!") do (endlocal & set "substring=%%b")
rem We need delayed expansion enabled to read the changed value
rem If substring is empty, the previous endlocal was not executed and
rem there is no need for a new setlocal
if defined substring setlocal enabledelayedexpansion
echo(substring value=!substring!
endlocal
)
I have a simple ini file... really just a key=value file that I want to use to set as variables for my script.
My ini file:
DATABASE=snoopy
My Batch file code
#echo off
SET DATABASE=woodstock
FOR /f "tokens=1,2 delims==" %%a in ('C:\mycfg.ini') do (
echo a=%%a
echo b=%%b
pause
SET %%a=%%b
ECHO DATABASE=%DATABASE%
)
The echo a and b are correct, it shows
a=DATABASE
b=snoopy
But at the end when I echo %DATABASE% after calling the SET %%a=%%b
It still shows
DATABASE=woodstock
If I use delayed expansion, it works but only locally. I need it to overwrite the global so I don't see why this shouldn't work.
Well, no need to do anything to make it work.
It works.
Your problem is that when the for block (all the lines in parenthesis in your code) are read, variables are replaced with their values, so the line echo %DATABASE% is converted in echo woodstock. BUT the variable hold the correct value, changed inside the for loop. Try to place the echo outside of the for and see what the value is.
Delayed expansion is needed when a variable is changed inside a block and it is necesary to access the changed value inside the same block.
Ok I got it now..
Had to enable delayed expansion at the top of the file rather than just on the subroutine call. Then set the %%a and %%b to delayed variables and set them equal to eachother and that worked. Final code:
#echo off
SetLocal EnableDelayedExpansion
SET DATABASE=woodstock
FOR /f "tokens=1,2 delims==" %%a in ('C:\mycfg.ini') do (
set tmpA=%%a
set tmpB=%%b
SET !tmpA!=!tmpB!
)
ECHO DATABASE=%DATABASE%
And that changes it to "snoopy"
Thanks all!
Am trying to read characters of a string inside a for loop.
The command !string:~1,3! works fine. But can I do this with variables instead of 1 and 3. I tried the following code, but I don't know what is wrong. Its not working.
#echo off
setlocal enableextensions enabledelayedexpansion
set string=abcdefghij
set /a count=1
for /l %%x in (1,1,3) do (
set string2=!string:~%count%,1!
set /a count+=1
echo !string2!
pause
)
but It always gives the output as:
b
I want the output to be as:
b
c
d
Kindly help in solving this.. A big thanks in advance
In order to achieve what you want, you need to do a Delayed Expansion twice, that is, something like this:
set string2=!string:~!count!,1!
Of course, previous line is invalid. Although there are several ways to solve this problem, most of they use call command that is slow. To fix this problem so it run in the fastest way use a for command to change the first !count! expansion into a FOR replaceable parameter, and then use it in the original expression:
for %%i in (!count!) do set string2=!string:~%%i,1!
The problem is that the expansion of %count% isn't delayed, so it has the same value for every loop iteration. It's better written like this:
#echo off
setlocal enableextensions enabledelayedexpansion
set string=abcdefghij
set /a count=1
for /l %%x in (%count%,1,3) do (
set string2=!string:~%%x,1!
echo !string2!
)
Edit
If you want to keep %count% evaluated as the variable is set and not just at the beginning of the for loop, use Aacini's answer.