SETLOCAL ENABLEDELAYEDEXPANSION causes CD and PUSHD to not persist - batch-file

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.

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.

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
>

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)

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

Related

Trying use a batch file to delete filenames appended with numbers

I"m trying to delete these files with numbers appended to them using a for loop in a Windows batch file.
My problem is that I can't seem to build the filename strings using the for loop, despite several attempts at doing this.
Below is the snippet of my code that I"m trying to run with. As you can see, I've got 4 files named file0.txt, file1.txt, file2.txt and file3.txt in some nested folder, and I'm trying to delete them using a for loop
Ideally I want to be able to be able to set the limit of the for loop depending on how many files there are. And then I'd want to change the file extension from .txt to whatever to delete other files except the ones I want to keep.
Any help would be appreciated!! Here's the code:
setlocal enabledelayedexpansion
set num=3
set /a forLoopLimit=%num%-1
cd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
setlocal enabledelayedexpansion
echo %x
set y=%x
set filename=file%y%.txt
del %filename% /f /q
echo %filename%
)
cd ..
setlocal enabledelayedexpansion
set num=3
set /a forLoopLimit=%num%-1
cd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
echo %%x
set filename=file%y%.txt
ECHO del file%%x /f /q
echo file%%x
)
cd ..
Problems:
To refer to the metavariable (Loop-control variable) x, you need %%x, not %x within a batch file.
setlocal is not a switch. Each time it is used, it establishes a new frame which is terminated by endlocal or reaching end-of-file.
If you are using delayededexpansion then you need to refer to the variable you are changing using !var!, not %var%. !var! means the changed value, %var% means the value of the variable as it was when the for keyword was encountered.
You don't need to use y in your application. %%x is actually a string, but it will be a numeric string, so it can be used in calculations.
The del command is simply echoed above to allow the command to be displayed - in case there's a code error which might delete unexpectedly.
Basically, you fell into the delayed expansion trap (you enabled delayed expansion, but you didn't use it).
But your task can be done without those variables that need delayed expansion. You can use %%x directly and define the rest outside the loop:
#echo off
set num=3
set /a forLoopLimit=%num%-1
set "filebase=file"
pushd folder
FOR /L %%x IN (0,1,%forLoopLimit%) DO (
ECHO del "%filebase%%%x.txt" /f /q
)
popd
Like Magoo, I disarmed the del command by just echoing it. If the output is what you expect, just remove the ECHO.

Assigning the value in the variable from the FOR loop in batch script

I have written one batch script, to get the all pdf files in the directory including Subfolders, but i want to know is it possible to assign the value of %%x in the some other variable, like set temp = %%~na.
#echo off
setlocal enabledelayedexpansion
setlocal
for /r %%a in (*.pdf) do (
echo %%~na
)
endlocal
Almost exactly as you had it.
#echo off
setlocal enabledelayedexpansion
for /R %%a in (*.pdf) do (
set "var=%%~na"
echo !var!
)
You do not have to endlocal as it will end when the script completes in this case.
Doing setlocal a second time is an issue. You did setlocal at the beginning when you enabled delayedexpansion but that being said, you never used it. Look at setlocal /? you will notice ! is used instead of % in order to tell the system which variables should be used in the delayed environment expansion.

Batch file crashes after if statement

I'm trying to make a linux like terminal with batch. I don't know what's wrong with the code.
It crashes after
Set "word[!i!]=%cmd: ="&Set /A i+=1&Set "word[!i!]=%"
echo %word[1]%
if "%word[1]%==ls" dir.
Here's my code:
#Echo on
pushd C:\
Set "cmd="
For /F "Delims==" %%A In ('Set word[') Do Set "%%A="
set "UserAccount=%username%"
cls
:Loop
SetLocal EnableDelayedExpansion
Set /P "cmd=%UserAccount%#%UserAccount%~%cd%$ "
If Not Defined cmd EndLocal EnableDelayedExpansion & GoTo Loop
Set "i=1"
Set "word[!i!]=%cmd: ="&Set /A i+=1&Set "word[!i!]=%"
echo %word[1]%
if "%word[1]%==ls" dir
if "%word[1]%==cd" goto cd
if "%word[1]%==cd .." cd..
if "%word[1]%==cd ." cd.
GoTo Loop
:cd
if Not Defined %word[2]% echo %cd% & GoTo Loop
cd %word[2]%
EndLocal EnableDelayedExpansion
goto Loop
Probably you've coded
Set "word[!i!]=%cmd: ="&Set /A i+=1&Set "word[!i!]=%"
where you intended
Set "word[!i!]=%cmd: =%"&Set /A i+=1&Set "word[!i!]="
You'd then need to fix
if "%word[1]%==ls" dir
to
if /i "%word[1]%"=="ls" dir
(where the /i has been inserted to make the comparison case-insensitive)
I'd also avoid using cmd as a variable-name since cmd.exe is the command-processor itself.
Also, be aware that setlocal establishes a copy of the current environment which is released by an endlocal. As your code currently stands your setlocals are not balanced by endlocals` so you have an ever-accumulating nested environment arrangement - and the nesting level is limited.
As well as completely overhauling your SetLocal/EndLocal expressions...
Change:
if "%word[1]%==ls"
to
If /I "%word[1]%"=="ls"
Also change:
if "%word[1]%==cd"
to
If /I "%word[1]%"=="cd"
These two are not possible because each are two words, where %word[1]% is just cd
if "%word[1]%==cd .."
if "%word[1]%==cd ."
Also change:
if Not Defined %word[2]%
to
If Not Defined word[2]
Also, I told you when I gave you the code for setting your words, that it depended on your sentences. If you are expecting that entering commands and having the special characters and syntax recognized as individual words with that code then your likely to be disappointed.

cd command does not changed to variable value change directories

Here the value is coming correct in the variable lastoccur and also it is echoed
but the cd command to move to that directory does is seemingly not working. Why is this happening?
#echo off
mode con: cols=157 lines=2500
setlocal enabledelayedexpansion
for /f "tokens=2" %%G in ('p4 client -o 2850NewDev ^| findstr /c:"Root:"') do (
set lastoccur=%%G
)
cd /d !lastoccur!
echo !lastoccur!
PAUSE
UPDATE 1
I changed the code to this:
#echo off
mode con: cols=157 lines=2500
set lastoccur=""
for /f "tokens=2" %%G in ('p4 client -o 2850NewDev ^| findstr /c:"Root:"') do (
set lastoccur=%%G
)
cd %lastoccur%
echo %lastoccur%
PAUSE
Now also cd does not change the directory. I am not seeing any path on the window, just the echoed value.
The problem comes from the line setlocal enabledelayedexpansion.
setlocal creates a local session when it runs. You can think of this as a child cmd.exe process being spawned (it's not, but it acts like one). Everything you do after that line but before a corresponding endlocal (if any) is done in its own compartmentalized environment.
You are changing the directory in the newly-created local environment, but the global environment is not being changed.
You were close with your second attempt, but you cannot use variables that you create inside of code blocks without delayed expansion. You can, however, use the for loop variables to do things.
for /f "tokens=2" %%G in ('p4 client -o 2850NewDev ^| findstr /c:"Root:"') do (
cd /d %%G
echo %%G
)

Batch script, file drag&drop, change extension and reload file

I have a batch script which accepts >=1 files via drag & drop. The full paths are kept in an array and later fed to another program as input. The file name+extension is kept in another array which is then shown to the user.
I am trying to check the file extension and if it is not .bin, automatically rename the file I dragged & dropped to .bin and reload it to be added to the two arrays. How can I do that? I have tried some if commands or %%~xi but usually it doesn't properly detect if it's .bin or not and of course the path is not updated at the array.
#echo off
pushd %~dp0
setlocal enabledelayedexpansion
set /a Count = 0
:START
set file="%~1"
if %file%=="" goto RUN
for %%i in (%file%) do set name=%%~nxi
set /a Count += 1
set [FilePath.!Count!]=%file%
set [FileName.!Count!]=%name%
shift
goto START
:RUN
for /l %%i in (1, 1, %Count%) do (
program.exe -command "![FilePath.%%i]!"
echo.
echo File Name: ![FileName.%%i]!
echo.
pause
cls
if exist file.log del file.log
)
You could just rename it during your array population. There are a few other potential problems with your script (such as pushd to an unquoted directory, delayedexpansion where you don't need it where it will potentially clobber filenames containing exclamation marks, and if %file%=="" should have "%file%" quoted). And there's really no reason to maintain an array of filenames or loop twice. Your script is much more complicated than it needs to be.
#echo off
setlocal
pushd "%~dp0"
for %%I in (%*) do (
if /I not "%%~xI"==".bin" move "%%~fI" "%%~dpnI.bin" >NUL
program.exe -command "%%~dpnI.bin"
echo;
echo File Name: "%%~nxI"
echo;
rem If you want to rename the file back the way it was after running
rem program.exe on it, uncomment the following line:
rem if /I not "%%~xI"==".bin" move "%%~dpnI.bin" "%%~fI" >NUL
pause
cls
if exist file.log del file.log
)

Resources