批处理中的while循环

65

这是我的需求,在 BACKUPDIR 中,我想要执行 cscript /nologo c:\deletefile.vbs %BACKUPDIR% 直到文件夹中的文件数大于21(countfiles 保存着它)。 这是我的代码:

@echo off
SET BACKUPDIR=C:\test
for /f %%x in ('dir %BACKUPDIR% /b ^| find /v /c "::"') do set countfiles=%%x

for %countfiles% GTR 21 (
cscript /nologo c:\deletefile.vbs %BACKUPDIR%
set /a countfiles-=%countfiles%
)

2
那么你的问题是什么?这个批处理文件能否正常工作?或者你遇到了它无法运行的问题? - schnaader
5个回答

69
set /a countfiles-=%countfiles%

这将把countfiles设置为0。我认为你想将其减1,所以请使用以下代码:

set /a countfiles-=1

我不确定for循环是否有效,最好尝试类似以下的代码:

:loop
cscript /nologo c:\deletefile.vbs %BACKUPDIR%
set /a countfiles-=1
if %countfiles% GTR 21 goto loop

65

可以使用以下命令在 cmd.exe 中模拟 while 循环:

:still_more_files
    if %countfiles% leq 21 (
        rem change countfile here
        goto :still_more_files
    )
例如,下面的脚本:
    @echo off
    setlocal enableextensions enabledelayedexpansion
    set /a "x = 0"

:more_to_process
    if %x% leq 5 (
        echo %x%
        set /a "x = x + 1"
        goto :more_to_process
    )

    endlocal

输出:

0
1
2
3
4
5

针对您的特定情况,我会从以下开始。您最初的描述有点令人困惑。我假设您想在该目录中删除文件,直到剩余20个或更少:

    @echo off
    set backupdir=c:\test

:more_files_to_process
    for /f %%x in ('dir %backupdir% /b ^| find /v /c "::"') do set num=%%x
    if %num% gtr 20 (
        cscript /nologo c:\deletefile.vbs %backupdir%
        goto :more_files_to_process
    )

你所说的完全正确,但是有一个问题就是它进入了一个无限循环,看起来循环内部的计数器应该减少,例如像这样设置 /a "countfiles = countfiles-1"。但是对我来说好像不起作用。 - Hellnar
@Hellnar,由于计算countfiles的for循环语句位于循环内部,因此它再次设置为实际值。我这样做是因为不清楚你的VBScript文件要删除多少个文件。 - paxdiablo
如果你遇到了无限循环,可以将 "@echo off" 替换为 "::@echo off" 并重新运行脚本。这样会输出执行的命令,你就能看到 countfiles 被设置成了什么。 - paxdiablo
它的命名为 while 时能够正常运行,你将它命名为 while1 有什么特殊原因吗? - Noumenon
1
@Noumenon:只是因为我倾向于在代码中使用多个while循环。实际上,你可以几乎称其为任何你想要的名称。但我会使它更加详细以澄清意图。 - paxdiablo
谢谢,如果我把它们都命名为相同的名称,那将是一个陷阱。你能给出一个有帮助的详细标签名称的例子吗? - Noumenon

15

我有一个做这件事的诀窍!

我想出了这个方法,因为在CLI上,不可能使用其他答案中提供的方法,这一直困扰着我。

我把它叫做“Do Until Break”或“无限”循环:

基本示例

FOR /L %L IN (0,0,1) DO @(
 ECHO. Counter always 0, See "%L" = "0" - Waiting a split second&ping -n 1 127.0.0.1>NUL )

这真的是一个无限循环!

这对于在 CMD 窗口中监视某些内容非常有用,允许您在完成后使用 CTRL+C 来中断它。

想要计数器吗?

可以使用 SET /A 或修改 FOR /L 循环进行计数,仍然是无限循环(请注意,这两种方法都存在32位整数溢出的问题)

SET /A 方法:

FOR /L %L IN (0,0,1) DO @(
 SET /A "#+=1"&ECHO. L Still equals 0, See "%L = 0"!  - Waiting a split second &ping -n 1 127.0.0.1>NUL  )

原生的 FOR /L 循环计数器:

FOR /L %L IN (-2147483648,1,2147483648) DO @(
 ECHO.Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL  )

计算4294967295个集合并显示L的当前值:

FOR /L %L IN (1,1,2147483648) DO @(
 (
  IF %L EQU 0 SET /A "#+=1">NUL
 )&SET /A "#+=0"&ECHO. Sets of 4294967295 - Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL )

然而,如果:

  • 您有兴趣查看监视器的输出,并担心在完成后检查之前我会通过9999行缓冲区限制。
  • 一旦我正在监视的事物完成,您想要采取一些额外的行动。

为此,我确定了如何使用几种方法有效地中断FOR循环,从而将其转换为“DO WHILE”或“DO UNTIL”循环,在CMD中否则极为缺乏。

注意:大多数情况下,循环将继续迭代超过您检查的条件,通常这是一种想要的行为,但不适用于我们的情况。

将“无限循环”转换为“DO WHILE”/“DO UNTIL”循环

更新:由于希望在CMD脚本(并使它们持久存在!)以及CLI中使用此代码,并且考虑是否可能存在“更正确”的实现方法,我建议使用新方法!

新方法(可在CMD脚本内部使用而不退出脚本):

FOR /F %%A IN ('
  CMD /C "FOR /L %%L IN (0,1,2147483648) DO @( ECHO.%%L & IF /I %%L EQU 10 ( exit /b  ) )"
') DO @(
  ECHO %%~A
)

