6

I am trying to use setlocal enabledelayedexpansion and cd together in a batch script, which seems to not persist changes back to shell.

The reason I need setlocal enabledelayedexpansion is that I need the variables in the script to be expanded dynamically upon runtime of the script.

Consider the below sample batch file :

a.bat
================================
setlocal enabledelayedexpansion
cd ..

The above batch file does not migrate to previous directory as expected !

Check this.

Raghav RV
  • 3,510
  • 2
  • 17
  • 26
  • What makes you say that it isn't working? Can you describe your actual problem? – Taylor Hx Oct 07 '14 at 22:25
  • Referring to the thread title mentioning PUSHD, I agree that CD doesn't persist, but pushd DOES persist, doesn't it? Just tested; I did correctly popd from command line after batch completed. (Even a pair of POPD where each worked) – MicrosoftShouldBeKickedInNuts Dec 21 '17 at 18:53

4 Answers4

7

Blorgbeard provided an explanation as to why CD issued after SETLOCAL does not persist after ENDLOCAL (could be explicit or implicit).

Here is an interesting work-around. The PUSHD stack is independent of the environment that is saved/restored by SETLOCAL/ENDLOCAL. So the following simple sequence will preserve the directory change:

@echo off
setlocal
cd somePath
pushd .
endlocal
popd

Not very useful if somePath is constant - you could just as easily issue the CD after the ENDLOCAL. But it can be very useful if somePath is dynamic.

dbenham
  • 119,153
  • 25
  • 226
  • 353
  • Very nice, can we use this to pass a string across an ENDLOCAL? – mosh Jul 27 '17 at 15:51
  • @mosh - Sure, but only if the string is a valid canonical folder path to which the user has access. That does not seem very useful to me. I suppose you could create a temporary folder named the same as your string value in a "standard" location. You could then extract your string value and delete the folder. But that would have significant limitations as to what characters could be used. – dbenham Jul 27 '17 at 17:02
  • @mosh - But it is an interesting concept from the standpoint that it supports the return of multiple strings - one PUSHD/POPD pair per returned value. – dbenham Jul 27 '17 at 17:08
  • @mosh - Or the multiple strings could be returned as a hierarchical chain of folders with a single PUSHD/POPD. – dbenham Jul 27 '17 at 17:13
  • @mosh - But if you are willing to create a temporary folder, then you might as well create a temporary file instead with one value per line. – dbenham Jul 27 '17 at 18:20
  • @dbenham thanks, You can just read/write a string into volatile registry, posting demo code below (it doesn't fit here). – mosh Jul 28 '17 at 09:25
  • 1
    Note that this work-around would need to be done for each drive whose current dir is modified during setlocal, if one wishes other drives to also retain their current dirs when leaving setlocal. Perhaps stating the obvious, the pushd only pushes "." which is the current dir on the current drive at time of execution - ignoring the dir of other drives, who will revert if not attended to. – MicrosoftShouldBeKickedInNuts Dec 21 '17 at 18:39
4

The problem is that setlocal causes any current directory changes to be local to the batch file.

See setlocal /?:

Begins localization of environment changes in a batch file. Environment changes made after SETLOCAL has been issued are local to the batch file. ENDLOCAL must be issued to restore the previous settings. When the end of a batch script is reached, an implied ENDLOCAL is executed for any outstanding SETLOCAL commands issued by that batch script.

Current directory is included in "environment changes".

Try this, notice that it echoes C:\ for %CD% inside the batch, but the current directory is still reset when the batch exits.

[11:42:00.17] C:\temp
> cat test.bat
@echo off
setlocal enabledelayedexpansion
cd ..
echo %CD%
[11:42:19.38] C:\temp
> test.bat
C:\

[11:42:23.00] C:\temp
>
Blorgbeard
  • 93,378
  • 43
  • 217
  • 263
  • 1
    Thanks I understood this better ! So is there any way I could get a cd to persist change outside too, when I need a setlocal so that the variables are expanded dynamically ? – Raghav RV Oct 07 '14 at 22:47
  • You could try explicitly calling `endlocal`, and *then* changing the directory - or changing it before calling `setlocal`. – Blorgbeard Oct 07 '14 at 22:51
  • 2
    @Blorgbeard: Your explanation is wrong. Current directory is **NOT** included into the environment variables, but it is a _dynamic variable_ maintained by cmd.exe in the same way as %DATE%, %TIME%, %RANDOM% and others; to confirm this point, just type: `set`. This behavior of current directory is a very particular one that, as far as I know, is undocumented. See: [this post](http://www.dostips.com/forum/viewtopic.php?f=3&t=4436) – Aacini Oct 08 '14 at 01:34
  • 3
    @Aacini I didn't mean that the current directory was literally an environment variable, just that it's part of what's referred to as "environment changes" in the quoted documentation. – Blorgbeard Oct 08 '14 at 02:07
  • @Blorgbeard: The "environment" is the place where environment variables are stored; there is a ton of information about this point. The `SETLOCAL` description refers to changes in the environment _variables_, unless of course that you know of a site that indicate that the environment may store another data besides variables and what data is this; for example, the current directory. – Aacini Oct 09 '14 at 18:44
0

Here is proof that it works - start the batch file in any folder above the root.

@echo off
setlocal enabledelayedexpansion
echo "%cd%"
cd ..
echo "%cd%"
pause
foxidrive
  • 37,659
  • 8
  • 47
  • 67
0

Saving a string in registry across endlocal. Tested on win7 cmd (skip=2, may differ with different versions of reg.exe)

:: What: stash string in registry from cmd line, across endlocal barrier
:: PS. I added %RANDOM% to Windows-NT and Win95 command.com and still using it.
@echo off

set key="HKCU\Volatile Environment"
set keyname=stash%RANDOM%
set keytype=REG_SZ

setlocal ENABLEDELAYEDEXPANSION

::== Save string
set data_stash=Hello-%DATE%-%TIME%
echo Saving=%data_stash% in key=%keyname%
reg add %key% /v %keyname% /t %keytype% /d "%data_stash%" /f

endlocal

::== Read string
echo reg query %key% /v %keyname%
for /f "tokens=2* skip=2" %%a in ('reg query %key% /v %keyname%') do (
  set data_unstashed=%%b
)
echo Read data_unstashed=%data_unstashed% 

::== Delete stash
reg delete %key%  /v %keyname% /f  

Edit Stephan another way to save variables beyond endlocal:

@echo off
set var1=hello
set var2=world
setlocal 
echo previous: %var1%, %var2%
set var1=HELLO
set var2=WORLD
echo inside:   %var1%, %var2%
endlocal & set "var1=%var1%" & set "var2=%var2%" 
echo after:    %var1%, %var2%

The trick is that due to the parsing the variables are expanded before endlocal is effective, but setted after that.
(sorry for hijacking your answer, but that's also too big for a comment)

Stephan
  • 47,723
  • 10
  • 50
  • 81
mosh
  • 1,166
  • 12
  • 14