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.)
Related
I'm trying to make a simple batch file ("javapath.bat") to add the Java compiler to the path when I need it so it won't be on the path all the time. I also want be able to do something like #call javapath.bat in other build scripts so the path can be added automatically when needed. Since those will be run repeatedly during the edit-save-compile-run grind, that means that javapath.bat needs to check if Java is already on the path and not readd it if it is, because apparently Microsoft thinks it's a good idea to let the path variable have lots of silly duplicates.
So to detect if it needs to be added I use setlocal to enable "command extensions" so I can use the environment variable string substitution thing. That ugliness works fine.
Then I use endlocal so I can actually set the enviroment variables without the changes being reverted at the end of the script. That's not working. Or, it certainly stops the variable changes being reverted, but it's not normal: it completely stops them from being visible locally, but they are still visible afterwards.
#echo off
setlocal enableextensions
if "%path:jdk1=%"=="%path%" (
endlocal
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
)
After the above, ANT_HOME and JAVA_HOME are properly set. But the only change to PATH is that "\bin;" has been prepended to it, because none of the variables set during the script seem to be visible until afterwards (so ANT_HOME and JAVA_HOME are blank, and the first change to PATH is forgotten). Therefore, running it twice adds Java to the path okay, and not Ant. I could hardcode the paths twice but this behavior is so bizarre and ridiculous and I've been stuck on it for an hour.
Edit: Adding enabledelayedexpansion had no effect either.
#echo OFF
ECHO starting %PATH%
if "%path:jdk1=%"=="%path%" CALL :addjava
ECHO.
ECHO resulting %PATH%
GOTO :eof
:addjava
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
SET "path=%ANT_HOME%\bin;%JAVA_HOME%\bin;%path%"
GOTO :eof
This is what I'd use - other methods run afoul of the mininterpreted closing-parenthesis problem.
The key to understanding this odd behaviour is history. Batch has always substituted the parse-time value of any %var% into the code, then validated the result and executed if valid. As the language developed, it was necessary to maintain compatibility with existing batches, so you could only ADD new keywords and functionality, not remove or alter functionality.
So, as the capacity to call internal subroutines was added, and cascade instructions on a single line with '&' and allow multi-line instructions for if and for by enclosing the instructions in parentheses were introduced, and the capacity to use spaces and other separator characters in file or directory names was required, the batch language began to have a few little quirks.
It was a really bizarre decision to have the ! to access the run-time value of a variable invoked as a subclause of setlocal - personally, I'd have used a switch like ECHO on/off (ie EXPANSION on/off) but I'm not running the project. In the same way, DATE could have been equipped with a /u switch to return the date in a universal form, but the opportunity was missed (and continues to be missed, 17 years after NT4 and 5 wingenerations later...)
As others have noted, extensions should already be enabled except under rather extraordinary circumstances. All you need is to eliminate your SETLOCAL and restructure your IF a bit so that it exits the script if the PATH is already set.
#echo off
if not "%path:jdk1=%"=="%path%" exit /b
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If you really need to enable extensions, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" exit /b
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
If your script has additional work to do, then
#echo off
setlocal enableExtensions
if not "%path:jdk1=%"=="%path%" goto :skip
endlocal
set "ANT_HOME=C:\Program Files\Java\ant"
set "JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07"
path %ANT_HOME%\bin;%path%
path %JAVA_HOME%\bin;%path%
:skip
REM carry on with additional code as needed
Everything inside the if block is evaluated in one go. So %ANT_HOME% has no effect after set ANT_HOME, you want delayed expansion you need to type:
setlocal enabledelayedexpansion
if "%path:jdk1=%"=="%path%" (
set ANT_HOME=C:\Program Files\Java\ant
set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_07
path = !ANT_HOME!;!path!
path = !JAVA_HOME!;!path!
)
:: important trick since they evaluate together %path% is still
:: what is inside local
endlocal & path %path%
path
Otherwise no delayed expansion. Also you need to use on undeleyed call with endlocal to escape the block. remember % variables never delay.
#echo off
cd %~dp0
md .\newfolder
for /f "usebackq delims=" %%f in ("list.txt") do (
call set /a add=%%add%%+1
call set addx=0000%%add%%
call set addx=%%addx:~-3%%
call copy "%%f" ".\newfolder\%%addx%%_%%f"
)
pause
I made simple namechange code. I usually use command without 'call' but here it makes error message . why is that? .. and when i use %variable% not %%variable%% , It doesn't work well..
plz tell me why it happens.. and last question.. environment variable's value is stored until exit cmd . I want to know how i can unset that.. thank you..
All code within a parenthesized block is parsed in one pass. Normal variable expansion using percents occurs at parse time. So if you set a variable within a block, you cannot access the value using normal expansion because the value will be the value that existed before you entered the block.
You have the above situation. There are two classic ways to resolve the problem.
1) You can use CALL and double the percents as you have done. The CALL solves the problem because normal expansion occurs twice for a called line - once for the entire block, and again before the line is executed, but after previous lines in the block have executed. The first expansion converts the double percents to single percents, and the second expansion actually expands the variable.
I do not like this solution because it is slow, and also because the CALL causes problems with quoted ^ characters - they are doubled.
You can use multiple CALLs on the same command. Each Call requires the percents to be doubled. So one CALL requires 2 percents, two CALLs requires 4 perecents, three CALLs 8 percents, etc.
2) I think the preferred solution is to use delayed expansion. It is much faster, and also you never have to worry about escaping or quoting special characters like &, |, >, < etc. when you used delayed expansion. Delayed expansion does just what it says - the variable is not expanded until just before the line is executed. Delayed expansion must be enabled before it can be used. Within a batch file you can use setlocal enableDelayedExpansion.
The one problem that can occur with delayed expansion is FOR variables are corrupted if they contain ! and delayed expansion is enabled when they are expanded. That can usually be solved by toggling delayed expansion on and off within the loop.
If you type HELP SET from the command prompt, you will get a pretty good description of the problem with expanding variables within a block of code, and how delayed expansion can help. The description starts about half way down with the words Finally, support for delayed environment variable expansion....
Note - you do not need to expand variables when used within a SET /A computation. SET /A will automatically expand the value at execution time. Undefined variables are treated as zero.
In your code, you can simply use set /a add=add+1
But there is an even simpler shorthand way - you can use the += operator: set /a add+=1.
Here is another way your code could be written without using CALL. The code is untested, but I think I got it right.
#echo off
setlocal disableDelayedExpansion
cd "%~dp0"
md newfolder
set add=0
for /f "usebackq eol=: delims=" %%F in ("list.txt") do (
set /a add+=1
set "file=%%F"
setlocal enableDelayedExpansion
set "addx=00!add!"
copy "!file!" "newfolder\!addx:~-3!_!file!"
endlocal
)
pause
I explicitly initialize add to 0 because it might already be set to a value. If you know that it is undefined or already set to 0, then the initialization is not needed.
Your FOR loop is dealing with file names, and ! is valid within file names. That is the reason I toggle delayed expansion on and off within the loop - I don't want file names with ! to be corrupted when I expand %%F. File names can also start with ; (though highly unlikely). If it does, then FOR will skip that file because the default EOL character is ;. A file can never start with :, so I like to set EOL to : instead.
I put SETLOCAL near the top so that the environment variable definitions do not persist after the batch file completes.
I'd like to put each of the many properties' file names into variable fileName and echo them out to the command prompt window. But only the last properties file name to be cycled thru is printed out as many times as there are properties files. Is there an easy fix to this problem. I know that ...DO echo %%-nxG can do the same thing but I'd like to save the file name in %%~nxG for future use.
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo %fileName%
)
You need to use delayed expansion:
setlocal enabledelayedexpansion
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo !fileName!
)
Environment variables in cmd are expanded when a command is parsed – in this case this includes the whole block in parentheses. So %fileName% gets replaced by an empty string because it didn't have a value before the loop ran. Delayed expansion uses ! instead of % and changes variable evaluation so that they are evaluated just before a command is run.
help set has more details about why and when it is necessary. In general, whenever you modify and use a variable within a loop you have to use delayed expansion, but it comes with a few other benefits too.
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%
I'm trying to accomplish the following ridiculous task:
I have a text file containing a set of fully qualified filesnames. I want to iterate through the file and append each line to a common variable, that can be passed to a command line tool. For example, the file might be:
C:\dir\test.txt
C:\WINDOWS\test2.txt
C:\text3.txt
and I'd like to assign them to some variable 'a' such that:
a = "C:\dir\test.txt C:\WINDOWS\test2.txt C:\text2.txt"
A secondary question is - what is a good batch file reference? I'm finding some stuff in the Windows material, and a lot of home-grown websites, but nothing particularly complete.
As for references, SS64.com isn't bad. Rob van der Woude gets linked fairly often, too.
As for your problem, that's easy:
#echo off
setlocal enableextensions enabledelayedexpansion
set LIST=
for /f %%x in (yourfile.txt) do (
set LIST=!LIST! "%%x"
)
echo %LIST%
endlocal
More in-depth explanation:
setlocal enableextensions enabledelayedexpansion
We're enabling delayed expansion here. This is crucial as otherwise we wouldn't be able to manipulate the list of files within the for loop that follows.
for /f %%x in (yourfile.txt) do (
set LIST=!LIST! "%%x"
)
for /f iterates over lines in a file, so exactly what we need here. In each loop iteration we append the next line to the LIST variable. Note the use of !LIST! instead of the usual %LIST%. This signals delayed expansion and ensures that the variable gets re-evaluated every time this command is run.
Usually cmd expands variables to their values as soon as a line is read and parsed. For cmd a single line is either a line or everything that counts as a line, which happens to hold true for blocks delimited by parentheses like the one we used here. So for cmd the complete block is a single statement which gets read and parsed once, regardless of how often the interior of the loop runs.
If we would have used %LIST% here instead of !LIST! then the variable would have been replaced immediately by its value (empty at that point) and the loop would have looked like this:
for /f %%x in (yourfile.txt) do (
set LIST= "%%x"
)
Clearly this isn't what we wanted. Delayed expansion makes sure that a variable is expanded only when its value is really needed. In this case when the interior of the loop runs and constructs a list of file names.
Afterwards the variable %LIST% or !LIST! (now it doesn't really matter anymore which to use) contains the list of lines from the file.
Funnily enough, the help for the set command includes exactly this example for delayed expansion:
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%
What you're after can be done with a FOR /F command.
Here's a good resource I've used many times:
http://www.robvanderwoude.com/batchfiles.php
A good book: Windows NT Shell Scripting by Tim Hill. The edition I have was published in 1998 but it is still valid for Windows command programs in Windows 2008.
type *commonfilepart* >> concat_result_file
OS: WINDOWS SERVER 2003