Here is the batch program:
#echo off
SETLOCAL EnableDelayedExpansion
set /a incr=0
set arg1=%1
del test_output.csv > NUL 2>&1
del test_output.log > NUL 2>&1
set startTime=%time%
for /d %%i in ("%cd%\*") do call :run_test "%%i"
.\log_parser.exe
type test_output.csv
echo Start Time: %startTime%
echo Finish Time: %time%
exit /B
:run_test
if not "%~nx1" == "shared" (
echo Test: %~nx1
cd %1
del pass_fail_output.csv > NUL 2>&1
echo Running...
cd temp_root_fs
start application.exe
set /a incr=0
:while1
tasklist /fi "IMAGENAME eq application.exe" 2>NUL | find /i /n "application.exe">NUL
if "%ERRORLEVEL%"=="0" (
if %incr% leq 60 (
echo Still running...
timeout /t 1 > NUL 2>&1
set /a incr+= 1
goto :while1
)
echo Test timed out...
taskkill /im application.exe /f
)
echo Test completed...
cd logs
.\pass_fail_parser.exe
type log.log >> ..\..\..\test_output.log
copy pass_fail_output.csv ..\..\
cd ..\..\
)
echo Cleaning...
rmdir /S /Q temp_root_fs
cd ..
)
This is my expected execution:
Loop through folders
Run the application
Wait for 60 seconds and either kill the application after 60 seconds passes or continue if the application finishes
Do some other executions to port log messages into an overall log file
The first loop works fine, but this is what my current output looks like when I execute it:
Test: test1
Initializing...
Running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Test completed...
1 file(s) copied.
Cleaning...
I know this is not working properly because I have 3 more folders for tests, so it should continue on to the other folders, but somehow it seems to break out of the for loop early.
I have read about the /I option that supposedly prevents the goto from breaking out of iff and do loops, but I am not entirely sure how it works (I tried adding it as a parameter but it either errors out or does not seem to do anything).
Any help would be greatly appreciated!
:run_test
if not "%~nx1" == "shared" (
echo Test: %~nx1
cd %1
del pass_fail_output.csv > NUL 2>&1
echo Running...
cd temp_root_fs
start application.exe
set /a incr=0
CALL :while1
echo Test completed...
cd logs
.\pass_fail_parser.exe
type log.log >> ..\..\..\test_output.log
copy pass_fail_output.csv ..\..\
cd ..\..\
)
echo Cleaning...
rmdir /S /Q temp_root_fs
cd ..
)
GOTO :EOF
:while1
tasklist /fi "IMAGENAME eq application.exe" 2>NUL | find /i /n "application.exe">NUL
if "%ERRORLEVEL%"=="0" (
if %incr% leq 60 (
echo Still running...
timeout /t 1 > NUL 2>&1
set /a incr+= 1
goto :while1
)
echo Test timed out...
taskkill /im application.exe /f
)
GOTO :EOF
In your code, if not "%~nx1" == "shared" ( to the final ) is a code block. Labels are not permitted within a code block. The values %var% are replaced when the if statement is parsed by the values of those variables at that time, not as the change due to actions executed within the block. Beware of the delayed expansion trap
The above code converts the :while1 loop to an internal subroutine invoked by CALL :while1 (the colon is required to indicate the call is to an internal label)
Note the GOTO :EOF statements. These transfer execution to the physical end-of-file (the colon is again, required) The first is to prevent execution from proceeding from :run_test into :while1 by flow-through. The second is to force a return to the statement following CALL :while1 when the :while1 routine finishes. The :while1 routine may be placed after any goto statement in the batch's mainline (ie. not a goto within a code block).
Related
So I'm working a project that make competitive judger from batch file, and it works well with correct and incorrect answer. But I stuck with my problem when I try to measure time execution this code. I used timeout and taskkill to delay it for an amount of time, and kill it if didn't finish.
But I faced two problem:
My code process can be killed twice, and it can crash my batch file.
It denied my input and output file, so I can't compare my file for verdict.
So how can I measure time, kill a process on Windows and get rid all of my problem?
Here is my code:
main.bat
#echo off
:init
set home=%cd%
chcp 65001>nul
set total=0
set pass=0
:input
set /p user="Enter username: "
if not exist .\submits\%user% (
echo [[91mERROR[0m] Invalid username.
pause 0
exit /b 1
)
set /p prob="Problem: "
if not exist .\problems\%prob% (
echo [[91mERROR[0m] Invalid problem.
pause 0
exit /b 1
)
if not exist .\submits\%user%\%prob%.cpp (
echo [[91mERROR[0m] User haven't submited this problem yet.
pause 0
exit /b 1
)
:judge
rem **compile file, setup for judging**
echo [[93mJury[0m] Judging problem %prob% from user %user%...
if exist result.log del /q result.log
cd submits\%user%
g++ %prob%.cpp -o %prob%.o -std=c++14 -O2
if not errorlevel 0 (
echo [91mERROR[0m Compile failed...
pause 0
exit /b 1
)
move %prob%.o "%home%">nul
cd %home%\problems\%prob%\tests
for /d %%i in ("*") do (
set /a total+=1
rem echo [[93mJury[0m] Test %%i...
copy "%%i\*.txt" "%home%">nul
cd %home%
ren out.txt ans.txt
rem more ans.txt
echo|set /p=Test %%i: >>result.log
judge %prob%.o
if errorlevel 0 set /a pass+=1
del /q *.txt
cd %home%\problems\%prob%\tests
)
cd %home%
echo [[92mOK[0m] Judging completed, user passed %pass%/%total% test(s), please check result.log for more detail.
del /q %prob%.o
pause 0
judge.bat
rem #echo off
start %1
timeout /t 1 /nobreak
taskkill /im %1 /f
rem echo %errorlevel%
if not errorlevel 128 (
echo TLE.>>result.log
exit /b 1
)
fc /a /w out.txt ans.txt>nul && (
echo Correct.>>result.log
exit /b 0
) || (
echo Wrong Answer.>>result.log
exit /b 1
)
(I know my English is so bad, so I can't explain all of my problem, if you have any question, feel free to ask me, I will answer for you)
In
echo|set /p=Test %%i: >>result.log
judge %prob%.o
if errorlevel 0 set /a pass+=1
You are executing judge which appears to be a batch file.
You need
CALL judge %prob%.o
in order that processing will continue in your main procedure after judge ends. As it is, processing will be switched to judge.bat and end when judge.bat ends.
if errorlevel 0 set /a pass+=1
IF ERRORLEVEL n is TRUE if the runtime (ie. current) errorlevel is n or greater than n. IF ERRORLEVEL 0 is therefore always true. IF NOT ERRORLEVEL 1 is a test for errorlevel=0.
Hope this helps a bit - not really sure about your other problems.
I have a series of bat files that I want to run in parallel, for example:
start program1_1.bat
start program1_2.bat
start program1_3.bat
wait until program1 finished then
start program2_1.bat
start program2_2.bat
start program2_3.bat
wait until program2 finished then
...
So far, what I've tried is this function:
:waitForFinish
set counter=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe') do set /a counter=counter+1
if counter GTR 2 Goto waitForFinish
But it just launched the first 3 bat files and stop... How can I solve this problem?
Thanks,
EDIT: This is the content of program1_i.bat file:
program1.exe input_i.txt
It will run program1.exe for each input file. The same for program2_i.bat.
Your question is a little vague on the exact expected results.
I am however assuming that you want to do something like this.
start /wait program1_1.bat | start /wait program1_2.bat | start /wait program1_3.bat
start /wait program2_1.bat | start /wait program2_2.bat | start /wait program2_3.bat
The single pipe separators let's us launch the first three commands in parallel and only start the next three commands once the first three has all completed, simply because the next 3 commands are in the next batch line the use of start /wait
* Update *
The solution provided here works nicely to achieve parallel running of subprograms without needing user entry (pause) or a fixed length Timeout between program groups.
Combined with the original answer:
#echo off
:controller
Call :launcher "program1_1.bat" "program1_2.bat" "program1_3.bat"
Call :launcher "program2_1.bat" "program2_2.bat" "program2_3.bat"
pause
EXIT
:launcher
For %%a in (%*) Do (
Start "+++batch+++" "%%~a"
)
:loop
timeout /t 1 >nul
tasklist /fi "windowtitle eq +++batch+++*" |find "cmd.exe" >nul && goto :loop
GOTO :EOF
Original Answer:
This is a simple way to ensure each group of programs is finished before the next. The fault in the tasklist method is that if there's other cmd.exe processes running, the If condition may not be true when expected, causing the script to hang.
The start /wait option is not ideal, as you intend on running multiple programs simultaneously - and if the subprogram you wait on finishes before the other subs, you're back to square 1.
#echo off
:controller
Call :launcher "program1_1.bat" "program1_2.bat" "program1_3.bat"
Call :launcher "program2_1.bat" "program2_2.bat" "program2_3.bat"
pause
EXIT
:launcher
For %%a in (%*) Do (
Start "" "%%~a"
)
pause
GOTO :EOF
Ok, here is what worked for me:
#echo off
setlocal enableextensions enabledelayedexpansion
call :InitDos
start program1_1.bat
start program1_2.bat
start program1_3.bat
call :waitForFinish
start program2_1.bat
start program2_2.bat
start program2_3.bat
call :waitForFinish
Goto:eof
: waitForFinish
set /a counter=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe"') do (
set /a counter+=1
)
if !counter! GTR !init_count! Goto waitForFinish
goto :eof
: InitDos
set /a init_count=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe"') do (
set /a init_count+=1
)
goto :eof
Try /WAIT switch on start command. I guess if you wrap bats into script called with wait switch, it could work.
Windows 10 I can't seem to get the batch file to activate the second batch file if the process is not running. First part works fine, calling the second batch file is broken. I tried to call, I tried %~dp0, like this: %~dp0MyProgram.bat
#echo off
:start
cls
set proc=zm.exe
set runme=c:\ZM\zcash.bat
set time=30
tasklist /FI "IMAGENAME eq %proc%" 2>NUL | find /I /N "%proc%">NUL
if "%ERRORLEVEL%"=="0" (
echo Everything is OK
timeout /t %time%
goto :start ) || (
echo ERROR! restarting service
taskkill /IM "%proc%" /F
else (start "" "%runme%")
)
timeout /t %time%
goto :start
Despite saying you'd tried it, you should use Call because you're wanting to return to the same script upon it's completion.
Perhaps something like this would work for you?
#Echo off
Set "proc=zm.exe"
Set "runme=C:\ZM\zcash.bat"
Set "secs=30"
:Loop
ClS
TaskList | FindStr /I "\<%proc%\>" >Nul && (Echo Everything is OK) || (
Echo ERROR! starting service
Call "%runme%")
Timeout %secs% /NoBreak >Nul
GoTo Loop
Note: I changed the variable name to %secs% because %TIME% is already a defined system variable.
I am finding it difficult to modify the script here to suit my requirements:
https://stackoverflow.com/a/12665498/4683898
#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 am using a batch script, not to execute further batch scripts, but simply executing cmd commands (which run for a period of time) in parallel, and that works fine with the above script.
However, I want to be able to run more than just 2 commands/scripts, i.e. 3, 4, 5, (or whatever I desire) commands at a single time, running in parallel, let's call it, x.
So, I want to run x amount of cmd commands (that are executing in parallel), wait for them to terminate (using /c), and then executing the next bunch of x amount of cmd commands, and then the next bunch, etc, etc, until all cmd commands have executed.
How could I modify that script accordingly? (I have made a few attempts albeit with repeating errors "The process cannot access the file because it is being used by another process." in the initiating batch script; I presume this 'file' refers to the lock file.)
Thanks
EDIT:
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
call :a start cmd.exe /c somecommandA 1
call :a start cmd.exe /c somecommandB 2
call :wait
call :a start cmd.exe /c somecommandC 1
call :a start cmd.exe /c somecommandD 2
call :a start cmd.exe /c somecommandE 3
exit /b
:a
start "%~2" cmd /c 9>"%lock%%2" %1
exit /b
:wait
1>nul 2>nul ping /n 2 ::1
for %%N in (%lock%*) do (
( rem
) 9>"%%N" || goto :Wait
) 2>nul
You can call the files easily with this script:
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
call :a one.bat 1
call :a two.bat 2
call :wait
call :a three.bat 1
call :a name.bat 2
call :a gfwagwa.bat 3
exit /b
:a
start "%~2" cmd /c 9>"%lock%%2" %1
exit /b
:wait
1>nul 2>nul ping /n 2 ::1
for %%N in (%lock%*) do (
( rem
) 9>"%%N" || goto :Wait
) 2>nul
call :wait simply replaces the waiting. Whenever you have called all files that are to be run asynchronously, call the wait function. Then you can call more scripts.
The second parameter is the number of the lock file. Make sure you don't have duplicate numbers before all those scripts using them are closed (i.e. before the next call :wait). Though you're not going to run out of numbers anyway, no reason to use duplicates.
just to offer an alternative way:
#ECHO off
start "MyCommand-%~n0" cmd.exe /c ping localhost
start "MyCommand-%~n0" cmd.exe /c ipconfig /all
start "MyCommand-%~n0" cmd.exe /c sysinfo
:loop
tasklist /fi "windowtitle eq MyCommand-%~n0" | find "===" >nul && goto :loop
echo finished!
Edit for your comment. bunch is the number of commands running in parallel.
#ECHO off
setlocal enabledelayedexpansion
set bunch=3
for /f "delims=:" %%a in ('findstr /n /b "REM ==" %~f0') do set /a datastart=%%a+1
set count=0
for /f "skip=%datastart% usebackq delims=" %%a in ("%~f0") do (
set /a "count=(count+1) %% %bunch%"
echo starting: %%a
start "MyCommand-%~n0" cmd.exe /c %%a
if !count!==0 echo waiting & call :loop
)
echo waiting & call :loop
echo finished!
goto :eof
:loop
tasklist /fi "windowtitle eq MyCommand-%~n0" | find "===" >nul && goto :loop
goto :eof
REM == START DATA ==
ping localhost
ipconfig /all
systeminfo
tasklist
echo hello
schtasks /query
wmic bios get /value
timeout 10
the first for just gets the line number where your commands are (start of DATA section)
Ok, after seeing crazy stuff being completed in so little code, I have high hopes this is possible.
Pretty much, I want to use the pause command normally, however, if the user doesn't input anything for a specified duration of time, it automatically continues.
In pseudo code:
(sleep %sleep-time%&Echo Pass)1>0 & pause
I thought at first I could do this using start /b to create a process that echoed input while being paused i the current thread, but that could cause problems if the user does input something.
Bonus
What would be really cool is if the errorlevel would be changed based on whether the user inputted something, or if the pause command timed out.
I suggest using timeout:
timeout /T 60 >NUL
This will sleep your script for 1 minute, or unless the user hits a key.
#echo off
setlocal
rem TimedPause.bat - Antonio Perez Ayala
if "%1" equ ":PausePart" goto PausePart
if "%1" neq "" goto begin
echo TimedPause.bat seconds
echo/
echo Wait for given seconds or until user press a key
echo At end, the presence of keyPressed.txt file indicate the cause of exit
goto :EOF
:begin
set seconds=%1
start "" /B "%~F0" :PausePart
for /F "skip=2 tokens=2 delims=," %%a in ('tasklist /FI "IMAGENAME eq cmd.exe" /FO CSV') do (
set PausePart=%%a
goto TimePart
)
:TimePart
ping -n 2 localhost > NUL
if exist keyPressed.txt goto :EOF
set /A seconds-=1
if %seconds% gtr 0 goto TimePart
taskkill /PID %PausePart% /F > NUL
goto :EOF
:PausePart
del keyPressed.txt 2> NUL
pause
echo %time% > keyPressed.txt
exit