Windows batch file looping Wait animation during processing command - windows

I have created the following batch file that makes Wait "| / -- \" animation. I want to use it during processing mysql restore database command mysql -u %DBuser% -p %DB% < "%thefile%" where thefile is the sql dump file path
#Echo OFF
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
SET p=-1
set num=1
set "st[1]=| "
set "st[2]=/ "
set "st[3]=--"
set "st[4]=\ "
if /i %p% lss 0 (
set p=2
call :LOOP
call :DoSomeThing
)
:LOOP
if /i %num% lss 4 (
set /a num=num+1
) else (
set num=1
)
<nul set /P "=Wait !st[%num%]!!CR!"
TIMEOUT /T 1 >NUL
GOTO :LOOP
:DoSomeThing
TIMEOUT /T 10 >NUL
echo Doing...
Here, :DoSomeThing is for testing purposes and It should be replaced or include the mysql command. I get the problem that :LOOP works for ever and there is no call to :DoSomeThing
I tried to call :DoSomeThing before call :LOOP but the LOOP started after DoSomeThing is finished so it becomes useless! Is there any way to make the DoSomeThing or the MySQL command works in the background while the animation wait loop works too?

EDIT: Some explanations added
In order to fulfill your request it is necessary to execute two threads simultanously, so one thread execute the mysql command and the other thread execute the looping wait animation. Both threads can be synchronized using a flag file that is created before mysql execution starts and is deleted after it ends, so the wait animation loops until the flag file is deleted.
The way to create a new thread is via start command, that may run a second Batch file that execute the mysql command and delete the flag file. However, in order to keep all the code in the same place, the start command may run the same Batch file (represented by "%~F0" in the code below). The key that allows this trick to work is a special parameter that indicate if the Batch file was re-executed from inside itself, so in this case the code just goto the section that execute the mysql command and delete the flag file.
#Echo OFF
rem If the Batch file was re-executed with the special parameter (second thread)
rem go to the section that execute the mysql command
if "%~1" equ ":DoSomething" goto %1
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
set num=0
set "st[0]=| "
set "st[1]=/ "
set "st[2]=--"
set "st[3]=\ "
rem Do here anything you want before the mysql command...
echo Doing something for 10 seconds...
rem Create the flag file
echo X > DoingSomething
rem Re-start this Batch file with the special parameter
start "" /B "%~F0" :DoSomething
rem Simultaneously execute the waiting animation
call :LOOP
rem Do here anything you want after the mysql command...
rem ... and terminate
goto :EOF
:LOOP
set /a num=(num+1) %% 4
<nul set /P "=Wait !st[%num%]!!CR!"
TIMEOUT /T 1 >NUL
IF EXIST DoingSomething GOTO :LOOP
echo Ending loop
goto :EOF
:DoSomeThing
rem Place here the mysql command
TIMEOUT /T 10 >NUL
rem Delete the flag file
del DoingSomething
rem And terminate the second thread
goto :EOF

Related

Batch File to execute all .exe in a folder with count

I have quite a few exe files, I want to run them with a single batch file. As far as I understand, these two codes work for me;
for %%a in ("\*.exe") do start "" "%%\~fa"
for %%i in (\*.exe) do start "" /b "%%i"
But that cmd screen closes when all files are run. What I want is this: That cmd screen will not close when the process is finished and will show me the result (counting if possible), a code that can count how many of these .exe files work and how many fail.
So for example;
87 files blocked
13 files could not be blocked
Something like this? Is this possible?
Maybe you can get an inspiration from this batch below. It works through program exit code. It spawns all executable, wait for their completion, then count how much failed / succeeded.
It should also work with well-designed GUI program, not only command-line based ones.
It's a rough/basic answer, you may need to refine it according to your exact needs.
#echo off
setlocal enableextensions enabledelayedexpansion
REM Use marker files for getting results.
set OK_EXT=.SUCCESS
set FAIL_EXT=.FAILURE
REM Purge all possible marker files.
del /q *!OK_EXT! *!FAIL_EXT! > NUL 2>&1
set /a count=0
REM Parse all executables
for %%E in (*.exe) do (
echo Launching: %%~nxE
REM Create two marker files for each executable.
echo.>%%~nE!OK_EXT!
echo.>%%~nE!FAIL_EXT!
REM Start the executable, delete the WRONG marker.
REM I would have prefered to use "touch" to create the good one instead, but not standard on Windows.
start %comspec% /C "%%~nxE && ( del /q %%~nE!FAIL_EXT! ) || ( del /q %%~nE!OK_EXT! )"
set /a count +=1
)
REM Now, "count" contains the number of executables launched.
echo All processes launched.
echo.
:loop
echo Waiting for results...
set /a curr=0
REM Simply count the number of marker files. Must be equal to "count" when everything is finished.
for /F "usebackq tokens=*" %%C in (`dir /b *!OK_EXT! *!FAIL_EXT!`) do (
set /A curr+=1
)
if !curr! GTR !count! (
set /a curr-=!count!
echo Still !curr! processes running...
timeout /t 2
goto :loop
)
echo All results found.
echo.
echo Parsing results...
set /a ok_exe=0
set ok_exe_list=
set /a fail_exe=0
set fail_exe_list=
REM Parse all marker files.
for /F "usebackq tokens=*" %%C in (`dir /b *!OK_EXT! *!FAIL_EXT!`) do (
REM And set counters + list according to the marker file type (OK or FAILED).
if /I "%%~xC"=="!OK_EXT!" (
set /A ok_exe+=1
set ok_exe_list=!ok_exe_list! %%~nC
) else (
set /A fail_exe+=1
set fail_exe_list=!fail_exe_list! %%~nC
)
)
REM Simple display.
echo Programs without error: !ok_exe!/!count!
echo !ok_exe_list!
echo.
echo Programs with error: !fail_exe!/!count!
echo !fail_exe_list!
echo.
goto :eof

