退出批处理中的for循环

5

如果计数器j达到0,我希望退出for循环。

set /a j=3
for /R c:\dsd_imports\ad_and_deal %%i IN (*.*) DO (
  MDI_import_ad_command.bat C:\DSD_IMPORTS\AD_AND_DEAL\%%~nxi
  MOVE %%i c:\dsd_imports\ad_and_deal\in_process
  set /a j=j-1
  if j == 0
     break
)

3
在批处理中,break命令没有任何作用。请使用goto命令。 - SomethingDark
在批处理中,if语句必须写在一行上,并且变量必须使用%%进行展开。if j == 0是一个语法错误。 - phuclv
谢谢,我只是不熟悉if语句的语法。您能提供确切的语法吗?是这样的吗:if %%j=0 goto Here。 - Steven Miller
2个回答

5
这里是您的批处理代码,已进行改写并添加注释:
@echo off
rem Define environment variable FileCount with value 3.
set "FileCount=3"

rem Push path of current directory on stack and make specified directory
rem the current directory for everything up to command POPD.
pushd C:\dsd_imports\ad_and_deal

rem Process in directory specified above all non hidden files.

rem For each file call another batch file with name of current file.
rem Then move current file to subdirectory in_process and decrement
rem the file count variable by 1.

rem Enable delayed expansion which results also in creating a copy of
rem all environment variables and pushing current directory once again
rem on stack.

rem Run a string comparison (a few microseconds faster than an integer
rem comparison as environment variables are always of type string) to
rem determine if 3 files were already processed in which case the loop
rem is exited with a jump to a label below the loop.

rem In any case the previous environment must be restored with command
rem ENDLOCAL before the batch file execution continues on label Done or
rem with loop execution.

for %%I in (*) do (
    call MDI_import_ad_command.bat "%%I"
    move /Y "%%I" in_process\
    set /A FileCount-=1
    setlocal EnableDelayedExpansion
    if "!FileCount!" == "0" endlocal & goto Done
    endlocal
)

rem Delete the environment variable FileCount as no longer needed.
rem Then pop the previous current directory path from stack and make
rem this directory again the current directory for rest of batch file.

:Done
set "FileCount="
popd

我希望你不需要递归处理 C:\dsd_imports\ad_and_deal 目录下的文件,因为这样会导致已经在子目录 in_process 中处理过的文件再次被处理。

为了理解所使用的命令及其工作原理,请打开命令提示窗口,执行以下命令,并仔细阅读每个命令所显示的所有帮助页面。

  • call /?
  • echo /?
  • endlocal /?
  • goto /?
  • if /?
  • move /?
  • popd /?
  • pushd /?
  • rem /?
  • set /?
  • setlocal /?

关于使用 IF 比较值的额外信息

IF 等于运算符 == 总是进行字符串比较,而运算符 EQU 首先尝试整数比较,如果无法进行整数比较,则也进行字符串比较,可以通过以下方式证明:

@echo off
if 00 == 0  (echo 00 is equal 0 on using ==)  else (echo 00 is different 0 on using ==)
if 00 EQU 0 (echo 00 is equal 0 on using EQU) else (echo 00 is different 0 on using EQU)

执行后的输出为:
00 is different 0 on using ==
00 is equal 0 on using EQU

在以上批处理代码中,参数!FileCount!0周围的双引号可以被安全地去掉,这并非总是成立,但在这里是可以的。
我添加了双引号是为了让每个人都清楚,因为两个参数的双引号也会被比较。
例如,IF==运算符可以用下面的C代码编写:
#include <stdio.h>
#include <string.h>

int main(int argc, char* args[])
{
    if(argc != 3)
    {
        puts("Error: This compare demo requires exactly two parameters.");
        return 2;
    }

    /* Note: The startup code added by used compiler to executable being
             executed before calling function main removes most likely
             the surrounding double quotes on the argument strings.
             Specify the arguments in form \"value\" to compare
             the arguments with surrounding double quotes. */
    printf("Compare %s with %s.\n",args[1],args[2]);

    if(strcmp(args[1],args[2]) == 0)
    {
        puts("The strings are equal.");
        return 0;
    }

    puts("The strings are different.");
    return 1;
}

