Waiting for eight parallel batch scripts - batch-file

I have like 0 experience with coding batch files and I'm actually refering to an older post
"Waiting for parallel batch scripts", and to the answer of dbenham.
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
:: Launch one and two asynchronously, with stream 9 redirected to a lock file.
:: The lock file will remain locked until the script ends.
start "" cmd /c 9>"%lock%1" one.bat
start "" cmd /c 9>"%lock%2" two.bat
:Wait for both scripts to finish (wait until lock files are no longer locked)
1>nul 2>nul ping /n 2 ::1
for %%N in (1 2) do (
( rem
) 9>"%lock%%%N" || goto :Wait
) 2>nul
::delete the lock files
del "%lock%*"
:: Launch three and four asynchronously
start "" cmd /c three.bat
start "" cmd /c four.bat
His answer works fine, but I would like to adapt his solution to run not 2 files simultaneously but 8, and after that another 8 and so on...
Can someone help me?
What I tried so far is this (for 3 blocks of 8 batch files each)
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
:: Launch 8 files asynchronously, with stream 9 redirected to a lock file.
:: The lock file will remain locked until the script ends.
start "" cmd /c 9>"%lock%1" one.bat
start "" cmd /c 9>"%lock%2" two.bat
start "" cmd /c 9>"%lock%3" three.bat
start "" cmd /c 9>"%lock%4" four.bat
start "" cmd /c 9>"%lock%5" five.bat
start "" cmd /c 9>"%lock%6" six.bat
start "" cmd /c 9>"%lock%7" seven.bat
start "" cmd /c 9>"%lock%8" eight.bat
:Wait for all scripts to finish (wait until lock files are no longer locked)
1>nul 2>nul ping /n 2 ::1
for %%N in (1 2 3 4 5 6 7 8) do (
( rem
) 9>"%lock%%%N" || goto :Wait
) 2>nul
::delete the lock files
del "%lock%*"
:: Launch 8 files asynchronously, with stream 9 redirected to a lock file.
:: The lock file will remain locked until the script ends.
start "" cmd /c 9>"%lock%1" nine.bat
start "" cmd /c 9>"%lock%2" ten.bat
start "" cmd /c 9>"%lock%3" eleven.bat
start "" cmd /c 9>"%lock%4" twelve.bat
start "" cmd /c 9>"%lock%5" thirteen.bat
start "" cmd /c 9>"%lock%6" fourteen.bat
start "" cmd /c 9>"%lock%7" fifteen.bat
start "" cmd /c 9>"%lock%8" sixteen.bat
:Wait for all scripts to finish (wait until lock files are no longer locked)
1>nul 2>nul ping /n 2 ::1
for %%N in (1 2 3 4 5 6 7 8) do (
( rem
) 9>"%lock%%%N" || goto :Wait
) 2>nul
::delete the lock files
del "%lock%*"
:: Launch three and four asynchronously
start "" cmd /c seventeen.bat
start "" cmd /c eighteen.bat
start "" cmd /c nineteen.bat
start "" cmd /c twenty.bat
start "" cmd /c twenty-one.bat
start "" cmd /c twenty-two.bat
start "" cmd /c twenty-three.bat
start "" cmd /c twenty-four.bat
But it doesn't seem to work right. Normaly a block of eight batch files will be completed after max. 3h but I waited almost 24h and he seems to be stuck in the first block...
Critical Info: When a batch file (or Job) is executed a "cmd" window opens for like 5 seconds, closes and an other programm starts which is doing the actual job of calculations for max. 3h. I looked into the Task Manager and what happens is, 3 processes are started. "standard.exe" and "eliT_DriverLM.exe" after like 20 sec and "python.exe" after 5 sec. Since the process is finished when this 3 processes are no longer running (not showing up in the Task Manager anymore), the programm should wait for the for at least the "standard.exe" to terminate.

