I wanted to call waiting spinner on my batch script like this is my code:
#echo off
::-----------------------------Waiting-Spinner-------------------------------
:spinner
set mSpinner=%mSpinner%.
if %mSpinner%'==....' (set mSpinner=.)
cls
::----------------------------Subdomain-Script-------------------------------
echo Enumerating Subdomains From Script1 %mSpinner%
python2 enumsubdomain.py google.com > google.txt
SLEEP 1
goto spinner
echo Enumerating Subdomains From Script2 %mSpinner%
python2 enumsubdomain2.py yahoo.com > yahoo.txt
SLEEP 1
goto spinner
#pause
And this spinner text output should be something like this:
Enumerating Subdomains From Script1 ...<Here this dot will be animated]
Enumerating Subdomains From Script2 ...<Here this dot will be animated]
But it only outputs first line(Script1) and the 2nd script stops and doesn't outputs the 2nd line as well, i guess it's because of goto line in batch script and i have no idea what can be done here to make it work!
After clarification in your comment, you'd probably want something like this.
We start the commands in separate windows with new window titles per window, then we use tasklist to determine of either are still running. So both results will be echo if both run, or only one will be echod if only one runs.
#echo off
set done1=1
set done2=1
start "enum1" /min cmd.exe /C ^(python2 enumsubdomain.py google.com ^> google.txt^)
start "enum2" /min cmd.exe /C ^(python2 enumsubdomain2.py yahoo.com ^> yahoo.txt^)
:spinner
set mSpinner=%mSpinner%.
if "%mSpinner%"=="...." (set mSpinner=.)
cls
(tasklist /FI "WINDOWTITLE eq enum1" | findstr /i "cmd")>nul && echo Enumerating Subdomains From Script1 %mSpinner% || set done1=0
(tasklist /FI "WINDOWTITLE eq enum2" | findstr /i "cmd")>nul && echo Enumerating Subdomains From Script2 %mSpinner% || set done2=0
if %done1% equ 0 if %done2% equ 0 goto :eof
(sleep 1)>nul
goto :spinner
You want to use the spinner several times, so using a subroutine for the spinner is a good idea.
The main problem is: batch waits for a command to end before continuing with the next one, so you can't include the python command into the loop. Solution: start the command as an independent process and then start the "spinning loop". Break it, when the independent process does not exist anymore.
Here the subprocess :spinner takes a string as parameter (the message), making it flexible.
Instead of clearing the screen (cls) for each iteration of the loop, I took a different approach: overwriting the line over and over (less flickering and keeping the previous screen intact)
#echo off
setlocal EnableDelayedExpansion
REM create a CariageReturn:
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
cls
start /min "MySpinnerProcess" cmd /c "timeout 8 >google.txt"
call :spinner "Enumerating Subdomains From Script1"
start /min "MySpinnerProcess" cmd /c "timeout 5 >yahoo.txt"
call :spinner "Enumerating Subdomains From Script2"
echo finished.
goto :eof
:spinner
tasklist /fi "WindowTitle eq MySpinnerProcess" 2>nul | findstr /bilc:"cmd.exe" >nul || (
echo %~1 done.
goto :eof
)
set "mspinner=%mspinner%."
if %mspinner% == .... set "mspinner=."
<nul set /p ".=%~1 %mspinner% !CR!"
timeout 1 >nul
goto :spinner
For completeness my script for running the two scripts simultaneously. (very similar to Gerhard's solution, but keeping two spinners) (in my defense: I already had it ready when I asked you if you want them one after the other or simultaneously, but I just had to spend the sunny Sunday Afternoon outside)
#echo off
setlocal
::---------Waiting-Spinner---------
start /min "MyPhytonProcess1" cmd /c "timeout 8 >google.txt"
start /min "MyPhytonProcess2" cmd /c "timeout 5 >yahoo.txt"
:spinner
set mSpinner1=%mSpinner1%.
set mSpinner2=%mSpinner2%.
if %mSpinner1%==.... (set mSpinner1=.)
if %mSpinner2%==.... (set mSpinner2=.)
cls
::----------------------------Subdomain-Script-------------------------------
echo Enumerating Subdomains From Script1 %mSpinner1%
echo Enumerating Subdomains From Script2 %mSpinner2%
if "%mSpinner1%%mSpinner2%" == "Done.Done." goto :done
tasklist /FI "WindowTitle eq MyPhytonProcess1" 2>nul |find "cmd.exe" >nul || set "mSpinner1=Done"
tasklist /FI "WindowTitle eq MyPhytonProcess2" 2>nul |find "cmd.exe" >nul || set "mSpinner2=Done"
timeout 1 >nul
goto :spinner
:done
echo finished.
#pause
This question already has answers here:
Parallel execution of shell processes
(6 answers)
Closed 4 years ago.
I have this batch command
#echo off
FOR %%i in (C:\input\*.*) DO (
echo processing %%i
if not exist "C:\output\%%i" process.exe "%%i" -out "C:\output\%%i"
)
echo ---- finished ----
pause
Here my tool process.exe processes in a loop all files within a directory - if a result doesn't already exist.
Now my CPU is fast enough to run this process.exe on 2 or 3 files at the same time which would make the processing of the files much faster.
Question: How do I have to change the command to make my batch file processing 2-3 files at the same time?
The following starts processes up to a max count of %bunch%. Whenever one of them finishes, another one will be started.
#ECHO off
setlocal enabledelayedexpansion
set bunch=3
for %%a in (C:\input\*) do (
call :loop
echo processing: %%a
start "MyCommand" cmd /c timeout 60
REM if not exist "C:\output\%%i" start "MyCommand" cmd /c process.exe "%%i" -out "C:\output\%%i"
)
call :loop
goto :eof
:loop REM waits for available slot
echo on
for /f %%x in ('tasklist /fi "windowtitle eq MyCommand" ^| find /c "cmd.exe"') do set x=%%x
if %x% geq %bunch% goto :loop
echo off
goto :eof
I don't have your process.exe, so I have to guess. but the REMed line should work for you. (the timeout command is just to show the princip)
How can I check if an application is running from a batch (well cmd) file?
I need to not launch another instance if a program is already running. (I can't change the app to make it single instance only.)
Also the application could be running as any user.
Another possibility I came up with, which does not require to save a file, inspired by using grep is:
tasklist /fi "ImageName eq MyApp.exe" /fo csv 2>NUL | find /I "myapp.exe">NUL
if "%ERRORLEVEL%"=="0" echo Program is running
/fi "" defines a filter of apps to find, in our case it's the *.exe name
/fo csv defines the output format, csv is required because by default the name of the executable may be truncated if it is too long and thus wouldn't be matched by find later.
find /I means case-insensitive matching and may be omitted
See the man page of the tasklist command for the whole syntax.
Here's how I've worked it out:
tasklist /FI "IMAGENAME eq notepad.exe" /FO CSV > search.log
FOR /F %%A IN (search.log) DO IF %%~zA EQU 0 GOTO end
start notepad.exe
:end
del search.log
The above will open Notepad if it is not already running.
Edit: Note that this won't find applications hidden from the tasklist. This will include any scheduled tasks running as a different user, as these are automatically hidden.
I like Chaosmaster's solution! But I looked for a solution which does not start another external program (like find.exe or findstr.exe). So I added the idea from Matt Lacey's solution, which creates an also avoidable temp file. At the end I could find a fairly simple solution, so I share it...
SETLOCAL EnableExtensions
set EXE=MyProg.exe
FOR /F %%x IN ('tasklist /NH /FI "IMAGENAME eq %EXE%"') DO IF NOT %%x == %EXE% (
echo %EXE% is Not Running
)
This is working for me nicely...
The above is an edit. The original code apparently had a GOTO in it, which someone in the comments thought uncouth.
Spaces
If you are concerned that the program name may have spaces in it then you need to complicate the code very slightly:
SETLOCAL EnableExtensions
set EXE=My Prog.exe
FOR /F %%x IN ("%EXE%") do set EXE_=%%x
FOR /F %%x IN ('tasklist /NH /FI "IMAGENAME eq %EXE%"') DO IF NOT %%x == %EXE_% (
echo %EXE% is Not Running
)
The original code will work fine whether or not other running processes have spaces in their names. The only concern is whether or not the process we are targeting has space(s).
ELSE
Keep in mind that if you add an ELSE clause then it will be executed once for every instance of the application that is already running. There is no guarantee that there be only a single instance running when you run this script.
Should you want one anyway, either a GOTO or a flag variable is indicated.
Ideally the targeted application should already mutex itself to prevent multiple instances, but that is a topic for another SO question and is not necessarily applicable to the subject of this question.
GOTO again
I do agree with the "ELSE" comment. The problem with the GOTO-less solution, that is may run the condition part (and the ELSE part) multiple times, so it is a bit messy as it has to quit the loop anyway. (Sorry, but I do not deal with the SPACE issue here, as it seems to be pretty rare and a solution is shown for it)
SETLOCAL EnableExtensions
SET EXE=MyProg.exe
REM for testing
REM SET EXE=svchost.exe
FOR /F %%x IN ('tasklist /NH /FI "IMAGENAME eq %EXE%"') DO IF NOT %%x == %EXE% (
ECHO %EXE% is Not Running
REM This GOTO may be not necessary
GOTO notRunning
) ELSE (
ECHO %EXE is running
GOTO Running
)
...
:Running
REM If Running label not exists, it will loop over all found tasks
The suggestion of npocmaka to use QPROCESS instead of TASKLIST is great but, its answer is so big and complex that I feel obligated to post a quite simplified version of it which, I guess, will solve the problem of most non-advanced users:
QPROCESS "myprocess.exe">NUL
IF %ERRORLEVEL% EQU 0 ECHO "Process running"
The code above was tested in Windows 7, with a user with administrator rigths.
TASKLIST | FINDSTR ProgramName || START "" "Path\ProgramName.exe"
Under Windows you can use Windows Management Instrumentation (WMI) to ensure that no apps with the specified command line is launched, for example:
wmic process where (name="nmake.exe") get commandline | findstr /i /c:"/f load.mak" /c:"/f build.mak" > NUL && (echo THE BUILD HAS BEEN STARTED ALREADY! > %ALREADY_STARTED% & exit /b 1)
TrueY's answer seemed the most elegant solution, however, I had to do some messing around because I didn't understand what exactly was going on. Let me clear things up to hopefully save some time for the next person.
TrueY's modified Answer:
::Change the name of notepad.exe to the process .exe that you're trying to track
::Process names are CASE SENSITIVE, so notepad.exe works but Notepad.exe does NOT
::Do not change IMAGENAME
::You can Copy and Paste this into an empty batch file and change the name of
::notepad.exe to the process you'd like to track
::Also, some large programs take a while to no longer show as not running, so
::give this batch a few seconds timer to avoid a false result!!
#echo off
SETLOCAL EnableExtensions
set EXE=notepad.exe
FOR /F %%x IN ('tasklist /NH /FI "IMAGENAME eq %EXE%"') DO IF %%x == %EXE% goto ProcessFound
goto ProcessNotFound
:ProcessFound
echo %EXE% is running
goto END
:ProcessNotFound
echo %EXE% is not running
goto END
:END
echo Finished!
Anyway, I hope that helps. I know sometimes reading batch/command-line can be kind of confusing sometimes if you're kind of a newbie, like me.
I use PV.exe from http://www.teamcti.com/pview/prcview.htm installed in Program Files\PV with a batch file like this:
#echo off
PATH=%PATH%;%PROGRAMFILES%\PV;%PROGRAMFILES%\YourProgram
PV.EXE YourProgram.exe >nul
if ERRORLEVEL 1 goto Process_NotFound
:Process_Found
echo YourProgram is running
goto END
:Process_NotFound
echo YourProgram is not running
YourProgram.exe
goto END
:END
The answer provided by Matt Lacey works for Windows XP. However, in Windows Server 2003 the line
tasklist /FI "IMAGENAME eq notepad.exe" /FO CSV > search.log
returns
INFO: No tasks are running which match the specified criteria.
which is then read as the process is running.
I don't have a heap of batch scripting experience, so my soulution is to then search for the process name in the search.log file and pump the results into another file and search that for any output.
tasklist /FI "IMAGENAME eq notepad.exe" /FO CSV > search.log
FINDSTR notepad.exe search.log > found.log
FOR /F %%A IN (found.log) DO IF %%~zA EQU 0 GOTO end
start notepad.exe
:end
del search.log
del found.log
I hope this helps someone else.
I like the WMIC and TASKLIST tools but they are not available in home/basic editions of windows.Another way is to use QPROCESS command available on almost every windows machine (for the ones that have terminal services - I think only win XP without SP2 , so practialy every windows machine):
#echo off
:check_process
setlocal
if "%~1" equ "" echo pass the process name as forst argument && exit /b 1
:: first argument is the process you want to check if running
set process_to_check=%~1
:: QPROCESS can display only the first 12 symbols of the running process
:: If other tool is used the line bellow could be deleted
set process_to_check=%process_to_check:~0,12%
QPROCESS * | find /i "%process_to_check%" >nul 2>&1 && (
echo process %process_to_check% is running
) || (
echo process %process_to_check% is not running
)
endlocal
QPROCESS command is not so powerful as TASKLIST and is limited in showing only 12 symbols of process name but should be taken into consideration if TASKLIST is not available.
More simple usage where it uses the name if the process as an argument (the .exe suffix is mandatory in this case where you pass the executable name):
#echo off
:check_process
setlocal
if "%~1" equ "" echo pass the process name as forst argument && exit /b 1
:: first argument is the process you want to check if running
:: .exe suffix is mandatory
set "process_to_check=%~1"
QPROCESS "%process_to_check%" >nul 2>&1 && (
echo process %process_to_check% is running
) || (
echo process %process_to_check% is not running
)
endlocal
The difference between two ways of QPROCESS usage is that the QPROCESS * will list all processes while QPROCESS some.exe will filter only the processes for the current user.
Using WMI objects through windows script host exe instead of WMIC is also an option.It should on run also on every windows machine (excluding the ones where the WSH is turned off but this is a rare case).Here bat file that lists all processes through WMI classes and can be used instead of QPROCESS in the script above (it is a jscript/bat hybrid and should be saved as .bat):
#if (#X)==(#Y) #end /* JSCRIPT COMMENT **
#echo off
cscript //E:JScript //nologo "%~f0"
exit /b
************** end of JSCRIPT COMMENT **/
var winmgmts = GetObject("winmgmts:\\\\.\\root\\cimv2");
var colProcess = winmgmts.ExecQuery("Select * from Win32_Process");
var processes = new Enumerator(colProcess);
for (;!processes.atEnd();processes.moveNext()) {
var process=processes.item();
WScript.Echo( process.processID + " " + process.Name );
}
And a modification that will check if a process is running:
#if (#X)==(#Y) #end /* JSCRIPT COMMENT **
#echo off
if "%~1" equ "" echo pass the process name as forst argument && exit /b 1
:: first argument is the process you want to check if running
set process_to_check=%~1
cscript //E:JScript //nologo "%~f0" | find /i "%process_to_check%" >nul 2>&1 && (
echo process %process_to_check% is running
) || (
echo process %process_to_check% is not running
)
exit /b
************** end of JSCRIPT COMMENT **/
var winmgmts = GetObject("winmgmts:\\\\.\\root\\cimv2");
var colProcess = winmgmts.ExecQuery("Select * from Win32_Process");
var processes = new Enumerator(colProcess);
for (;!processes.atEnd();processes.moveNext()) {
var process=processes.item();
WScript.Echo( process.processID + " " + process.Name );
}
The two options could be used on machines that have no TASKLIST.
The ultimate technique is using MSHTA . This will run on every windows machine from XP and above and does not depend on windows script host settings. the call of MSHTA could reduce a little bit the performance though (again should be saved as bat):
#if (#X)==(#Y) #end /* JSCRIPT COMMENT **
#echo off
setlocal
if "%~1" equ "" echo pass the process name as forst argument && exit /b 1
:: first argument is the process you want to check if running
set process_to_check=%~1
mshta "about:<script language='javascript' src='file://%~dpnxf0'></script>" | find /i "%process_to_check%" >nul 2>&1 && (
echo process %process_to_check% is running
) || (
echo process %process_to_check% is not running
)
endlocal
exit /b
************** end of JSCRIPT COMMENT **/
var fso= new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1);
var winmgmts = GetObject("winmgmts:\\\\.\\root\\cimv2");
var colProcess = winmgmts.ExecQuery("Select * from Win32_Process");
var processes = new Enumerator(colProcess);
for (;!processes.atEnd();processes.moveNext()) {
var process=processes.item();
fso.Write( process.processID + " " + process.Name + "\n");
}
close();
I don't know how to do so with built in CMD but if you have grep you can try the following:
tasklist /FI "IMAGENAME eq myApp.exe" | grep myApp.exe
if ERRORLEVEL 1 echo "myApp is not running"
Just mentioning, if your task name is really long then it won't appear in its entirety in the tasklist result, so it might be safer (other than localization) to check for the opposite.
Variation of this answer:
:: in case your task name is really long, check for the 'opposite' and find the message when it's not there
tasklist /fi "imagename eq yourreallylongtasknamethatwontfitinthelist.exe" 2>NUL | find /I /N "no tasks are running">NUL
if "%errorlevel%"=="0" (
echo Task Found
) else (
echo Not Found Task
)
If you have more than one .exe-file with the same name and you only want to check one of them (e.g. you care about C:\MyProject\bin\release\MyApplication.exe but not C:\MyProject\bin\debug\MyApplication.exe) then you can use the following:
#echo off
set "workdir=C:\MyProject\bin\release"
set "workdir=%workdir:\=\\%"
setlocal enableDelayedExpansion
for /f "usebackq tokens=* delims=" %%a in (`
wmic process where 'CommandLine like "%%!workdir!%%" and not CommandLine like "%%RuntimeBroker%%"' get CommandLine^,ProcessId /format:value
`) do (
for /f "tokens=* delims=" %%G in ("%%a") do (
if "%%G" neq "" (
rem echo %%G
set "%%G"
rem echo !ProcessId!
goto :TheApplicationIsRunning
)
)
)
echo The application is not running
exit /B
:TheApplicationIsRunning
echo The application is running
exit /B
I needed a solution with a retry. This code will run until the process is found and then kill it. You can set a timeout or anything if you like.
Notes:
The ".exe" is mandatory
You could make a file runnable with parameters, version below
:: Set programm you want to kill
:: Fileextension is mandatory
SET KillProg=explorer.exe
:: Set waiting time between 2 requests in seconds
SET /A "_wait=3"
:ProcessNotFound
tasklist /NH /FI "IMAGENAME eq %KillProg%" | FIND /I "%KillProg%"
IF "%ERRORLEVEL%"=="0" (
TASKKILL.EXE /F /T /IM %KillProg%
) ELSE (
timeout /t %_wait%
GOTO :ProcessNotFound
)
taskkill.bat:
:: Get program name from argumentlist
IF NOT "%~1"=="" (
SET "KillProg=%~1"
) ELSE (
ECHO Usage: "%~nx0" ProgramToKill.exe & EXIT /B
)
:: Set waiting time between 2 requests in seconds
SET /A "_wait=3"
:ProcessNotFound
tasklist /NH /FI "IMAGENAME eq %KillProg%" | FIND /I "%KillProg%"
IF "%ERRORLEVEL%"=="0" (
TASKKILL.EXE /F /T /IM %KillProg%
) ELSE (
timeout /t %_wait%
GOTO :ProcessNotFound
)
Run with .\taskkill.bat ProgramToKill.exe
I'm assuming windows here. So, you'll need to use WMI to get that information. Check out The Scripting Guy's archives for a lot of examples on how to use WMI from a script.
I used the script provided by Matt (2008-10-02). The only thing I had trouble with was that it wouldn't delete the search.log file. I expect because I had to cd to another location to start my program. I cd'd back to where the BAT file and search.log are, but it still wouldn't delete. So I resolved that by deleting the search.log file first instead of last.
del search.log
tasklist /FI "IMAGENAME eq myprog.exe" /FO CSV > search.log
FOR /F %%A IN (search.log) DO IF %%-zA EQU 0 GOTO end
cd "C:\Program Files\MyLoc\bin"
myprog.exe myuser mypwd
:end
Building on vtrz's answer and Samuel Renkert's answer on an other topic, I came up with the following script that only runs %EXEC_CMD% if it isn't already running:
#echo off
set EXEC_CMD="rsync.exe"
wmic process where (name=%EXEC_CMD%) get commandline | findstr /i %EXEC_CMD%> NUL
if errorlevel 1 (
%EXEC_CMD% ...
) else (
#echo not starting %EXEC_CMD%: already running.
)
As was said before, this requires administrative privileges.
I usually execute following command in cmd prompt to check if my program.exe is running or not:
tasklist | grep program
You should check the parent process name, see The Code Project article about a .NET based solution**.
A non-programmatic way to check:
Launch Cmd.exe
Launch an application (for instance, c:\windows\notepad.exe)
Check properties of the Notepad.exe process in Process Explorer
Check for parent process (This shows cmd.exe)
The same can be checked by getting the parent process name.
I'm trying to mass encode video files in x265 and wanted to come up with a batch file that could automate the process. In order to expedite things, I found that having 3 instances of ffmpeg called with 2 threads resulted in ideal encoding times, however I've tried all day yesterday to come up with a way to get a batch file that will call 3 instances and then call new ones when they complete. So far, this is where I am:
PARENT BATCH
#echo off
Setlocal EnableDelayedExpansion
SET /A COUNT=0
for %%a in (*.mkv) do (
CALL :CHECK
SET /A COUNT+=1
START CALL "child.bat" "%%a"
)
EXIT /B 0
:CHECK
IF !COUNT! EQU 3 (
TIMEOUT /T 5
GOTO :CHECK
)
EXIT /B 0
CHILD BATCH
ffmpeg <COMMAND LINE ARGS>
SET /A COUNT-=1
EXIT /B 0
I have two problems. 1) The COUNT variable isn't being updated in the parent process and it never spawns new instances when the child processes finish. 2) The child process doesn't cleanly exit. It leaves a separate cmd.exe window open with a DOS prompt.
Any ideas?
Edit: Replaced nested GOTO to prevent FOR loop breakage
Workaround below
Setlocal EnableDelayedExpansion
SET /A COUNT=0
for %%a in (*.mkv) do (
IF !COUNT! EQU 3 CALL :CHECK
SET /A COUNT+=1
START CALL "child.bat" "%%a"
)
EXIT /B 0
:CHECK
IF EXIST DONE.TXT (
SET /A COUNT-=1
DEL DONE.TXT
EXIT /B 0
) ELSE (
TIMEOUT /T 5
GOTO :CHECK
)
EXIT /B 0
CHILD BATCH
ffmpeg <COMMAND LINE ARGS>
:DONE
IF EXIST DONE.TXT (
TIMEOUT /T 1
GOTO :DONE
)
echo 1 >> DONE.TXT
EXIT 0
Regarding your stated problems:
1) A child process cannot modify environment variables in the parent process. You will need a different mechanism to detect when the child has terminated. Also, as Squashman states in his comment, a GOTO within a loop will break (terminate) the loop, which is why no new child processes are launched after the first 3.
2) Your child window does not terminate because you use EXIT /B. Use EXIT instead and the window will close.
You have a long way to go before you have a working solution ;-)
Perhaps the biggest hurdle is detecting when a child process terminates.
I know of 3 strategies:
1) Use TASKLIST coupled with FIND /C to count the number of ffmpeg processes that are currently running. This is perhaps the simplest solution, but it cannot differentiate between processes that your script launches vs processes that may have been launched by some other mechanism.
2) Use a file as a signal. Create an empty file for each process, and then when the process finishes, have it delete the file. Your main script can monitor which processes are active by looking for the files. This is also simple, but it does not behave well if one of your processes crashes before it can delete the file. That leaves your system in an unhealthy state.
3) My favorite is to use lock files. Each child process locks a file via redirection, and when the process terminates (crash, normal exit, it doesn't matter how), then the lock is released. The main process can attempt to lock the same files. It knows the child has terminated if the lock is successful, else the child is still running. This strategy is the most complicated, and it uses arcane syntax, but I find it highly effective.
I have already implemented a solution at Parallel execution of shell processes that uses option 3). Below is an adaptation/simplification of that code for your situation.
I launch each child process in the parent window using START /B, and I redirect all output to the lock file. When finished, I type the output file so you can see what happened. I also list the start and stop times for each child process.
You just need to adjust the 3 top environment variables to suit your needs. The remainder should be good to go. However, the code as written will fail if any file names contain the ! character. This limitation can be removed with a bit more work.
There is extensive documentation within the script. The %= COMMENT =%syntax is one way of safely embedding comments within a loop without using REM.
#echo off
setlocal enableDelayedExpansion
:: Define the command that will be run to obtain the list of files to process
set listCmd=dir /b /a-d *.mkv
:: Define the command to run for each file, where "%%F" is an iterated file name from the list
:: something like YOUR_COMMAND -i "%%F"
set runCmd=ffmpeg [** command arguments go here **]
:: Define the maximum number of parallel processes to run.
set "maxProc=3"
::---------------------------------------------------------------------------------
:: The remainder of the code should remain constant
::
:: 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
for /f "tokens=* delims=:" %%F in ('%listCmd%') do (
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set cmd!nextProc!=%runCmd%
echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting %runCmd%
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 >"%lock%!nextProc!" 2^>^&1 %runCmd%
)
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 2>nul (
%= 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" 9>>"%lock%%%N" (
%= Made it inside the IF body so the process must have finished =%
echo ===============================================================================
echo !time! - proc%%N: finished !cmd%%N!
type "%lock%%%N"
if defined launch (
set nextProc=%%N
exit /b
)
set /a "endCount+=1, endProc%%N=1"
)
)
if %endCount% lss %startCount% (
timeout /t 1 /nobreak >nul
goto :wait
)
2>nul del %lock%*
echo ===============================================================================
echo Thats all folks
Easy solution with a random window title (based on user2956477 idea):
set windowtitle=Worker_%random%%random%%random%
start /min cmd /c "title %windowtitle% & dosomething.bat file1"
start /min cmd /c "title %windowtitle% & dosomething.bat file2"
start /min cmd /c "title %windowtitle% & dosomethingelse.exe"
timeout 3 >nul
:LOOP
tasklist /V /FI "imagename eq cmd.exe" /FI "windowtitle eq %windowtitle%*" | findstr %windowtitle% >nul
if "%errorlevel%"=="0" timeout 1 >nul && goto LOOP
I have a situation very similar to the one described in this question (but in batch, not shell). I made a simple batch script to iterate through the lines of a tile and download data from a server using a python script (the process itself is more complicated than just a simple download, it has to authenticate with an API and fetch several URLs).
The first version was as follows:
for /F "tokens=*" %%A in (client_name_list.txt) do python download_metadata.py "%%A"
The way it is it waits until each iteration is done to move on, so I updated it to the following:
for /F "tokens=*" %%A in (client_name_list.txt) do start cmd /C python download_metadata.py "%%A"
The second versions does what I want to but, as the file client_name_list.txt is about 30,000 lines long, a lot of command prompts start spawning and the computers freezes within seconds.
How do I limit the number of running instances of CMD (to, for example 10) and make the script wait until there is a "free CMD slot" to go the next line?
Adapted from my answer to "Parallel execution of shell processes". Follow the link to get an explanation.
#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.
set "maxProc=10"
:: 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
for /f "delims=" %%A in (client_name_list.txt) do (
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set cmd!nextProc!=%%A
if defined showOutput echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting %%A
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 python download_metadata.py "%%A"
)
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 2>nul (
%= 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" 9>>"%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"
)
)
if %endCount% lss %startCount% (
1>nul 2>nul ping /n 2 ::1
goto :wait
)
2>nul del %lock%*
if defined showOutput echo ===============================================================================
echo Done
In each iteration of your for loop you can count the number of CMD task open. If the value is lesser than the limit you start a new task else you wait until one slot is free.
#echo off
set $Limit=11
setlocal enabledelayedexpansion
for /F "tokens=*" %%A in (client_name_list.txt) do (call:wait %%A)
exit/b
:wait
set "$cmd="
for /f %%a in ('tasklist ^| findstr /i "cmd"') do set /a $cmd+=1
if !$cmd! lss %$Limit% (
start cmd /C python download_metadata.py "%1"
goto:eof)
ping localhost -n 2 >nul
goto:wait