Executing shell commands in parallel but limiting jobs (Windows without Cygwin)

Here is what I am trying to do. Suppose I have a program called myprogram.exe, which I have to execute 1000 times.
Under Windows, I could usually do something as simple as:
for /L %n in (1,1,1000) do start /myfolder/myprogram.exe
However, suppose I only have 5 CPU threads I can devote to running the 1000 instances of myprogram.exe, such that I launch only 5, then when one of these finishes another one is launched, etc until the whole 1000 end.
Under Linux and using GNU Parallel, I could simply do:
seq 1000 | parallel -N0 -j5 "nohup myprogram.exe"
How could I achieve something like that in Windows command line? Notice that in my case using Cygwin is not an option, so resorting to xargs and GNU Parallel under Windows are not options either.
Here is a way, by using powershell to do the process count. and using a simply set /a as counter.
#echo off
setlocal enabledelayedexpansion
set /a cnt=0
:counter
if !cnt! lss 1000 (
for /F "tokens=*" %%i in ('powershell ^(Get-Process -Name 'myprogram'^).count') do set proc=%%i
if !proc! lss 5 (
start "C:\myfolder\myprogram.exe"
set /a cnt+=1
)
goto :counter
)
You could add echo !cnt! in the line before goto :counter if you want to see it count.
It can be done without using delayedexpansion but I prefer to use it here.
This will run five processes in parallel. Each time one of them finishes, the next process will be started (so there are always 5 of them until they all are done)
#ECHO off
setlocal enabledelayedexpansion
set bunch=5
for /l %%a in (1,1,1000) do (
call :loop
echo processing: %%a
start "MyCommand" cmd /c timeout !random:~-1!
)
call :loop
goto :eof
:loop REM waits for available slot
for /f %%x in ('tasklist /fi "windowtitle eq MyCommand" ^| find /c "cmd.exe"') do set x=%%x
if %x% geq %bunch% goto :loop
goto :eof
Add the /min switch to start to minimize the started processes.
Give them a unique windowtitle (MyCommand here) to be able to count them.
Replace cmd /c timeout !random:~-1! with your actual command.
EDIT a slightly modified script, which may work better, if myprogram is a GUI (script above will work better with CLI applications):
#ECHO off
setlocal enabledelayedexpansion
set bunch=5
for /l %%a in (1,1,100) do (
call :loop
echo processing: %%a
start notepad.exe
)
call :loop
goto :eof
:loop REM waits for available slot
for /f %%x in ('tasklist /fi "imagename eq Notepad.exe"^|find /c "."') do set x=%%x
if %x% geq %bunch% goto :loop
goto :eof

Windows batch file script IF does not work

I have tried to make Windows batch file that blinking the word "Wait" & "Wait..". I tried the following code:
#Echo OFF
setlocal EnableDelayedExpansion
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
SET p=-1
set num=2
set st[1]=Wait
set st[2]=Wait..
set st[3]=eer
:LOOP
if /i %num% equ 1 (
set num=2
) else (
set num=1
)
<nul set /P "=!st[%num%]!!CR!"
TIMEOUT /T 1 >NUL
GOTO :LOOP
The problem here, the IF seems to be worked only one time. i.e running the batch makes it prompt "Wait" only one time, then "Wait.." forever. What is the mistake here?
You problem is not the if (that works as intended), your problem are the spaces that should delete/overwrite the dots.
set "st[1]=Wait "
set "st[2]=Wait.."

Limiting the number of spawned processes in batch script

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

How can I copy a statically named file to another location with increasing numerical suffixes?