#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q57071663.txt"
SET "lockfilename=%temp%\LOCK#_"
:: BLOCKSIZE - if positive, run in blocks of this size.
:: If negative, run max this number of parallel jobs
SET /a blocksize=-5
FOR /f "usebackqtokens=1*delims=|" %%a IN ("%filename1%") DO (
IF "%%b"=="" (SET "jobname="&SET "jobinstr=%%a") ELSE (SET "jobname=%%a"&SET "jobinstr=%%b")
CALL :sub
)
GOTO :EOF
:sub
SET /a absblocksize=blocksize
IF %blocksize% gtr 0 GOTO blockmode
SET /a absblocksize=-blocksize
:blockmode
FOR /L %%c IN (1,1,%absblocksize%) DO IF NOT EXIST "%lockfilename%_%%c" SET "lock=%lockfilename%_%%c"&GOTO release
:: No vacant spots, so block complete. Wait for ALL to finish
:blockwait
CALL :wait1
IF %blocksize% gtr 0 IF EXIST "%lockfilename%_*" (GOTO blockwait) ELSE (GOTO blockmode)
GOTO blockmode
:release
ECHO.>"%lock%"
START "%jobname%" CMD /c "call %jobinstr%&DEL "%lock%""
GOTO :eof
:wait1
timeout /t 1 >NUL 2>NUL
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q57071663.txt containing some dummy data for my testing.
%lockfile% is used temporarily and is a filename of your choosing.
The usebackq option is only required because I chose to add quotes around the source filename.
The contents of q57071663.txt are, for my demo,
Job 1 | q57071663_sub.bat 12
Job 2 | q57071663_sub.bat 4
Job 3 | q57071663_sub.bat 15
Job 4 | q57071663_sub.bat 2
Job 5 | q57071663_sub.bat
Job 6 | q57071663_sub.bat
Job 7 | q57071663_sub.bat
Job 8 | q57071663_sub.bat
Job 9 | q57071663_sub.bat 6
Job11 | q57071663_sub.bat 12
Job12 | q57071663_sub.bat 4
Job13 | q57071663_sub.bat 15
Job14 | q57071663_sub.bat 2
Job15 | q57071663_sub.bat
Job16 | q57071663_sub.bat
Job17 | q57071663_sub.bat
Job18 | q57071663_sub.bat
Job19 | q57071663_sub.bat 6
Each line is an optional job name, a separator (I chose |, obviously) and the command to be executed by start...
and q57071663_sub.bat is simply
#ECHO OFF
SETLOCAL
SET "delay=%1"
:: IF no delay defined, random 3..19 secs
IF NOT DEFINED delay SET /a delay=3+(%RANDOM% %% 17)
timeout /t %delay% >NUL 2>NUL
ie. delay for (argument) seconds, or 3 to 19 if no argument provided.
As for the main routine, the for/f splits each line from the file using the | separator, and assigns the two parts to jobname and jobinstr, then executes the subroutine :sub.
The subroutine calculates the absolute value of blocksize and looks for a missing lockfile. If it finds a lockfile missing in the range, lock gets the lock filename and release sets a lockfile and starts the job, appending an instruction to delete the lockfile when the job terminates.
if there are no vacant slots available, we wait. If blocksize is greater than 0, then we may proceed to the next block if there are no remaining lockfiles, otherwise we wait. If blocksize is negative, then we simply try again.
So - if blocksize is positive, this would run all of the jobs in the file n at a time, waiting until each block of n is finished before starting the next block. If blocksize is negative, then we run n jobs in parallel, starting the next job in the list when any job terminates. This runs n jobs at all times.
Complete revision in the light of comments and critical information now included in question.
It would appear that the actual requirement is that at most 8 instances of standard.exe are running in parallel.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q57071663_c.txt"
SET /a blocksize=5
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
SET "jobinstr=%%a"
CALL :sub
)
GOTO :EOF
:sub
:: Wait 30 secs (29 + 1) to ensure last-started job is active
timeout /t 5 >NUL 2>NUL
:delay1
timeout /t 1 >NUL 2>NUL
:: Count #times critical .exe is in tasklist
SET /a active=0
:: FOR /f %%c IN ('tasklist^|findstr /b "standard.exe"^|find /c "standard.exe"' ) DO SET active=%%c
FOR /f %%c IN ('tasklist/v^|findstr /b "cmd.exe"^|find /c "q57071663"' ) DO SET active=%%c
ECHO %active% instances active
IF %active% geq %blocksize% GOTO delay1
START "%jobinstr%" CMD /c "call %jobinstr%"
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q57071663_c.txt containing the same job-data as above, except that the job-name and delimiter are removed.
I used a blocksize of 5 for testing. In OP's application, substitute 8
So - read each line of job-instructions to %%a and thence to jobinstr and call the subroutine SUB.
First thing to do in sub is to wait 30 secs (I used 6 sec for my testing) - this is just to ensure that the last-started job has created its instance of standard.exe
Then count the active standard.exe instances. I've commented-out the line for standard.exe and left in my test command, which requires both cmd.exe and q57071663 to be on the same tasklist line to indicate an instance of interest.
If there are the required number of instances already active, delay 1 second and try again.
Otherwise, start the required job.

