pCan anyone explain why SET is not doing anything in the following code?:
ECHO OFF
FOR /L %%b IN (-1,1,1) DO (
SET /A _lat=%%b
ECHO variable lat: %_lat%
ECHO variable b: %%b
ECHO:
)
output:
variable lat:
variable b: -1
variable lat:
variable b: 0
variable lat:
variable b: 1
I tried SET without /A of course, I tried that within FOR where variable %%b was a string from a file and it worked normally, but just here when variabl %%b is a changing number it does not work.
If you would have any suggestion, please tell me. Of couse I can use just %%b to diplay what I want, but since this is a part of larger scritp, to make it more readable, I would like to but the vaules to approproate variable.
You need to use ENABLEDELAYEDEXPANSION. Try with the below:
setlocal ENABLEDELAYEDEXPANSION
echo off
for /L %%b in (-1,1,1) do (
set /A _lat=%%b
echo variable lat: !_lat!
echo variable b: %%b
echo:
)
Take a look at the output of SET /? in a console. You need to enable and use delayed environment variable expansion for this to work. Here's the relevant snippet from the help set:
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%
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 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.
I have the following snippet (some code omitted):
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST %%f (
echo(
echo Destination folder %%f does not exist. Is version %version% correct? Please update the script with your required parameters.
SET /p continue="Do you want to abort the operation? [Y/N]:"
echo cont=%continue%
IF %continue:~0,1%==y (
goto :eof
)
IF %continue:~0,1%==Y (
goto :eof
)
)
)
The issue is that the variable continue is not being assigned until after the script has been executed. For example, if continue was N from the the previous execution and then I input Y it will not update it, hence the echo after the assignment will output cont=N although it should be cont=Y. What am I doing wrong?
You need delayed expansion. setlocal enabledelayedexpansion somewhere, and use !continue! instead of %continue%. The problem is that %continue% is expanded as the entire parenthetical code block is read for the first time, so it's treated as flat text by the time for performs its first loop. !continue! retains its variability, and it's re-evaluated on each iteration.
You can also avoid the delayed expansion problem by using choice and if errorlevel like this:
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST "%%~f" (
echo(
echo Destination folder %%f does not exist. Is version %version% correct?
echo Please update the script with your required parameters.
choice /N /M "Do you want to abort the operation? [Y/N]: "
if not errorlevel 2 goto :EOF
)
)
To learn in greater detail about delayed expansion, read this portion of the help set documentation:
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 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%]
File test.cmd:
name=dummy
for /f "eol=; tokens=1 delims=," %%i in (list.txt) do (
echo i: %%i
set name=%%i
echo name: %name%)
the file list.txt contains this lines (one name per line):
John
Tom
Erica
Sara
Each time I launch this batch I get this output:
i: John
name: dummy
i:T om
name: dummy
i: Erica
name: dummy
i: Sara
name: dummy
It seems that the variable name does not get assigned the value of %%i
Any idea?
You're falling into the old trap of not using delayed expansion.
For a quick fix, just put
setlocal enabledelayedexpansion
before that loop in your batch file and use !name! instead of %name%.
CMD expands variables while parsing a command. A command in this sense is a single line or a “block”, delimited with parentheses. The complete for loop is only parsed once and in that stage %name% gets replaced with the value it has at that point, namely "dummy". Delayed expansion on the other hand uses ! instead of % to delimit variable names and the variables then get expanded right before execution of a command.
Whenever you are setting a variable inside a parenthesized block and use its value in the same block again you need to use delayed expansion. help set has also some info about this:
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%