I've got three files in a directory that appear via another process:
c:\result\results-a.txt
c:\result\results-b.txt
c:\result\results-c.txt
Each time they all appear, I'd like to copy them to another directory with an increasing numerical suffix/prefix, once the files are copied they can be deleted. Every time the batch file starts, it can start with the number 0 (it doesn't have to scan the target directory and continue).
Ex. The first time the files all appear, the target directory might look like this:
c:\archive\results-a.0000.txt
c:\archive\results-b.0000.txt
c:\archive\results-c.0000.txt
The second time they appear, the target directory would then contain:
c:\archive\results-a.0000.txt
c:\archive\results-b.0000.txt
c:\archive\results-c.0000.txt
c:\archive\results-a.0001.txt
c:\archive\results-b.0001.txt
c:\archive\results-c.0001.txt
And so on. I'm comfortable piecing this together in a BASH enviroment, but my client requires this be done on a Windows NT (Windows 7, actually) machine. Could someone get me started?
[Edit - Answer]
Thanks to Joey below, this is what I ended up coding.
#echo off
setlocal enabledelayedexpansion
set Counter=0
:loop
call :test_file %1\results1.txt
call :test_file %1\results2.txt
call :test_file %1\results3.txt
timeout 2 /nobreak >nul
call :movefiles
timeout 2 /nobreak >nul
goto loop
:test_file
timeout 2 /nobreak >nul
if not exist %1 goto :test_file
goto :eof
:lz
set LZ=000%Counter%
set LZ=%LZ:~-4%
goto :eof
:movefiles
for %%f in (C:\test\*.txt) do (
call :lz
move "%%f" "c:\tmp\c-!LZ!-%%~nxf"
)
set /a Counter+=1
goto :eof
A very nice introduction to batch programming. Thanks.
You need a few pieces for this to work.
First of all, a counter:
set Counter=0
Then a subroutine that pads the value with leading zeroes:
:lz
set LZ=000%Counter%
set LZ=%LZ:~-4%
goto :eof
The %LZ:~-4% is a substring operation that retains the last four characters of the variable value. In this case this is a number, zero-padded to four places.
A loop that checks for files in a certain location:
:loop
if exist c:\result\*.txt call :movefiles
timeout 2 /nobreak >nul
goto loop
Fairly readable, this one, I guess.
A subroutine that moves the files away:
:movefiles
setlocal enabledelayedexpansion
for %%f in (C:\result\*.txt) do (
rem Generate the zero-padded number
call :lz
move "%%f" "some\target\directory\%%~nf.!LZ!%%~xf"
)
endlocal
rem Increment the counter for next use
set /a Counter+=1
goto :eof
Piecing all that together leaves you with
#echo off
setlocal enabledelayedexpansion
set Counter=0
:loop
if exist c:\result\*.txt call :movefiles
timeout 2 /nobreak >nul
goto loop
:lz
set LZ=000%Counter%
set LZ=%LZ:~-4%
goto :eof
:movefiles
for %%f in (C:\result\*.txt) do (
call :lz
move "%%f" "some\target\directory\%%~nf.!LZ!%%~xf"
)
set /a Counter+=1
goto :eof
It can be adapted to remember its last value. However, this will only work if the batch file resides in a writable location.
#echo off
setlocal enabledelayedexpansion
set Counter=0
call :init
:loop
if exist c:\result\*.txt call :movefiles
timeout 2 /nobreak >nul
goto loop
:lz
set LZ=000%Counter%
set LZ=%LZ:~-4%
goto :eof
:movefiles
for %%f in (C:\result\*.txt) do (
call :lz
move "%%f" "some\target\directory\%%~nf.!LZ!%%~xf"
)
set /a Counter+=1
>>%~dpnx0 echo set Counter=%Counter%
goto :eof
:init
Note that the last line (:init) must be terminated with a line break (or better two; I had some issues sometimes with just one in my testing here). This essentially creates a subroutine at the end of the batch file that sets the counter repeatedly until it arrives at its last value.
It isn't exactly fast, though. There will be one set call per counter increment at the end, and all those will be run initially.
Here's something that should get you started. Drop this in a file called incrementName.bat and then run it several times in succession.
#echo off
goto :start
--------------------------------------------
incrementName.bat
shows how to generate a filename with a monotonically
increasing numeric portion.
Run this several times in succession to see it in action.
Mon, 18 Apr 2011 12:51
--------------------------------------------
:START
setlocal enabledelayedexpansion
call :GETNEXTFILENAME rammalamma.txt
echo Next: %nextname%
#REM copy self to that new name.
copy %0 %nextname%
GOTO END
--------------------------------------------
:GETNEXTFILENAME
#REM this is a subroutine.
#REM %1 is the basename. This logic assumes a 3-character
#REM filename extension.
set fname=%1
set ext=%fname:~-3%
set base=%fname:~0,-4%
set idx=0
:toploop1
#set NUM=00000!idx!
set nextname=%base%.!NUM:~-5!.%ext%
if EXIST !nextname! (
if !idx! GTR 99999 goto:FAIL
set /a idx=!idx! + 1
goto:toploop1
)
)
:Success
goto:EOF
:Fail - overflow
set nextname=%base%.xxxxx.%ext%
goto:EOF
--------------------------------------------
:END
endlocal

Resources