Why does this batch file never break out of the loop?
For /L %%f In (1,1,1000000) Do #If Not Exist %%f Goto :EOF
Shouldn't the Goto :EOF break out of the loop?
Edit:
I guess I should've asked more explicitly... how can I break out of the loop?
Based on Tim's second edit and this page you could do this:
#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 suggests a way to use a goto inside a loop, it seems it does work, but it takes some time in a large loop. So internally it finishes the loop before the goto is executed.
You could simply use echo on and you will see that goto :eof or even exit /b doesn't work as expected.
The code inside of the loop isn't executed anymore, but the loop is expanded for all numbers to the end.
That's why it's so slow.
The only way to exit a FOR /L loop seems to be the variant of exit like the exsample of Wimmel, but this isn't very fast nor useful to access any results from the loop.
This shows 10 expansions, but none of them will be executed
echo on
for /l %%n in (1,1,10) do (
goto :eof
echo %%n
)
My answer
Use nested for loops to provide break points to the for /l loop.
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
Explanation
The code must be tweaked significantly to properly use the nested loops. For example, what is written will have leading zeros.
"Regular" for loops can be immediately broken out of with a simple goto command, where for /l loops cannot. This code's innermost for /l loop cannot be immediately broken, but an overall break point is present after every 10 iterations (as written). The innermost loop doesn't have to be 10 iterations -- you'll just have to account for the math properly if you choose to do 100 or 1000 or 2873 for that matter (if math even matters to the loop).
History
I found this question while trying to figure out why a certain script was running slowly. It turns out I used multiple loops with a traditional loop structure:
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
This script file had become somewhat long and it was being run from a network drive. This type of loop file was called maybe 20 times and each time it would loop 50-100 times. The script file was taking too long to run. I had the bright idea of attempting to convert it to a for /l loop. The number of needed iterations is unknown, but less than 10000. My first attempt was this:
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
With echo on, I quickly found out that the for /l loop still did ... something ... without actually doing anything. It ran much faster, but still slower than I thought it could/should. Therefore I found this question and ended up with the nested loop idea presented above.
Side note
It turns out that the for /l loop can be sped up quite a bit by simply making sure it doesn't have any output. I was able to do this for a noticeable speed increase:
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
you do not need a seperate batch file to exit a loop using exit /b if you are using call instead of goto like
call :loop
echo loop finished
goto :eof
:loop
FOR /L %%I IN (1,1,10) DO (
echo %%I
IF %%I==5 exit /b
)
in this case, the "exit /b" will exit the 'call' and continue from the line after 'call'
So the output is this:
1
2
3
4
5
loop finished
So I realize this is kind of old, but after much Googling, I couldn't find an answer I was happy with, so I came up with my own solution for breaking a FOR loop that immediately stops iteration, and thought I'd share it.
It requires the loop to be in a separate file, and exploits a bug in CMD error handling to immediately crash the batch processing of the loop file when redirecting the STDOUT of DIR to 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 )
)
When run, this produces the following output. You'll notice that both iteration and execution of the loop stops when %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 . . .
If you need to preserve a single variable from the loop, have the loop ECHO the result of the variable, and use a FOR /F loop in the MainFile.cmd to parse the output of the LOOP.cmd file.
Example (using the same LOOP.cmd file as above):
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
Output:
:>MainFile.cmd
Simple test demonstrating loop breaking.
After LOOP
3
Press any key to continue . . .
If you need to preserve multiple variables, you'll need to redirect them to temporary files as shown below.
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
)
Output:
:>MainFile.cmd
Simple test demonstrating loop breaking.
After LOOP
ONE
TWO
Press any key to continue . . .
I hope others find this useful for breaking loops that would otherwise take too long to exit due to continued iteration.
As jeb noted, the rest of the loop is skipped but evaluated, which makes the FOR solution too slow for this purpose. An alternative:
set F=1
:nextpart
if not exist "%F%" goto :EOF
echo %F%
set /a F=%F%+1
goto nextpart
You might need to use delayed expansion and call subroutines when using this in loops.
It is impossible to get out of a FOR /L before it completes all iterations.
I have debugged the execution of a FOR /L by the cmd.exe process.
Microsoft could document it better and save us all this effort.
Facts:
The loop is a simple while (TRUE) and the break only happens when the iteration limit is reached.
When an EXIT /b or a GOTO is encountered, no more commands are executed until the end of the iterations.
When an EXIT is encountered, the cmd.exe process is terminated.
Tests:
12 seconds
FOR /L %%G in (1,1,5000000) do (ECHO Only once & GOTO :EOF)
7 seconds
FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT /b)
0 seconds, but this terminates the cmd.exe process
FOR /L %%G in (1,1,5000000) do (ECHO Only once & EXIT)
Assuming that the OP is invoking a batch file with cmd.exe, to properly break out of a for loop just goto a label;
Change this:
For /L %%f In (1,1,1000000) Do If Not Exist %%f Goto :EOF
To this:
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
Better still, add some error reporting:
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
I find this to be a helpful site about lesser known cmd.exe/DOS batch file functions and tricks: https://www.dostips.com/
Did a little research on this, it appears that you are looping from 1 to 2147483647, in increments of 1.
(1, 1, 2147483647): The firs number is the starting number, the next number is the step, and the last number is the end number.
Edited To Add
It appears that the loop runs to completion regardless of any test conditions. I tested
FOR /L %%F IN (1, 1, 5) DO SET %%F=6
And it ran very quickly.
Second Edit
Since this is the only line in the batch file, you might try the EXIT command:
FOR /L %%F IN (1, 1, 2147483647) DO #IF NOT EXIST %%F EXIT
However, this will also close the windows cmd prompt window.
Related
I have an SSIS job running in a batch file that executes asynchronously.
I need to know when the SSIS job is done outputting a bunch of PDF and XLS files.
The files appear in two directories, PDFs first XLS following.
I chose to write a second batch file that will wait a bit after the SSIS job exits, then check to see that the last file written in the directory has been there for 3 minutes, which, after observation, is an ample interval for the job to write a file.
The problem is: the outer loop is never run if the inner loop iterates more than once, which seems to indicate that the second time MYPATH is declared, the value of n is foobarred, but this cannot be true because the script is returning 1, rather than crapping out when Arr[badval] is checked.
#ECHO OFF
REM SSIS process is asyncronous, and executes in background. Problematic for
REM DAG, which relies on exit code to understand process.
REM Check for "last file written" in output directories every N seconds.
REM It's a good bet we are done when they match.
#setlocal enabledelayedexpansion
REM "sleep" wait for non-existent command to complete
waitfor ragnarok /t 180>NUL 2>&1
set Arr[0]=E:\pdf_output
set Arr[1]=E:\xls_output
for /l %%n in (0,1,2) do (
if defined Arr[%%n] (
REM set value for path within loop or scope will bite you
set MYPATH=!Arr[%%n]!
echo Checking file ages in !MYPATH!.
) else (
echo Done
EXIT /B 0
)
:while1
REM don't put a blank line here, it throws a syntax error
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE1=%%I
REM "sleep" use ping for delay, since waitfor will break loop
arp -s 192.168.1.254 >nul
ipconfig /flushdns >nul
ping localhost -n 180 >nul
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE2=%%I
if NOT "!FILE1!" == "!FILE2!" (
goto :while1
)
)
endlocal
REM Something is wrong, return 1 to stop DAG and invite inspection.
exit /B 1
Breaking the while loop into a subroutine as #aschipfl suggested seems to have done the trick:
#ECHO OFF
REM SSIS process is asyncronous, and executes in background. Problematic for
REM DAG, which relies on exit code to understand process.
REM Check for "last file written" in output directories every N seconds.
REM It's a good bet we are done when they match.
#setlocal enabledelayedexpansion
REM "sleep" wait for non-existent command to complete
waitfor ragnarok /t 120>NUL 2>&1
set Arr[0]=E:\pdf_output
set Arr[1]=E:\xls_output
for /l %%n in (0,1,2) do (
if defined Arr[%%n] (
REM set value for path within loop or scope will bite you
set MYPATH=!Arr[%%n]!
echo Checking file ages in !MYPATH!.
CALL :checkfiles
) else (
echo Done
goto :NormalExit
)
)
:checkfiles
:while1
REM don't put a blank line here, it throws a syntax error
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE1=%%I
REM "sleep" use ping for delay, since waitfor will break loop
arp -s 192.168.1.254 >nul
ipconfig /flushdns >nul
ping localhost -n 120 >nul
FOR /F "delims=|" %%I IN ('DIR !MYPATH! /B /O:D') DO SET FILE2=%%I
if NOT "!FILE1!" == "!FILE2!" (
goto :while1
)
endlocal
:NormalExit
exit /B 0
REM Something is wrong, return 1 to stop DAG and invite inspection.
exit /B 1
Hello Batch File experts,
I wrote this piece of code which will print the Latest file version present in the folder in comparison to file name sent as argument, however these line seems to work accordingly when I remove the outer for loop, which I designed to loop as many time as CLI arguments.
FOR /f %%f IN ('DIR /b %%a.*.zip') DO #SET last=%%f
ECHO %last%
Full code :
cd C:\Users\batch\Desktop\test
chdir
set arg1=%1
set arg2=%2
set list=%arg1% %arg2%
(for %%a in (%list%) do (
FOR /f %%f IN ('DIR /b %%a.*.zip') DO #SET last=%%f
ECHO %last%
))
pause
what am I missing here because of which variable last is not set with value when outer loop is present which works perfectly without it.
Thanks,
Might I suggest you use SHIFT instead:
#Echo Off
SetLocal
If %1'==' Exit/B
If /I Not "%CD%"=="%USERPROFILE%\Desktop\test" (
PushD "%USERPROFILE%\Desktop\test" 2>Nul&&(Set _=PopD)||Exit/B)
:Loop
For %%A In ("%~1*.zip") Do Set "last=%%A"
Echo=%last%
Shift
If Not %1'==' GoTo Loop
%_%
EndLocal
Timeout -1
This of course means that you are free to use it with more than two arguments!
You need to use delayed expansion (about ten thousand SO items on this) or use a subroutine or
call echo %%last%%
I know breaking out of a nested loop is fairly easy, however, I'm not sure how to do it when I'm working with multiple lists of servers. Here's the scenario:
Goal: Search for sessions on a server matching a specific user ID, and also kill any disconnected sessions found
Problem: I have multiple lists of farms. I want to cycle through the lists until I find the users session, and then stop when that list is finished (not stop when the session is cleared, they may have multiple sessions in the farm).
Farmlist1.txt
farmlist2.txt
farmlist3.txt
If the session is found in farmlist2.txt, I want to FINISH searching in that list, but don't continue to farmlist3.txt.
Here's what I have so far, and it works like a charm. (Optimizations welcomed)
#echo off
echoCitrix Session Reset
echo.
echo This will look for a specific userID AND kill disconnected sessions on all servers!
set /p userid=User ID of the user:
for %%a in (q:\scripts\1common\citrixlists\*.txt) do (
for /f "tokens=*" %%l in (%%a) do (
ping %%l -n 1 | find /i "TTL=" > nul
if errorlevel 1 (
echo server %%l down or out of Load
) else (
echo Looking for %username% and killing disconnected sessions on %%l
for /f "tokens=3" %%b in ('qwinsta *tcp /server:%%l ^| find /i "%userid%"') do echo %%b | rwinsta %%b /server:%%l && echo SESSION FOR %userid% KILLED ON %%l
for /f "tokens=2" %%i IN ('qwinsta /server:%%l ^| find /i "disc"') DO (
if %%i gtr 0 (
rwinsta %%i /server:%%l && echo Disconnected sessions terminated
)
)
)
)
)
I do not exactly understand what you are trying to accomplish, but I do understand how to break out of nested for loops (or any other nested parenthesised blocks of code). So let me elaborate on that.
Breaking a single for loop is simple: put goto :STOP into the loop and the label :STOP in the line following the loop structure. The example below breaks the loop depending on its loop counter value:
#echo off
for /L %%I in (0,1,5) do (
if %%I GTR 3 (
echo Break!
goto :STOP
)
echo %%I
)
:STOP
Although the loop is specified to count up to 5, the output is:
0
1
2
3
Break!
Note that the loop actually completes the counting internally, but no more commands are executed. You can easily reproduce that when you increase the end value 5 to a huge number like 1000000.
But now let us concentrate on nested loops (two in each example):
The following code snippet breaks both loops upon a certain condition is met:
#echo off
for %%J in (Aa Bb Cc) do (
for /L %%I in (0,1,5) do (
if "%%~J"=="Bb" if %%I GTR 3 (
echo Break!
goto :STOP
)
echo %%J-%%I
)
)
:STOP
The output is:
Aa-0
Aa-1
Aa-2
Aa-3
Aa-4
Aa-5
Bb-0
Bb-1
Bb-2
Bb-3
Break!
As you can see, execution of the loop construct is interrupted immediately at a certain point.
The batch file here breaks the outer loop when the a certain condition in the inner loop is met. The result of the check is transferred to the outer loop using a variable FLAG:
#echo off
set "FLAG="
for %%J in (Aa Bb Cc) do (
for /L %%I in (0,1,5) do (
if "%%~J"=="Bb" if %%I GTR 3 (
echo Break!
set "FLAG=#"
)
echo %%~J-%%I
)
if defined FLAG goto :STOP
)
:STOP
The output is:
Aa-0
Aa-1
Aa-2
Aa-3
Aa-4
Aa-5
Bb-0
Bb-1
Bb-2
Bb-3
Break!
Bb-4
Break!
Bb-5
You will notice that the inner loop finishes execution before the outer one is broken.
The script below breaks the inner loop when a certain condition is net. To not break the outer loop, a sub-routine holding the inner loop and being called (call) from the outer one is required to hide the code block context of the outer loop from the goto break-up method:
#echo off
for %%J in (Aa Bb Cc) do (
call :SUB "%%~J"
)
goto :EOF
:SUB
for /L %%I in (0,1,5) do (
if "%~1"=="Bb" if %%I GTR 3 (
echo Break!
goto :STOP
)
echo %~1-%%I
)
:STOP
The output is:
Aa-0
Aa-1
Aa-2
Aa-3
Aa-4
Aa-5
Bb-0
Bb-1
Bb-2
Bb-3
Break!
Cc-0
Cc-1
Cc-2
Cc-3
Cc-4
Cc-5
Here the outer loop finishes its execution without being affected by the broken inner one.
I hope one of the (last two) examples suits the requirements for your script.
I am trying to read the filename and according to the filename set the Output variable.
I have tried using findstr directly on %%F (findstr /i "M002" %%F >nul 2>&1 ) and also writing to a temp text file (as below) to test and read it, but nothing worked.
What I'm doing wrong?
P.S. If I remove this out from the loop the code works, but I need it within the loop due to the last line.
rem ===========================================
set FileType=pdf
for %%F in (%~dp0*.%FileType%) do (
rem ECHO %%F
echo "%%F" > test.txt
findstr /i "M002" test.txt >nul 2>&1
echo %errorlevel%
if not %errorlevel% == 0 (
echo "4pp"
echo %%F
set Output=4PP
) ELSE (
echo "Letter"
echo %%F
set Output=Letter
)
set NetComm="XYZ" "%Output%" etc etc etc
)
rem ====================================
Generation 5,961 of delayedexpansion.
Batch parses the entire statement from the for through to the last closing parenthesis and replaces any %var% with the value of that variable at the time the statement is parsed.
Consequently, attempting to use a value which is established within the loop will fail. This applies to %output% and %errorlevel% in the current instance.
Dealing with %errorlevel% is easy. The syntax
if errorlevel n
works on the run-time value of errorlevel and is true if errorlevel has been set to n or greater than n.
Personally, I find the if not condition ... else clumsy. I can't see why it's so common. Fuzzy thinking in my book...
There are three common ways to overcome the problem, each has its own advantages and disadvantages, proponents and critics.
First, the "documented" method. Use a setlocal enabledelayedexpansion instruction. Once this instruction has been executed, !var! will access the current value and %var% the initialvalue ofvar`.
Second, the subroutine method. CALL :sub within a loop executes a subroutine (see the documentation - call /? from the prompt) and within that subroutine, %var% will have the value as established within the loop.
Third, it's sometimes possible to use call echo %%var%% (or call someotherinsruction) where the call is executiong the target as if it was a subroutine.
Hence, in your case, a fix might be
rem ===========================================
set FileType=pdf
for %%F in (%~dp0*.%FileType%) do (
rem ECHO %%F
findstr /i "M002" "%%F" >nul 2>nul
CALL echo %%errorlevel%%
if errorlevel 1 (
echo "4pp"
echo %%F
set Output=4PP
) ELSE (
echo "Letter"
echo %%F
set Output=Letter
)
CALL set NetComm="XYZ" "%%Output%%" etc etc etc
)
rem ====================================
depending entirely on your definition of "works" (which is not an absolute - it has meaning only to you.)
How do I loop a batch script only a certain amount of times (x10 or something)?
If the code was:
#echo off
:loop1
Start taskmgr.exe
Goto loop
:loop2
Start cmd.exe
goto loop2
How can loop loop1 and few times and go to loop2?
Any helpful answer would be appreciated :)
For a reason that i'm ignoring, the FOR command won't work for looping a specific label.
For example (I may be wrong):
#echo off
for /L %%a in (1,1,2) do (
goto loop
)
:loop
echo this won't loop for 2 times.
This will simply loop infinite times. So, I have found an alternative simple method to loop a label as many times as I want. To do this, I create a variable, like loop that will have an even bigger number every time the label repeats.
There is an example:
#echo off
set loop=0
:loop
echo hello world
set /a loop=%loop%+1
if "%loop%"=="2" goto next
goto loop
:next
echo This text will appear after repeating "hello world" for 2 times.
Output:
hello world
hello world
This text will appear after repeating "hello world" for 2 times.
Explanation:
set loop=0 sets the value of the variable loop at 0;
set /a loop=%loop%+1 adds 1 every time the label :loop is repeated.
if "%loop%"=="2" goto next tests if the variable loop is equal to 2 (so it was repeated for 2 times); if it's equal, it will go to the label :next, otherwise it will go to the label :loop.
if you open a command window and type FOR /? it will give you the command you are looking for.
FOR /L %variable IN (start,step,end) DO command [command-parameters]
The set is a sequence of numbers from start to end, by step amount.
So (1,1,5) would generate the sequence 1 2 3 4 5 and (5,-1,1) would
generate the sequence (5 4 3 2 1)
Here is an example:
#echo off
for /L %%a in (1,1,10) do (
Start taskmgr.exe
)
for /L %%a in (1,1,10) do (
Start cmd.exe
)
#echo off
set /a a=1
goto loop
:loop
echo looped %a% times so far
set /a a=%a%+1
if %a%=10 (
echo looped a total of %a% times
)
You can also use this command:
#echo off
for /l %%a in (1,1,10) do (
rem %%a is a variable and starts at a value of 1, steps by 1 and ends at a value of 10
start "Command Prompt #%%a Opened" cmd
rem eg Command Prompt #1 Opened
rem eg Command Prompt #2 Opened
rem etc
)
which opens Command Prompt with the title "Command Prompt #%%a Opened". rem is a command that you can use to write comments.
Try this:
#echo off
set loopvar=1
:repeat
if %loopvar% gtr 5 (goto :done) else (set /a loopvar=%loopvar%+1 && echo Loop && goto :repeat)
:done
Under :done, write the code you want it to do after the loop. Make sure to replace five with the number of times you want the loop to repeat.
For delay in the loop, try this:
#echo off
set loopvar=1
:repeatwithdelay
if %loopvar% gtr 5 (goto :done) else (set /a loopvar=%loopvar%+1 && timeout /T 1 >nul && echo Loop && goto :repeatwithdelay)
Hope it helped!