Related

Msgbox to prompt user if job (printing) is skip in batch

As suggested in my previous question, 1 question per thread. So Im here to open another question. Basically I want to prompt user that printing will be skip today because nothing to print, then the user will press OK, then code will continue to shutdown the computer. I want to do this to alert user that today's printing job have been run.
So I try some code like below, it seems working but i dont know how to implement in my main code.
#echo off
Call :Msgbox
if "%errorlevel%"=="1" GOTO SHUTDOWN
exit /b
:Msgbox
echo wscript.quit MsgBox ("Printing skipped.. Press ok to shutdow the computer", 48, "Shutdown Computer") >"%temp%\input.vbs"
wscript //nologo "%temp%\input.vbs"
exit /b
:SHUTDOWN
echo "%SystemRoot%\System32\shutdown.exe -s -t 60"
PAUSE
This is part of my main code where i want to place above code.
https://stackoverflow.com/a/57609600/6409413
IF pdf file size greater than 9000byte > Print PDF for today > Then go to Shutdown
IF pdf file size less than 9000byte > Promp user using msgbox > user press OK > Skip Print PDF for today > Then go to Shutdown
Rem ------------------------------------------------------------------------
Rem 4. Printing files with sizes over 9000 bytes
Set "_Exe1=%ProgramFiles(x86)%\Foxit Software\Foxit Reader\Foxit Reader.exe"
For %%A In ("%_Dir1%\c\%_FullDateString%.pdf")Do If %%~zA GTR 9000 (
Echo Printing %%A&Echo=&"%_Exe1%" /t "%_Dir1%\c\%_FullDateString%.pdf")
UPDATE:
Sort of working for now using code below. Any others way to achieve this?
Rem ------------------------------------------------------------------------
Rem 4. Printing files with sizes over 9000 bytes
Set "_Exe1=%ProgramFiles(x86)%\Foxit Software\Foxit Reader\Foxit Reader.exe"
setlocal EnableDelayedExpansion
For %%A In ("%_Dir1%\c\%_FullDateString%.pdf")Do ( set size=%%~zA
if !size! GTR 9000 (
goto PRINT
) else if !size! LSS 9000 (
goto NOPRINT
:NOPRINT
msg * /time:0 /w Printing skipped.. Press "OK" to shutdown the computer"
goto SHUTDOWN
)
)
:PRINT
Echo "Printing %%A&Echo=&"%_Exe1%" /t "%_Dir1%\c\%_FullDateString%.pdf")"
Rem ------------------------------------------------------------------------
Rem 5. Shutting down computer
:SHUTDOWN
echo "%SystemRoot%\System32\shutdown.exe -s -t 60"
PAUSE
I really think that you're overcomplicating this unnecessarily. You're already using the shutdown command, which can pop up a dialog box to the end user.
Rem ------------------------------------------------------------------------
Rem 4. Printing files with sizes over 9000 bytes
Set "_Exe1=%ProgramFiles(x86)%\Foxit Software\Foxit Reader\Foxit Reader.exe"
Set "_Msg=skipped"
For %%A In ("%_Dir1%\c\%_FullDateString%.pdf")Do If %%~zA GTR 9000 (
Set "_Msg=finished"&Echo Printing %%A&Echo=
"%_Exe1%" /t "%_Dir1%\c\%_FullDateString%.pdf")
Rem ------------------------------------------------------------------------
Rem 5. Shutting down computer
ShutDown /S /T 60 /C "Printing %_Msg%, shutting down your computer." /D P:0:0
Note: This section is a direct replacement for the previous code I provided, it is not based upon whatever you've posted above, (which is littered with issues).

