How do SETLOCAL and ENABLEDELAYEDEXPANSION work?
Asked Answered
A

4

75

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?

Anybody answered 13/7, 2011 at 13:44 Comment(4)
i usually put that line at the top of nearly all of my scripts and it rarely causes a problem, and I like how it handles escape characters better, especially when parsing XML or HTML.Hardden
@Hardden have you looked into using AutoIt for such tasks? Highly recommend it. I've moved away from batch/powershell/vbscript for AutoIt.Anybody
yeah, i have heard of it. maybe I will take a closer look. i don't like requiring 3rd party stuff though... and it appears I might as well use Powershell if that is the case.Hardden
there is also a "SETLOCAL ENABLEEXTENSIONS" option but I don't remember what effects it has.Hardden
S
32

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.

Sinistrorse answered 13/7, 2011 at 13:50 Comment(4)
what about the 2nd part to the question?Anybody
Well it would only have an adverse effect if you enabled it, then wrote some code that behaved differently were it not onSinistrorse
And it seems that unfortunately there is no way to enable delayed expansion without invoking setlocal, even though these control what ought to be separate features. :-PInkblot
I think Alex K has answer that part: ENDLOCAL is omitted because when we reach the end, SETLOCAL stops to function. We programmer just loves to keep it short... but is less verbose.Trudi
R
141

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).

Rodriquez answered 27/8, 2013 at 11:39 Comment(3)
To be clear, delayed expansion does nothing if no exclamation marks are present in your commands, right?Rags
Right! Exclamation marks do have other, subtle effects.Rodriquez
+1 Encountered this issue when checking %errorlevel% returned from another batch file in a for loop. Added the setlocal enabledelayedexpansion before the for loop, and changed the check from %errorlevel% to !errorlevel! and it worked like a charm. Thanks again for the in depth explanation!Exchequer
S
32

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.

Sinistrorse answered 13/7, 2011 at 13:50 Comment(4)
what about the 2nd part to the question?Anybody
Well it would only have an adverse effect if you enabled it, then wrote some code that behaved differently were it not onSinistrorse
And it seems that unfortunately there is no way to enable delayed expansion without invoking setlocal, even though these control what ought to be separate features. :-PInkblot
I think Alex K has answer that part: ENDLOCAL is omitted because when we reach the end, SETLOCAL stops to function. We programmer just loves to keep it short... but is less verbose.Trudi
F
11

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.

Flexuosity answered 21/7, 2011 at 20:25 Comment(4)
With EnableDelayedExpansion you got even problems if your text contains only a single ! like echo Hello! and with a exclamation mark in a line you got also problems with carets echo "Caret^" is gone!Salesman
@Salesman yep just struggled with this. The fix i have is [setlocal enableextensions enabledelayedexpansion] at the top of the script and before i enter a for loop that will be looking at files, e.g. [for /r %%a in (.rar,.zip) do] i do [setlocal disabledelayedexpansion]. Inside the for loop i store the %%a to a variable first, e.g. [set archive_path=%%a], and if i need to do any directory trimming i will do [setlocal enabledelayedexpansion] prior. The only good thing to say about this is it works, but its a failure of the language imho.Plenty
@FocusedWolf: You are right, it's a bit broken. Your solutions sounds like How to read a file?Salesman
@Salesman yes this is the same approach. Earlier i noticed the need for the endlocal inside the loop like your example shows. Its weird because in some tests not having it either caused no issues, or made it behave odd (specifically affecting %%~dpa string manipulation for some reason), or lead to "Maximum setlocal recursion level reached" errors.Plenty
M
0

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.)

Mallett answered 30/1, 2014 at 1:10 Comment(2)
Over the years of using batch files, I've automatically added that directive to each and every one of em and never turn it off. I wouldn't go as far as to enable it by default for a system because you never know if a system-ran batch script or some other company's batch scripts may have conflicts with it. This IS Windows we're talking about ;)Anybody
@Mechaflash Good points, but the problem that @Mallett is talking about is the hard coupling between setlocal and enabledelayedexpansion. If you want a script that affects the environment, but also wants enabledelayedexpansion, then you need to resort to hacks (like temporary script files) to make it work.Egret

© 2022 - 2024 — McMap. All rights reserved.