退出批处理文件中的 FOR 循环?

Posted

技术标签:

【中文标题】退出批处理文件中的 FOR 循环?【英文标题】:Exiting out of a FOR loop in a batch file? 【发布时间】:2011-10-07 09:54:38 【问题描述】:

为什么这个批处理文件永远不会跳出循环?

For /L %%f In (1,1,1000000) Do @If Not Exist %%f Goto :EOF

Goto :EOF 不应该跳出循环吗?

编辑:

我想我应该更明确地问... 如何我可以跳出循环?

【问题讨论】:

你指的是DOS批处理文件还是其他批处理文件?另外,你是如何运行它的? 为什么%%f 不存在? 【参考方案1】:

基于Tim's second edit 和this page,您可以这样做:

@echo off
if "%1"=="loop" (
  for /l %%f in (1,1,1000000) do (
    echo %%f
    if exist %%f exit
  )
  goto :eof
)
cmd /v:on /q /d /c "%0 loop"
echo done

This page 建议一种在循环中使用 goto 的方法,看起来它确实有效,但在大循环中需要一些时间。所以在内部,它在执行 goto 之前完成了循环。

【讨论】:

请勿在批处理文件中使用exit,除非您确实想关闭当前会话。如果您从命令提示符运行批处理,那就不好了。 批处理文件作为一个整体可以在命令提示符下完美运行。这就是为什么我使用cmd /c 来调用退出的部分。 啊,没有注意到那里的cmd 调用。不过,感觉很脏。 @joey exit /b 将只退出批处理文件而不是 cmd 窗口。所以我想 exit /b 或 goto 很好【参考方案2】:

您可以简单地使用echo on,您会看到goto :eof 甚至exit /b 无法按预期工作。

循环内的代码不再执行,但循环将所有数字展开到末尾。 这就是它如此缓慢的原因。

退出 FOR /L 循环的唯一方法似乎是 exit 的变体,就像 Wimmel 的示例一样,但这不是很快,对于访问循环中的任何结果也不是很有用。

这显示了 10 个扩展,但没有一个会被执行

echo on
for /l %%n in (1,1,10) do (
  goto :eof
  echo %%n
)

【讨论】:

这看起来像是问题的正确答案。 goto 具有预期的最终结果,但脚本解释器以意想不到的方式效率低下。【参考方案3】:

我的回答 使用嵌套的for 循环为for /l 循环提供断点。

for %%a in (0 1 2 3 4 5 6 7 8 9) do (
   for %%b in (0 1 2 3 4 5 6 7 8 9) do (
      for /l %%c in (1,1,10) do (
         if not exist %%a%%b%%c goto :continue
      )
   )
)
:continue

说明 必须对代码进行重大调整才能正确使用嵌套循环。例如,写入的内容将有前导零。 “常规”for 循环可以使用简单的goto 命令立即断开,而for /l 循环则不能。此代码最里面的for /l 循环不能立即中断,但每10 次迭代(如所写)后会出现一个整体断点。最里面的循环不一定是 10 次迭代——如果你选择做 100 或 1000 或 2873 (如果数学甚至对循环很重要),你只需要正确地考虑数学。

历史 我在试图弄清楚为什么某个脚本运行缓慢时发现了这个问题。事实证明,我使用了具有传统循环结构的多个循环:

set cnt=1
:loop
if "%somecriteria%"=="finished" goto :continue
rem do some things here
set /a cnt += 1
goto :loop

:continue
echo the loop ran %cnt% times

这个脚本文件有点长,它是从网络驱动器运行的。这种类型的循环文件可能被调用了 20 次,每次它会循环 50-100 次。脚本文件运行时间过长。我有一个好主意,就是尝试将其转换为 for /l 循环。需要的迭代次数未知,但小于 10000。我的第一次尝试是这样的:

setlocal enabledelayedexpansion
set cnt=1
for /l %%a in (1,1,10000) do (
   if "!somecriteria!"=="finished" goto :continue
   rem do some things here
   set /a cnt += 1
)

:continue
echo the loop ran %cnt% times

启用echo 后,我很快发现for /l 循环仍然执行... 某事 ...而实际上 没有做任何事情。它跑得快得多,但仍然比我想象的要慢。因此,我发现了这个问题,并最终得到了上面介绍的嵌套循环的想法。

旁注 事实证明,只需确保for /l 循环没有任何输出,就可以大大加快它的速度。我能够这样做以显着提高速度:

setlocal enabledelayedexpansion
set cnt=1
@for /l %%a in (1,1,10000) do @(
   if "!somecriteria!"=="finished" goto :continue
   rem do some things here
   set /a cnt += 1
) > nul

:continue
echo the loop ran %cnt% times

【讨论】:

【参考方案4】:

如果您使用 call 而不是 goto ,则不需要单独的批处理文件来使用 exit /b 退出循环

call :loop

echo loop finished

goto :eof

:loop
FOR /L %%I IN (1,1,10) DO (
    echo %%I
    IF %%I==5 exit /b
)

在这种情况下,"exit /b" 将退出 'call' 并从 'call' 之后的行继续 所以输出是这样的:

1
2
3
4
5
loop finished

【讨论】:

你应该用for /L %%f in (1,1,10000000)试试看,它不能解决问题,顺便说一句。 exit /bgoto :eof 完全相同,exit /b 在内部执行 goto :eof 为我工作。在我的情况下,我不能使用 goto 语句,因为我想从函数返回一个值并且 goto 移动到另一个函数,所以我使用了上面的解决方案和 Exit /b。【参考方案5】:

所以我意识到这有点过时了,但是经过多次谷歌搜索后,我找不到满意的答案,所以我想出了自己的解决方案来打破立即停止迭代的 FOR 循环,并认为我会分享的。

它要求循环在一个单独的文件中,并利用 CMD 错误处理中的一个错误,在将 DIR 的 STDOUT 重定向到 STDIN 时立即使循环文件的批处理崩溃。

MainFile.cmd

ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO.
ECHO After LOOP
PAUSE

LOOP.cmd

FOR /L %%A IN (1,1,10) DO (
    ECHO %%A
    IF %%A EQU 3 DIR >&0 2>NUL  )
)

运行时,会产生以下输出。您会注意到,当 %A = 3 时,循环的迭代和执行都会停止。

:>MainFile.cmd

:>ECHO Simple test demonstrating loop breaking.
Simple test demonstrating loop breaking.

:>ECHO.


:>CMD /C Z:\LOOP.cmd

:>FOR /L %A IN (1 1 10) DO (
ECHO %A
 IF %A EQU 3 DIR         1>&0 2>NUL
)

:>(
ECHO 1
 IF 1 EQU 3 DIR          1>&0 2>NUL
)
1

:>(
ECHO 2
 IF 2 EQU 3 DIR          1>&0 2>NUL
)
2

:>(
ECHO 3
 IF 3 EQU 3 DIR          1>&0 2>NUL
)
3

:>ECHO.


:>ECHO After LOOP
After LOOP

:>PAUSE
Press any key to continue . . .

如果您需要保留循环中的单个变量,请让循环 ECHO 变量的结果,并在 MainFile.cmd 中使用 FOR /F 循环来解析 LOOP.cmd 文件的输出。

示例(使用与上述相同的 LOOP.cmd 文件):

MainFile.cmd

@ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
FOR /F "delims=" %%L IN ('CMD /C %~dp0\LOOP.cmd') DO SET VARIABLE=%%L
ECHO After LOOP
ECHO.
ECHO %VARIABLE%
ECHO.
PAUSE

输出:

:>MainFile.cmd

Simple test demonstrating loop breaking.

After LOOP

3

Press any key to continue . . .

如果您需要保留多个变量,您需要将它们重定向到临时文件,如下所示。

MainFile.cmd

@ECHO OFF
ECHO.
ECHO Simple test demonstrating loop breaking.
ECHO.
CMD /C %~dp0\LOOP.cmd
ECHO After LOOP
ECHO.
SET /P VARIABLE1=<%TEMP%\1
SET /P VARIABLE2=<%TEMP%\2
ECHO %VARIABLE1%
ECHO %VARIABLE2%
ECHO.
PAUSE

LOOP.cmd

@ECHO OFF
FOR /L %%A IN (1,1,10) DO (
    IF %%A EQU 1 ECHO ONE >%TEMP%\1
    IF %%A EQU 2 ECHO TWO >%TEMP%\2
    IF %%A EQU 3 DIR >&0 2>NUL
)

输出:

:>MainFile.cmd

Simple test demonstrating loop breaking.

After LOOP

ONE
TWO

Press any key to continue . . .

我希望其他人发现这对于打破循环很有用,否则由于持续迭代而需要很长时间才能退出。

【讨论】:

支持,虽然我现在看到这个后觉得我的眼睛可能在流血 :-) 感谢发布这个! 我还没有尝试过这个(我很快就会尝试),但是由于循环在一个单独的批处理文件中,IF %%A EQU 3 goto :EOF 不是也可以代替IF %%A EQU 3 DIR &gt;&amp;0 2&gt;NUL 工作吗?我的意思是goto :EOF 不会立即结束“LOOP.cmd”的执行吗?【参考方案6】:

作为jebnoted,循环的其余部分被跳过但被评估,这使得FOR解决方案对于这个目的来说太慢了。另一种选择:

set F=1
:nextpart
if not exist "%F%" goto :EOF
echo %F%
set /a F=%F%+1
goto nextpart

在循环中使用它时,您可能需要使用delayed expansion 和call 子例程。

【讨论】:

简单得多。干得好。 这在小脚本文件中是一个不错的选择。对于慢(ish)网络上的大(ish)脚本文件,由于一遍又一遍地读取整个文件,这将很慢。即便如此,它仍然可以正常工作。【参考方案7】:

假设 OP 正在使用 cmd.exe 调用批处理文件,要正确跳出 for 循环,只需转到标签;

改变这个:

For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto :EOF

到这里:

For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto:fileError
.. do something
.. then exit or do somethign else

:fileError
GOTO:EOF

更好的是,添加一些错误报告:

set filename=
For /L %%f In (1,1,1000000) Do(
    set filename=%%f
    If Not Exist %%f set tempGoto:fileError
)
.. do something
.. then exit or do somethign else

:fileError
echo file does not exist '%filename%'
GOTO:EOF

我发现这是一个关于鲜为人知的 cmd.exe/DOS 批处理文件功能和技巧的有用网站:https://www.dostips.com/

【讨论】:

DOS 中没有goto :eoffor /l() 之类的代码块。这些是 Windows cmd 功能。它们是完全不同的环境 @phuclv 我指的是这个ss64.com/nt/for_l.html 和ss64.com/nt/goto.html。您能否详细说明“Windows cmd 功能”的含义,因为 OP 正在询问由 cmd、exe 运行的 BAT 文件。 您说的是“DOS 中的 for 循环”,而 OP 没有使用 DOS,上面的命令也无法在 DOS 中运行 @phuclv 也不知道为什么你认为 DOS 中没有代码块。我有许多带有功能的 DOS 批处理文件,也带有代码块。请参阅***.com/a/4983https://www.dostips.com/…,更完整的参考请参阅 OP 使用精确的 DOS 语言,并且还标记为批处理文件,定义为“批处理文件是包含一系列命令的文本文件,这些命令由 MS-DOS 上的命令解释器执行, IBM OS/2 或 Microsoft Windows 系统。”在我看来,这就像一个 DOS 批处理文件问题。所有其他答案也都作为 DOS 批处理答案回答。【参考方案8】:

对此进行了一些研究,您似乎正在从 1 循环到 2147483647,增量为 1。

(1, 1, 2147483647):第一个数字是开始数字,下一个数字是步骤,最后一个数字是结束数字。

编辑添加

无论任何测试条件如何,循环似乎都会运行完成。我测试过

FOR /L %%F IN (1, 1, 5) DO SET %%F=6

而且它跑得很快。

二次编辑

由于这是批处理文件中唯一的一行,您可以尝试 EXIT 命令:

FOR /L %%F IN (1, 1, 2147483647) DO @IF NOT EXIST %%F EXIT

不过,这也会关闭 DOS 提示窗口。

【讨论】:

是的,但Goto :EOF 早晚不应该跳出循环吗? 取决于是否满足 IF NOT EXIST %%F 条件。 我很确定我的文件夹中没有 任何 名称为 12 等的文件...如果你认为是因为这个。 (顺便说一句,我更改了数字,所以人们不认为这是问题所在。) 我同意它应该(我已经尝试过),但它似乎没有通过 NOT EXIST 检查。我不确定为什么会这样。 它实际上失败了Goto,但我不知道为什么。 (如果您将 Goto 重命名为 Foo,它会抱怨。)

以上是关于退出批处理文件中的 FOR 循环?的主要内容,如果未能解决你的问题,请参考以下文章

为啥即使使用 setlocal enabledelayedexpansion 也无法访问 for 循环(批处理文件)中的局部变量? [复制]

即使在设置 EnableDelayedExpansion [重复] 之后,也无法在批处理文件中的 for 循环内设置变量值

如果超过一分钟的 Windows 批处理文件,则退出循环

批处理文件循环中的算术

如何在批处理文件中的for循环中使用可变长度的子字符串?

如何在批处理文件中使用for循环列出文件夹中所有文件和目录的名称