Batch File: For loop variable dropped after goto invoked in nested for loop

I'm trying to write a batch file that, for a set of numbers (1 to 10), starts an executable sequentially. The numbers from the set of numbers are being passed to a file for the executable to run in batch mode. However, because the executable hogs resources, I want only 5 of these executables running at a time (there will be a total of 1500 executable runs in total). I've jimmy-rigged some script to monitor the tasklist and to not continue starting the executable while 5 of the executable are currently running. Once the number of tasks goes back down to 4, the next executable will be started.
Simplified minimum working example (I'm obviously not running notepad):
FOR %%g IN (1 2 3 4 5 6 7 8 9) DO (
:loop
tasklist | find /I /C "OpenSees.exe" >process.txt
FOR /F "tokens=*" %%a IN (process.txt) DO (
IF "%%a" EQU "5" (goto :loop)
)
start notepad.exe
PAUSE
)
What happens here is that once %%a does equal 5, goto is invoked, and the loop works fine and doesn't start more executables. However, once one "notepad" is closed, the waiting loop is broken (as intended), but the initial FOR loop is also broken (%%g is no longer a defined variable) and the script ends without %%g passing 5 to 6 7 8 and 9. Echo %%g gives back echo %g while the waiting loop is looping through.
What am I doing wrong here?
Thank you in advance.
I think this fixes your problem:
#setlocal ENABLEEXTENSIONS
#set prompt=$G
#rem Change this to whatever program you want to run.
#set _programToRun=notepad.exe
#for %%g in (1 2 3 4 5 6 7 8 9) do #call :TaskMonitor %%g
#exit /b 0
:TaskMonitor
#tasklist | find /I /C "%_programToRun%" > process.txt
#set /p "_procCount=" < process.txt
#echo %%1==%1 _procCount==%_procCount%
#rem Uncomment the next line, remove this line and the one following the next.
#rem #if _procCount lss 5 (start %_programToRun% %1 & exit /b 0)
#if %_procCount% lss 5 (start %_programToRun% & exit /b 0)
#goto :TaskMonitor
Always avoid multi-line for loop bodies (or if/else blocks for that matter), they are just not worth the effort required to debug them. Call out to a subroutine is the best way avoid those issues.

After terminate the batch needs to delete a .lock file that has been create

Hello I am very new in this and I was able to run one instance of a batch file.
However, another file is being created called something.lock but the file is not been deleted by itself when I stop the batch or close it.
The new file create is the one that helps to have one instance running.
Can the new file ".lock " be deleted after I close the script with the "X" or because an user ended correctly with going to label end:
The code that I have is
:init
set "started="
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
#if defined started (
del "%~f0.lock"
) else (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
exit /b
:start
cd /d %~dp0
cls
:initial
pause >nul
You are misapplying the lock file. You are simply checking to see if the file exists, which means you must guarantee that the file is deleted upon batch termination.
There is a much better way, which you have only partially implemented. Only one process can have the file open for write access. You just need to determine if the file is already locked by another process.
Once the process with the exclusive lock terminates, the lock will be released. This is true no matter how the script terminates - even if it was the result of Ctrl-C or window closure. The file might not be deleted, but the next time the script runs, the file won't be locked, so the script will proceed nicely.
In the code below I save the current definition of stderr to an unused file handle before I redirect sterr to nul. Within the inner block I redirect stderr back to the saved definition. In this way I prevent the error message if the file is already locked, but the CALLed :start routine will still print out error messages normally.
#echo off
:init
8>&2 2>nul ( 2>&8 9>"%~f0.lock" call :start ) || (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
del "%~f0.lock" 2>nul
exit /b
:start
cd /d %~dp0
cls
del asdfasdfasdf
:initial
pause >nul
The difficulty is that your batch thread itself won't have its own PID. There's no graceful way to tell whether your batch script is running or when it has terminated. And there's no way to wake the dead, to let the script have the last word when a user red X's or Ctrl+C's. When it's over, it's over.
There are a few ways you can do what you want to do. Try them all and see which you prefer. Use dbenham's solution. His is correct. The following efforts are left here as an exercise in futility, although Solution 4 seems to work very well. In the end, it's still just a hack; whereas dbenham's redirection sleight-of-hand provides a correct implementation of lock files the way lock files are supposed to work.
...
Solution 1
One simple way is to use powershell to minimize the current window, re-launch your script with start /wait, then after completion call powershell again to restore.
#echo off
setlocal
set "lock=%temp%\~%~n0.lock"
if "%~1" neq "wrapped" (
if exist "%lock%" (
echo Only one instance is allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
rem :: create lock file
>"%lock%" echo 1
rem :: minimize this console
powershell -windowstyle minimized -command ""
rem :: relaunch self with "wrapped" argument and wait for completion
start /wait "" cmd /c "%~f0" wrapped
rem :: delete lock file
del "%lock%"
rem :: restore window
powershell -windowstyle normal -command ""
goto :EOF
)
:: Main script goes here.
:loop
cls
echo Simulating script execution...
ping -n 2 0.0.0.0 >NUL
goto loop
This should be enough for casual use and should account for any cause of the batch file's termination short of taskkill /im "cmd.exe" /f or a reboot or power outage.
Solution 2
If you need a more bulletproof solution, you can get the current console window's PID and intermittently test that it still exists. start /min a helper window to watch for its parent window to die, then delete the lock file. And as long as you're creating a watcher anyway, might as well let that watcher be the lock file.
Biggest drawback to this method is that it requires to end your main script with exit to destroy the console window, whether you want it destroyed or not. There's also a second or two pause while the script figures out its parent's PID.
(Save this with a .bat extension and run it as you would any other batch script.)
#if (#a==#b) #end /* JScript multiline comment
:: begin batch portion
#echo off
setlocal
:: locker will be a batch script to act as a .lock file
set "locker=%temp%\~%~nx0"
:: If lock file already exists
if exist "%locker%" (
tasklist /v | find "cleanup helper" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
)
:: get PID of current cmd console window
for /f "delims=" %%I in ('cscript /nologo /e:Jscript "%~f0"') do (
set "PID=%%I"
)
:: Create and run lock bat.
>"%locker%" echo #echo off
>>"%locker%" echo setlocal
>>"%locker%" echo echo Waiting for parent script to finish...
>>"%locker%" echo :begin
>>"%locker%" echo ping -n 2 0.0.0.0^>NUL
>>"%locker%" echo tasklist /fi "PID eq %PID%" ^| find "%PID%" ^>NUL ^&^& ^(
>>"%locker%" echo goto begin
>>"%locker%" echo ^) ^|^| ^(
>>"%locker%" echo del /q "%locker%" ^&^& exit
>>"%locker%" echo ^)
:: Launch cleanup watcher to catch ^C
start /min "%~nx0 cleanup helper" "%locker%"
:: ==================
:: Rest of script
:: blah
:: blah
:: blah
:: ==================
:end
echo Press any key to close this window.
pause >NUL
exit
:: end batch portion / begin JScript
:: https://stackoverflow.com/a/27514649/1683264
:: */
var oShell = WSH.CreateObject('wscript.shell'),
johnConnor = oShell.Exec('%comspec% /k #echo;');
// returns PID of the direct child of explorer.exe
function getTopPID(PID, child) {
var proc = GetObject("winmgmts:Win32_Process=" + PID);
return (proc.name == 'explorer.exe') ? child : getTopPID(proc.ParentProcessID, PID);
}
var PID = getTopPID(johnConnor.ProcessID);
johnConnor.Terminate();
// output PID of console window
WSH.Echo(PID);
Solution 3
You can also test a lock file and see whether it's stale by setting a timestamp within the lock file, and setting that same timestamp in your console window title. Only problem with this is that the window title doesn't revert to normal if the user terminates with Ctrl+C, so you can't run the script twice without closing the cmd window. But closing the window and opening a new one for subsequent launches may not be too terrible a price to pay, as this is the simplest method described thusfar.
#echo off
setlocal
set "started=%time%"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "locktime="
) else (
set "locktime=%started%"
)
tasklist /v | find "%locktime%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %~nx0 started at %started%
>"%lockfile%" echo %started%
:: rest of script here
echo Simulating script execution...
:loop
ping -n 2 0.0.0.0 >NUL
goto loop
Solution 4
Here's a bit more polished solution, combining methods 1 and 3. It re-launches itself in the same window, then sets the window title to a unique ID. When the script exits gracefully, the lock file is deleted. Whether the script exits gracefully or forcefully, the window title reverts back to its default. And if no window exists in the task list with a title matching the unique ID, the lock file is deemed stale and is overwritten. Otherwise, the script notifies the user that only one instance is allowed and exits. This is my favorite solution.
#echo off
setlocal
if "%~1" neq "wrapped" (
cmd /c "%~f0" wrapped %*
goto :EOF
)
:: remove "wrapped" first argument
shift /1
:: generate unique ID string
>"%temp%\~%~n0.a" echo %date% %time%
>NUL certutil -encode "%temp%\~%~n0.a" "%temp%\~%~n0.b"
for /f "usebackq EOL=- delims==" %%I in ("%temp%\~%~n0.b") do set "running_id=%%I"
del "%temp%\~%~n0.a" "%temp%\~%~n0.b"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "lock_id="
) else (
set "lock_id=%running_id%"
)
tasklist /v | find "%lock_id%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %running_id%
>"%lockfile%" echo %running_id%
:: rest of script here
echo Press any key to exit gracefully, or Ctrl+C to break
pause >NUL
del "%lockfile%"
goto :EOF

Understanding start, 2>nul, cmd, and other symbols in a batch file

Following up from this topic on here, I'm trying to understand what's happening "behind the scenes" from a suggested answer. I don't understand what 2>nul or 1>nul is supposed to do. And I tried to decipher what the symbols in the start /b line is doing, but I am really clueless here. I need a step by step approach on that one if you don't mind.
What's happening in this part of the code?
2>nul del %lock%!nextProc!
%= Redirect the lock handle to the lock file. The CMD process will =%
%= maintain an exclusive lock on the lock file until the process ends. =%
start /b "" cmd /c %lockHandle%^>"%lock%!nextProc!" 2^>^&1 !cpu%%N! !cmd!
)
set "launch="
And this:
) 9>>"%lock%%%N"
) 2>nul
if %endCount% lss %startCount% (
1>nul 2>nul ping /n 2 ::1
goto :wait
)
2>nul del %lock%*
Copy of suggested code in full:
#echo off
setlocal enableDelayedExpansion
:: Display the output of each process if the /O option is used
:: else ignore the output of each process
if /i "%~1" equ "/O" (
set "lockHandle=1"
set "showOutput=1"
) else (
set "lockHandle=1^>nul 9"
set "showOutput="
)
:: Define the maximum number of parallel processes to run.
:: Each process number can optionally be assigned to a particular server
:: and/or cpu via psexec specs (untested).
set "maxProc=8"
:: Optional - Define CPU targets in terms of PSEXEC specs
:: (everything but the command)
::
:: If a cpu is not defined for a proc, then it will be run on the local machine.
:: I haven't tested this feature, but it seems like it should work.
::
:: set cpu1=psexec \\server1 ...
:: set cpu2=psexec \\server1 ...
:: set cpu3=psexec \\server2 ...
:: etc.
:: For this demo force all cpu specs to undefined (local machine)
for /l %%N in (1 1 %maxProc%) do set "cpu%%N="
:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
set "lock="
for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
set "lock=%%T"
goto :break
)
:break
set "lock=%temp%\lock%lock%_%random%_"
:: Initialize the counters
set /a "startCount=0, endCount=0"
:: Clear any existing end flags
for /l %%N in (1 1 %maxProc%) do set "endProc%%N="
:: Launch the commands in a loop
set launch=1
echo mem=1m 2m 3m 4m 6m 8m 12m 16m 24m 32m 48m 64m 96m 128m 192m 256m 384m 512m 768m 1024m
echo o=2 3 4 5 6 7 8 10 12 14 16 20 24 28 32
echo s=off 1m 2m 4m 8m 16m 32m 64m 128m 256m 512m 1g 2g 4g 8g 16g 32g 64g on
echo x=1 3 5 7 9
for %%x IN (9) DO for %%d IN (1024m 768m 512m 384m 256m 192m 128m 96m 64m 48m 32m 24m 16m 12m 8m 6m 4m 3m 2m 1m) DO (
set "cmd=7z.exe a teste.resultado\%%xx.ppmd.%%dd.%%ww.%%ss.7z .\teste.original\* -mx=%%x -m0=PPMd:mem=%%d:o=%%w -ms=%%s"
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set cmd!nextProc!=!cmd!
if defined showOutput echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting !cmd!
2>nul del %lock%!nextProc!
%= Redirect the lock handle to the lock file. The CMD process will =%
%= maintain an exclusive lock on the lock file until the process ends. =%
start /b "" cmd /c %lockHandle%^>"%lock%!nextProc!" 2^>^&1 !cpu%%N! !cmd!
)
set "launch="
:wait
:: Wait for procs to finish in a loop
:: If still launching then return as soon as a proc ends
:: else wait for all procs to finish
:: redirect stderr to null to suppress any error message if redirection
:: within the loop fails.
for /l %%N in (1 1 %startCount%) do (
%= Redirect an unused file handle to the lock file. If the process is =%
%= still running then redirection will fail and the IF body will not run =%
if not defined endProc%%N if exist "%lock%%%N" (
%= Made it inside the IF body so the process must have finished =%
if defined showOutput echo ===============================================================================
echo !time! - proc%%N: finished !cmd%%N!
if defined showOutput type "%lock%%%N"
if defined launch (
set nextProc=%%N
exit /b
)
set /a "endCount+=1, endProc%%N=1"
) 9>>"%lock%%%N"
) 2>nul
if %endCount% lss %startCount% (
1>nul 2>nul ping /n 2 ::1
goto :wait
)
2>nul del %lock%*
if defined showOutput echo ===============================================================================
echo Thats all folks!
The digit before a redirection symbol is the stream number to redirect.
The default stream is 1, when no number is present, so 1>... and >... are equivalent.
stream 1 is the standard input/output stream, 2 is the standard error stream.
A command can output to multiple streams and it's allowed to redirect each of them to a different destination.
So 2>nul and 1>nul simply said that the error output and the normal output will be redirected to nul. So nothing will be outputted.
My explanation:
1. 2>nul del %lock%!nextProc!
2. %= Redirect the lock handle to the lock file. The CMD process will =%
3. %= maintain an exclusive lock on the lock file until the process ends. =%
4. start /b "" cmd /c %lockHandle%^>"%lock%!nextProc!" 2^>^&1 !cpu%%N! !cmd!
5. )
6. set "launch="
Line 1: delete file and don't show errors. same as "del /Q". the exclamation
symbols require delayedexpansion be enabled to evaluate to anything. I would
have wrote it as: del /Q "%lock%!nextProc!"
Line 2: a really weird comment style. should just start each line with "::" instead
Line 3: same as line 2
Line 4: hard to tell without seeing rest of script. the %%N tells me that this section
is inside of a loop block. the ^ characters are necessary so that the start command
recognizes the special characters as part of the cmd command string. I don't think the start command was necessary
here IMHO. I bet that "start /B /wait" is the equivilant of "start /b "" cmd /c".
I would rewrite this script personally to make it easier to understand.
Also, see dostips.com
Also: 1>nul 2>nul ping /n 2 ::1 is the equivilant of "ping -n 2 -w 1000 127.1 >nul" but stupidly harder to understand.
Also: %~1 means to get the 1st arg %1 and trim quotes (if any)
I could go on and on but you should just research it yourself.
the 1>nul and 2>nul make it so no output is displayed.
the ^> in the start are so the > are passed to the start command, not interpreted. the cmd /c starts a new shell that executes the code after the /c and then exits.

