5

I wan't to leave the for loop if the count j reaches 0.

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
)
Andre Kampling
  • 5,476
  • 2
  • 20
  • 47
Steven Miller
  • 51
  • 1
  • 2

2 Answers2

5

Here is your batch code rewritten and commented:

@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

I hope you don't really need to recursively process files in C:\dsd_imports\ad_and_deal as this would result in processing also the files already processed in subdirectory in_process.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

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

Extra information about comparing values with IF

The IF equal operator == results always in a string comparison while operator EQU first always tries an integer comparison and performs also a string comparison if this is not possible as it can be proven with:

@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)

The output on execution is:

00 is different 0 on using ==
00 is equal 0 on using EQU

In the batch code above the double quotes around the arguments !FileCount! and 0 can be safely removed which is not always the case, but here it is.

I added the double quotes to make it clear for everybody that strings are compared because the double quotes of both arguments are compared as well.

The == operator of IF could be coded in C for example with this code:

#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;
}

So the difference on using "!FileCount!" == "0" versus !FileCount! == 0 is that strcmp has to compare 4 versus 2 bytes including the terminating null byte. This does not make a real difference as it could be proven by modifying the code above and run strcmp for example 100.000.000 times in a loop and measure the execution time for this again and again done compare in caches of the core/processor.

The usage of SETLOCAL and ENDLOCAL within the FOR loop and not outside makes a difference in time required for batch file execution completion because of all the operations done by those two commands as described in bottom half of this answer.

So faster would be definitely:

@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

But this faster batch code does not work if any file found by FOR contains 1 or more exclamation marks. The reason is that first ! in a file name is interpreted as begin of a delayed environment variable reference which is removed from file name if there is no second ! which is interpreted as end of delayed environment variable reference and the string between those two ! in file name would be replaced by most likely nothing on expanding %%I before calling the other batch file.

This always unwanted behavior can be seen by running this batch file:

@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

The output of this batch file executed on Windows XP and Windows 7 is:

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"

For completeness the equivalent C code for operator EQU:

#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;
}

It can be already seen here on comparing this C code for demonstrating EQU behavior with the C code above for demonstrating == behavior that an integer comparison caused by using EQU results in more CPU instructions being executed than on doing a string compare with using == operator. On running the application in single step mode also into the standard library functions strcmp and strtol it is even clearer that the processor has to do many more instructions to run an integer comparison in batch file than a string comparison.

This second application written in C demonstrates perfect what happens often unexpected for batch file writers on using numbers with 1 or more leading zeros in batch file on comparing values with EQU or using them in an arithmetic expression, i.e. in string after set /A.

For example compile above code to equ.exe and run following:

@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\"

The result I get with equ.exe compiled with gpp 4.7.3 (DJGPP package) is:

"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.

The first comparison "08" EQU "08" is executed as string comparison because of " in both arguments.

The second comparison 08 EQU 8 is finally also executed as string and not as integer comparison because first argument starts with a leading 0 and is therefore interpreted by function strtol with third parameter base being 0 as octal number which is invalid because of containing the digit 8. Valid octal numbers have only digits in range 0-7. So string to long integer conversion fails and for that reason a string comparison is executed for 08 compared with 8.

The third comparison 14 EQU 14 is executed as integer comparison with both numbers being interpreted decimal.

The fourth comparison 014 EQU 014 is executed also as integer comparison, but with both numbers being interpreted octal.

The fifth comparison 0x14 EQU 0x14 is executed again as integer comparison, but with both numbers being interpreted hexadecimal explaining twice 20 as output number.

So it is advisable to run always a string comparison of two values in batch files wherever possible with using operator == and without or with using explicitly surrounding double quotes.

It is absolutely useless to measure time differences on == versus EQU using a batch file because the time needed by Windows command interpreter to parse the command lines in batch file before executing the IF condition is many times the amount of time required for the compares itself as demonstrated by the compiled C/C++ code used internally.

Of course this also means using == or EQU does not make a real difference for the user regarding total time needed to accomplish the task done with the batch file. But for other reasons than execution time using == or EQU makes often a difference.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • Out of curiosity I benchmarked the `if "!FileCount!" == "0" endlocal & goto Done` line 1,000,000 times with and without quotes on my 64-bit Win 7 box. The results were identical (121,000 ms). I suppose to be expected because that's a string comparison with or without quotes. So I replaced the non-quoted version `==` with `EQU` and got the same result again. The only marked difference I measured was that using `!FileCount!` was 10% slower than using `%FileCount%` (again, timing a million reps). Looks like that extra pass if a bang sign exists makes a difference. – Paul Houle Aug 06 '17 at 06:56
3

A goto will exit the FOR code. Also you must use delayed environment variable expansion to test your loop control variable, since the FOR block is completely %var% expanded before it is executed. Something like this:

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
Paul Houle
  • 735
  • 9
  • 17
  • 1
    Running a different .bat without using call will break the batch. –  Aug 04 '17 at 08:59
  • I block copied (and ignored) that .bat call from the question, adding "for break" example logic. I've simplified the response further. – Paul Houle Aug 04 '17 at 17:21