因此,使用"!FileCount!" == "0"!FileCount! == 0的区别在于,strcmp必须比较4个字节和终止的空字节,而不是2个字节。这并没有实际的区别,可以通过修改上面的代码并在循环中运行strcmp,例如100,000,000次,并测量这些执行时间来证明这一点,这些比较在核心/处理器的缓存中进行。
FOR 循环内部而不是外部使�� SETLOCAL ENDLOCAL 的用法会影响批处理文件执行完成所需的时间,因为这两个命令所执行的所有操作都在该答案的下半部分描述。
因此,以下方式肯定更快:
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "FileCount=3"
cd /D C:\dsd_imports\ad_and_deal

for %%I in (*) do (
    call MDI_import_ad_command.bat "%%I"
    move /Y "%%I" in_process\
    set /A FileCount-=1
    if !FileCount! == 0 goto Done
)

:Done
rem Add here other commands.

rem This command destroys the local copy of the environment variables which
rem means FileCount does not exist anymore if it did not exist before running
rem this batch file. It also restores the previous current directory changed
rem above with command CD.
endlocal

但是,如果FOR找到的任何文件名中包含一个或多个感叹号,则这个更快的批处理代码将无法正常工作。原因是文件名中的第一个!被解释为延迟环境变量引用的开始,如果没有第二个!,则该变量引用将从文件名中删除,并且在调用其他批处理文件之前,%%I扩展时这两个!之间的字符串将被替换为最可能为空的内容。
可以通过运行此批处理文件来查看这种始终不想要的行为:
@echo off
echo File !1.txt>"%TEMP%\File !1.txt"
echo File !2!.txt>"%TEMP%\File !2!.txt"
echo File !XYZ! abc!.txt>"%TEMP%\File !XYZ! abc!.txt"

echo With delayed expansion disabled as by default:
echo/

for %%I in ("%TEMP%\File *") do echo "%%~nxI"

echo/
echo With delayed expansion enabled explicitly:
echo/

setlocal EnableExtensions EnableDelayedExpansion
for %%I in ("%TEMP%\File *") do echo "%%~nxI"
endlocal

del "%TEMP%\File *" >nul
echo/
pause

这个批处理文件在Windows XP和Windows 7上执行的输出结果是:
With delayed expansion disabled as by default:

"File !1.txt"
"File !2!.txt"
"File !XYZ! abc!.txt"

With delayed expansion enabled explicitly:

"File 1.txt"
"File .txt"
"File  abc.txt"

为了完整起见,以下是操作符 EQU 的等效C 代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* args[])
{
    char* psEnd;
    long int lArgument1;
    long int lArgument2;

    if(argc != 3)
    {
        puts("Error: This compare demo requires exactly two parameters.");
        return 2;
    }

    /* Note: The startup code added by used compiler to executable being
             executed before calling function main removes most likely
             the surrounding double quotes on the argument strings.
             Specify the arguments in form \"value\" to compare
             the arguments with surrounding double quotes. */
    printf("%s EQU %s\n",args[1],args[2]);

    lArgument1 = strtol(args[1],&psEnd,0);
    if(*psEnd != '\0')
    {
        if(strcmp(args[1],args[2]) == 0)
        {
            puts("The strings are equal.");
            return 0;
        }
        puts("The strings are different.");
        return 1;
    }

    lArgument2 = strtol(args[2],&psEnd,0);
    if(*psEnd != '\0')
    {
        if(strcmp(args[1],args[2]) == 0)
        {
            puts("The strings are equal.");
            return 0;
        }
        puts("The strings are different.");
        return 1;
    }

    if(lArgument1 == lArgument2)
    {
        printf("The integers %ld and %ld are equal.\n",lArgument1,lArgument2);
        return 0;
    }
    printf("The integers %ld and %ld are different.\n",lArgument1,lArgument2);
    return 1;
}

