9

If I run the batch file

setlocal
chdir ..

the directory is not changed, but if I run

setlocal
endlocal 
chdir ..

it works normally. This must be exactly what is expected with setlocal. However, it is not entirely obvious when you read the definition of setlocal, which is related to how the environment variables are seen. I am hoping that this is a good occasion to explain what setlocal actually does and why it interferes with chdir.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388

2 Answers2

7

The HELP documentation for SETLOCAL (help setlocal or setlocal /?) actually explains the situation pretty well. The only thing that is not obvious is that "localization of environment changes" doesn't just include environment variables, but also includes the current directory, and the delayed expansion and extensions states. There may be more, but I can't think of it at the moment.

The thing that is tripping you up is actually explained fairly well: "When the end of a batch script is reached, an implied ENDLOCAL is executed for any outstanding SETLOCAL commands issued by that batch script." Not stated is that the same is true for called subroutines.

When your batch script ends, the implicit ENDLOCAL "erases" the effect of your CHDIR. Your explicit ENDLOCAL in your second code gets you back to the root environment, so your CHDIR is then preserved.


Update

The current directory is not an environment variable, even though you can normally get the current value using %CD%. You can prove it by tring SET CD - it will probably give you "Environment variable CD not defined". If you explicitly define your own true CD variable using set "CD=some value", then %CD% will return the value you assigned, and not the current directory.

The original SETLOCAL command did not control delayed expansion or extensions back in the old COMMAND.COM days. The Enable/Disable DelayedExpansion and Enable/Disable Extensions options were added when CMD.EXE was introduced. It's just how MS decided to implement the feature. It didn't have to be that way. In many ways it is unfortunate that you cannot control those states without SETLOCAL/ENDLOCAL. I often wish I could enable or disable delayed expansion without localizing the environment.

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • There are two key points: the current directory is an environment variable and the implied ENDLOCAL at the end of the script, which returns the original environment. Now that I better understand setlocal, I am not so clear why enabledelayedexpansion has to be done with a setlocal. It's another question, but it explains why I was lost : my experience with setlocal was that it is used to change how variables are evaluated and I don't see the link. –  Nov 22 '13 at 21:40
  • Good update. I wasn't thinking that the current directory %__CD__% can be changed with SET, but only that it is a variable that is a part of the environment. –  Nov 22 '13 at 23:01
  • The great additional point, which is in fact the source of my question, is the statement that in principle we should not need to localize the environment to enable delayed expansion. –  Nov 22 '13 at 23:07
  • 1
    @Dominic108 - I had forgotten about `%__CD__%`, the only difference from `%CD%` being the trailing \. Also, you can define a true `__CD__` environment variable, but you cannot access it - `%__CD__%` always returns the current directory with trailing \, even if `__CD__` variable is defined. – dbenham Nov 22 '13 at 23:28
  • I know we cannot change it. Still, I am not sure why you insist on the viewpoint that it is not a part of the environment. It is analogous to the ROOTDOCUMENT in a web script : relative paths are relative to it, etc. The ROOTDOCUMENT also cannot be changed, not even with the equivalent of a cd command. We still consider it as an environment variable : http://www.cgi101.com/class/ch3/text.html . –  Nov 22 '13 at 23:37
  • @Dominic108 - I'm not saying its not part of the environment. I'm saying its not an environment variable. Type `SET` to list all the defined variables, and it will not be there. I suppose it all depends on how you define the term "environment variable". – dbenham Nov 22 '13 at 23:42
  • This distinction is nitpicking. I understood you right from the start, but the function SET is irrelevant here. –  Nov 23 '13 at 00:05
  • 1
    Good explanation, but the point about `"localization of environment changes"` also affects the echo state seems to be wrong – jeb Nov 23 '13 at 12:33
1

I ran into this behavior writing a batch file to change directories based on command-line parameters and a bunch of internal logic.

One way to use setlocal and change directories is to call the batch file recursively, return the destination path as a string, and have the top-level caller cd to it.

In this example the batch file takes either p or px on the command line to change to a particular directory:

@echo off
if not "%1"=="recurse" (
    for /f "delims=" %%i in ('%0 recurse %1') do cd %%i
    exit /b
)
setlocal
rem Do things here with "local" variables
if "%2"=="p" (
    echo "c:\program files"
) else if "%2"=="px" (
    echo "c:\program files (x86)"
)

Notes:

  • I chose the string "recurse" arbitrarily.
  • delims is set to nothing so it doesn't break the returned string on spaces in the path.
  • %0 returns the path to the batch file to recurse.

Thanks to SO Batch equivalent of Bash backticks.

Community
  • 1
  • 1
User5910
  • 463
  • 5
  • 13