批量退出时隐式“popd”

问题描述 投票:0回答:3

有没有办法撤消脚本末尾的所有

pushd
。我拥有的是:

pushd somwhere
rem doing stuff
goto end

:end
popd
goto :EOF

我想要的是:

setlocal
pushd somwhere
rem doing stuff
goto :EOF

但这不起作用,目录保持“pushd”状态。另一种我无法说出会发生多少个

pushd
的尝试是:

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

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

但是看起来我很容易陷入

:POPBACK
。添加一个计数器来退出该循环有点不确定我最终会在哪个目录。

在这种情况下,我想知道 a 是否可以看到 Pushd 目录的堆栈。我唯一发现的是

$+
中的
prompt $P$+$G
,它为每个 Pushd 目录添加了一个“+”,例如
D:\CMD++>
。但我看不出如何利用它。

编辑: 我刚刚注意到

$+
prompt
有问题,这让我很困惑。 上例中的
setlocal
实际上让脚本返回到初始目录,但是提示符显示一个“+”,表示缺少
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>

编辑: 由于提示本地环境和目录堆栈之间的差异,我扩展了上面的示例表单,在调用脚本之前推送到 C:\DATA。脚本返回到调用它的位置 (C:\DATA)。到目前为止,一切都很好。第一个

popd
没有显示任何效果,因为它从堆栈中删除了最后一个
pushd
,但没有更改目录,因为目录更改已被(隐式)endlocal撤消。第二个
popd
按预期返回到初始目录。

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>
windows batch-file cmd pushd
3个回答
5
投票

不带任何参数的

pushd
命令列出了目录堆栈的内容,可以通过使用输出重定向
>
将列表写入(临时)文件来使用该内容,然后由一个
for /F
循环
,并确定需要多少个
popd
命令

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

直接在中:

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

请注意,您不能使用

for /F "delims=" %P in ('pushd') do ( … )
,因为
for /F
会在
pushd
 的新实例中执行 
cmd.exe
,该实例拥有自己的新
pushd
/
popd
堆栈。


防止

pushd
/
popd
堆栈残留的一种方法是简单地避免
pushd
并在本地化环境中使用
cd /D
(通过
setlocal
/
endlocal
)批处理文件),因为与
popd
不同,
endlocal
会在必要时隐式执行(即使批处理文件包含错误的语法,导致中止执行的致命错误,如
rem %~
)。当然,这仅在没有要处理的 UNC 路径(即映射到本地路径)时才有效。


建议仅在没有任何分支的有限代码段中使用

pushd
/
popd
(通过
if
goto
等)。此外,请确保考虑到潜在的错误(此处使用条件执行):

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
)

3
投票

这里有两点:

1。 Setlocal/Endlocal 命令保存/恢复当前目录!

这意味着当执行

Endlocal
命令时,当前目录将返回到执行上一个
Setlocal
命令时所在的位置。无论
Endlocal
是显式执行,还是通过终止被调用的子例程(通过
exit /B
goto :EOF
命令)隐式执行,都会发生这种情况。

此行为可以用来解决您的问题,因为执行 endlocal 命令时,当前目录的

所有更改
都会恢复。那就是:

setlocal
cd somwhere
rem doing stuff
goto :EOF

但是请注意,此行为pushd/popd命令

相关!您不能将此功能与 
pushd/popd
 命令混合使用。您需要完成多次测试,直到理解这两个功能之间的交互机制。

这是

此处讨论的未记录行为。


2。当没有之前的 PUSHD 时,POPD 命令设置退出代码

如下面

此答案的“退出代码管理”部分所述,有一些命令在发生错误时不会设置 ErrorLevel,而是设置一个我们可以称为“退出代码”的内部值。该值可以通过 && || 构造进行测试,如下所示:

command && Then command when Exit Code == 0 || Else command when Exit code != 0

POPD
是此类命令之一(在

表 5

 中进行了描述),因此我们可以使用此功能多次执行 
POPD,直到之前执行的没有匹配的 PUSHD
@echo off
setlocal

echo Enter in:
for /L %%i in (1,1,%1) do echo %%i & pushd ..

echo Leave out:
set "n=0"
:nextLevel
popd || goto exitLevels
   set /A n+=1
   echo %n%
   goto nextLevel
:exitLevels

echo Done

底线:简单的
popd && goto POPBACK
线就可以解决您的问题...


在我自己在一条现已删除的评论中提醒

0
投票
以及@Aacini发布的答案之后,又一个版本

@echo off
pushd "somewhere"
pushd "somewhere else"

:pop
popd && (echo %cd% & goto :pop) || goto :EOF

其中
echo %cd%
只会显示堆栈中的每个项目,因此可以删除并简单地变为:

:pop
popd && goto :pop || goto :EOF


© www.soinside.com 2019 - 2024. All rights reserved.