在比较使用EQU操作符引起的整数比较和使用==操作符进行字符串比较时,可以在此处看到更多CPU指令被执行用于使用EQU操作符。通过在单步模式下运行应用程序,甚至可以清楚地看到在批处理文件中进行整数比较所需的处理器指令要比字符串比较多得多,包括标准库函数strcmpstrtol

这个用C编写的第二个应用程序完美地演示了在批处理文件中使用1或多个前导零的数字与EQU操作符进行值比较或在算术表达式中使用它们时经常出现的意外情况,即在set /A后的字符串中。

例如,将上面的代码编译为equ.exe并运行以下命令:

@echo off
equ.exe \"08\" \"08\"
equ.exe 08 8
equ.exe 14 14
equ.exe 014 014
equ.exe 0x14 0x14
equ.exe 0x14 20
equ.exe 0x14 \"20\"

我使用gpp 4.7.3(DJGPP包)编译的 equ.exe 的结果是:

"08" EQU "08"
The strings are equal.
08 EQU 8
The strings are different.
14 EQU 14
The integers 14 and 14 are equal.
014 EQU 014
The integers 12 and 12 are equal.
0x14 EQU 0x14
The integers 20 and 20 are equal.
0x14 EQU 20
The integers 20 and 20 are equal.
0x14 EQU "20"
The strings are different.

第一个比较 "08" EQU "08" 执行为字符串比较,因为两个参数都有 "
第二个比较 08 EQU 8 最终也被执行为字符串而不是整数比较,因为第一个参数以前导零开头,所以被函数 strtol 解释为八进制数字,其中第三个参数 base0,这是无效的,因为其中包含数字 8。有效的八进制数字只有 0-7 范围内的数字。因此,字符串转换为长整型失败,因此对于 088 进行了字符串比较。
第三个比较 14 EQU 14 被执行为十进制整数比较。
第四个比较 014 EQU 014 也被执行为整数比较,但两个数字都被解释为八进制。
第五个比较 0x14 EQU 0x14 再次被执行为整数比较,但两个数字都被解释为十六进制,解释为两次输出数字 20
因此,在批处理文件中尽可能使用运算符 == 对两个值进行字符串比较,并且可以选择是否使用双引号将其明确括起来。
在批处理文件中测量 ==EQU 的时间差异是完全无用的,因为在执行 IF 条件之前,Windows 命令解释器解析批处理文件中的命令行所需的时间比比较本身需要的时间多得多,这一点可以通过内部使用的编译的 C/C++ 代码进行证明。
当然,这也意味着对于用户而言,使用 ==EQU 并不会在完成批处理文件所需的总时间方面产生真正的差异。但是出于其他原因,使用 ==EQU 经常会有所不同。

出于好奇,我在我的64位Win 7系统上对“if“!FileCount!“ ==”0“endlocal & goto Done”的引号进行了100万次基准测试。结果是相同的(121,000毫秒)。这可能是预期的,因为这是一个有引号或没有引号的字符串比较。因此,我将非引用版本“==”替换为“EQU”,并再次获得了相同的结果。我测量到的唯一显着差异是使用“!FileCount!”比使用“%FileCount%”慢了10%(同样,计时一百万次)。看起来那个叹号是否存在会产生额外的影响。 - Paul Houle

3

goto语句将退出FOR循环。此外,您必须使用延迟的环境变量扩展来测试循环控制变量,因为FOR块在执行之前完全%var%扩展。可以像这样:

setlocal enabledelayedexpansion
set /a j=3
for /R c:\dsd_imports\ad_and_deal %%i IN (*.*) DO (
  rem ... work that needs to be done ...
  set /a j=j-1
  if !j!==0 goto exit_for
)
:exit_for

1
不使用call运行另一个.bat文件会破坏批处理程序。 - user6811411
我从问题中复制了(并忽略了)那个.bat调用,添加了“for break”示例逻辑。我进一步简化了响应。 - Paul Houle

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