在命令行界面:

FOR /F %A IN ('
  CMD /C "FOR /L %L IN (0,1,2147483648) DO @( ECHO.%L & IF /I %L EQU 10 ( exit /b  ) )"
') DO @(
  ECHO %~A
)

原始方法(在命令行界面上可以正常工作,但会终止脚本执行。)

FOR /L %L IN (0,1,2147483648) DO @(
  ECHO.Current value of L: %L - Waiting a split second &ping -n 1 127.0.0.1>NUL&(
    IF /I %L EQU 10 (
      ECHO.Breaking the Loop! Because We have matched the condition!&DIR >&0
    )
  )
) 2>NUL

旧的帖子内容

偶然间,我发现了一些退出循环的方法,这些方法不会在尝试做其他事情时关闭CMD提示符,这给了我一个想法。

注意:

虽然 ECHO.>&3 >NUL 在某些情况下对我起作用,但是多年来我一直在尝试和测试,发现 DIR >&0 >NUL 更加稳定可靠。

从现在开始,我将重新编写这个答案,使用更好的方法:上面提到的 DIR >&0 >NUL

改进后的帖子内容如下:

DIR >&0 >NUL

>NUL是可选的,我只是更喜欢不输出错误。

我尽可能地喜欢匹配inLine,就像您在这个经过清理的示例命令中看到的一样,我用它来监视我们VNX上的LUN迁移。

for /l %L IN (0,0,1) DO @(
   ECHO.& ECHO.===========================================& (
     [VNX CMD] | FINDSTR /R /C:"Source LU Name" /C:"State:" /C:"Time " || DIR >&0 >NUL
   ) & Ping -n 10 1.1.1.1 -w 1000>NUL )

此外,我在自己的笔记中找到了另一种方法,我刚刚重新测试了一下,确认它在CLI中同样有效。

显然,在我第一次发布帖子时,我发布了一个较旧的版本,而不是两个更好的新版本:

在这种方法中,我们使用EXIT /B退出For循环,但我们不想退出CLI,因此我们将其包装在CMD会话中:

FOR /F %A IN ('CMD /C "FOR /L %L IN (0,1,10000000) DO @( ECHO.%L & IF /I %L EQU 10 ( exit /b  ) )" ') DO @(ECHO %~A)

由于循环本身在CMD会话中发生,我们可以使用EXIT /B来退出循环的迭代,而不会丢失CMD会话,并且无需等待循环完成,这与其他方法类似。

我敢说,对于想要在CLI中中断for循环的情况,这种方法很可能是“预期”的方法,因为使用CMD会话也是CLI中获取延迟扩展工作的唯一方法,而此类行为显然是留下CMD会话的预期工作流程。

即:微软明显努力使CMD Exit /B For循环以这种方式运行,而使用我的另一种方法,则依赖于意外创建恰到好处的错误,以便在不让循环完成处理的情况下将您踢出循环,我仅偶然发现这种方法,并且似乎只在使用DIR命令时可靠地工作,这相当奇怪。

因此,我认为最好使用方法2:

FOR /F %A IN ('CMD /C "FOR /L %L IN (0,1,10000000) DO @( ECHO.%L & IF /I %L EQU 10 ( exit /b  ) )" ') DO @(ECHO %~A)

虽然我怀疑方法1会略微更快:

FOR /L %L IN (0,1,10000000) DO @( ECHO.%L & IF /I %L EQU 10 ( DIR >&) >NUL ) )

无论哪种情况,两者都应该允许您需要用于目的的DO-While循环。


循环语句,如For /L %L in (0,1, BigNumber)或无限循环,如For /L %L in (0,0,1),极其低效。 - elpezganzo
@prampe A) For /L %_ IN (0,1,2147483648) DO [something] For /L %_ IN (0) DO [something] (或者For /L %_ IN (0,0,1) DO [something] )都是无限循环。(末尾的大数字是复制粘贴的痕迹。)话虽如此,甚至不需要与您就“哪种循环方法最好”进行辩论——这是唯一可以直接在CLI上工作的方法。此外,等待单独进程完成并退出循环的无限循环将需要超时以避免不必要地消耗CPU周期。 - Ben Personick

10
@echo off

set countfiles=10

:loop

set /a countfiles -= 1

echo hi

if %countfiles% GTR 0 goto loop

pause

在第一个"set countfiles"中,你看到的10是循环次数。而"echo hi"是你希望循环的内容。 ...我迟到了5年。

2

这对我非常有用,我使用以下方式将用户添加到活动目录中:

:: This file is used to automatically add list of user to activedirectory
:: First ask for username,pwd,dc details and run in loop
:: dsadd user cn=jai,cn=users,dc=mandrac,dc=com -pwd `1q`1q`1q`1q

@echo off
setlocal enableextensions enabledelayedexpansion
set /a "x = 1"
set /p lent="Enter how many Users you want to create : "
set /p Uname="Enter the user name which will be rotated with number ex:ram then ram1 ..etc : "
set /p DcName="Enter the DC name ex:mandrac : "
set /p Paswd="Enter the password you want to give to all the users : "

cls

:while1

if %x% leq %lent% (

    dsadd user cn=%Uname%%x%,cn=users,dc=%DcName%,dc=com -pwd %Paswd%
    echo User %Uname%%x% with DC %DcName% is created
    set /a "x = x + 1"
    goto :while1
)

endlocal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接