Waiting for parallel batch scripts

I have 4 batch files. I want to run one.bat and two.bat at once, concurrently. After completion of these two batch files, three.bat and four.bat should run at once, in parallel. I tried with many ways but mot works fine.
Can anyone help me over this?
This is easily done using a much simplified version of a solution I provided for Parallel execution of shell processes. Refer to that solution for an explanation of how the file locking works.
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
:: Launch one and two asynchronously, with stream 9 redirected to a lock file.
:: The lock file will remain locked until the script ends.
start "" cmd /c 9>"%lock%1" one.bat
start "" cmd /c 9>"%lock%2" two.bat
:Wait for both scripts to finish (wait until lock files are no longer locked)
1>nul 2>nul ping /n 2 ::1
for %%N in (1 2) do (
( rem
) 9>"%lock%%%N" || goto :Wait
) 2>nul
::delete the lock files
del "%lock%*"
:: Launch three and four asynchronously
start "" cmd /c three.bat
start "" cmd /c four.bat
I had this same dilemma. Here's the way I solved this issue.
I used the Tasklist command to monitor whether the process is still running or not:
:Loop
tasklist /fi "IMAGENAME eq <AAA>" /fi "Windowtitle eq <BBB>"|findstr /i /C:"<CCC>" >nul && (
timeout /t 3
GOTO :Loop
)
echo one.bat has stopped
pause
You'll need to tweak the
<AAA>, <BBB>, <CCC>
values in the script so that it's correctly filtering for your process.
Hope that helps.
Create a master.bat file that starts one.bat and two.bat. When one.bat and two.bat end correctly, they echo to file they have finished
if errorlevel 0 echo ok>c:\temp\OKONE
if errorlevel 0 echo ok>c:\temp\OKTWO
Then the master.bat wait for the existence of the two files
del c:\temp\OKONE
del c:\temp\OKTWO
start one.bat
start two.bat
:waitloop
if not exist c:\temp\OKONE (
sleep 5
goto waitloop
)
if not exist c:\temp\OKTWO (
sleep 5
goto waitloop
)
start three.bat
start four.bat
Another way is to try with the /WAIT flag
start /WAIT one.bat
start /WAIT two.bat
but you don't have any control on errors.
Here's some references
http://malektips.com/xp_dos_0002.html
http://ss64.com/nt/sleep.html
http://ss64.com/nt/start.html
Just adding another way, maybe the shortest.
(one.cmd | two.cmd) && (three.cmd | four.cmd)
Concept is really straight forward. Start one and 2 in paralel, once done and errorlevel is 0 run three and four.

Resources