Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

implicit "popd" on batch exit

Is there a way to undo all pushd at the end of script. What I have is:

pushd somwhere
rem doing stuff
goto end

:end
popd
goto :EOF

What I'd like to have is:

setlocal
pushd somwhere
rem doing stuff
goto :EOF

But that doesn't work, the directory stays "pushd". Another try where I can't say how many pushd will occur would be:

set CurrentDir=%CD%
rem doing a lot of pushd and popd
pushd somwhere

:POPBACK
if /i not "%CD%" == "%CurrentDir%" popd & goto POPBACK

But that looks like I can easily get stuck at :POPBACK. Adding a counter to exit that loop is a bit uncertain at which dir I end up.

In that context I'd like to know if a can see the stack of pushd directories. The only thing I found was $+ in prompt $P$+$G which adds a '+' for each pushd directory e.g. D:\CMD++>. But I can't see a way how to make use of that.

EDIT: I just noticed there is something wrong with the $+ of prompt that confused me. The setlocal in the example above actually makes the script return to the initial directory, but the prompt shows a '+' indicating a missing popd.

D:\CMD>test.cmd
D:\CMD>prompt $P$+$g
D:\CMD>setlocal
D:\CMD>pushd e:\DATA
e:\DATA+>goto :EOF
D:\CMD+>popd
D:\CMD>

EDIT: Due to the hint to the difference between local environment and directory stack I expanded the example form above, pushing to C:\DATA before calling the script. the script returns to where it has been called from (C:\DATA). So far so good. The first popd shows no effect as it removes the last pushd from stack but not changing the directory, since the directory change has been undone by the (implicit) endlocal. The second popd returns to initial directory as expected.

D:\CMD>pushd C:\DATA
C:\DATA+>d:\cmd\test.cmd
C:\DATA+>prompt $P$+$g
C:\DATA+>setlocal
C:\DATA+>pushd e:\DATA
e:\DATA++>goto :EOF
C:\DATA++>popd
C:\DATA+>popd
D:\CMD>
like image 276
ooLi Avatar asked Dec 07 '25 19:12

ooLi


1 Answers

The pushd command without any arguments lists the contents of the directory stack, which can be made use of, by writing the list with output redirection > to a (temporary) file, which is then read by a for /F loop, and to determine how many popd commands are necessary.

In a batch-file:

pushd > "tempfile.tmp" && (
    for /F "usebackq delims=" %%P in ("tempfile.tmp") do popd
    del "tempfile.tmp"
)

Directly in cmd:

pushd > "tempfile.tmp" && (for /F "usebackq delims=" %P in ("tempfile.tmp") do @popd) & del "tempfile.tmp"

Note, that you cannot use for /F "delims=" %P in ('pushd') do ( … ), because for /F would execute pushd in a new instance of cmd.exe, which has got its own new pushd/popd stack.


One way to prevent residues from the pushd/popd stack is to simply avoid pushd and to use cd /D instead within a localised environment (by setlocal/endlocal in a batch file), because, unlike popd, endlocal becomes implicitly executed whenever necessary (even if the batch file contains wrong syntax that leads to a fatal error that aborts execution, like rem %~). This of course only works when there are no UNC paths to be handled (that is, to be mapped to local paths).


It is advisable to use pushd/popd only in a limited code section with out any branches (by if, goto, etc.). Additionally, ensure to account for potential errors (using conditional execution here):

rem // First stack level:
pushd "D:\main" && (
    rem // Second stack level:
    pushd "D:\main\sub" && (
        rem /* If `D:\main\sub` does not exist, `pushd` would obviously fail;
        rem    to avoid unintended `popd`, use conditional execution `&&`: */
        popd
    )
    rem // We are still in directory `D:\main` here as expected.
    popd
)
like image 55
aschipfl Avatar answered Dec 10 '25 11:12

aschipfl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!