I have a Windows bat file with the following content:
setlocal EnableDelayedExpansion
rem Stop batch script loop if a command failed.
#echo OFF
FOR %%a in (x86 x86_64 armeabi-v7a arm64-v8a) do (
set MY_ANDROID_BUILD_ABI=%%a
FOR %%m in (debug release) DO (
set MY_MODE=%%m
echo !MY_MODE! -^> !MY_ANDROID_BUILD_ABI!
cd D:\dev\libs\QT6.4\android\!MY_MODE!\!MY_ANDROID_BUILD_ABI!
rem some further build commands go here
cmake --build . --parallel && cmake --install .
)
)
it iterates over build configurations and executes multiple commands inside nested loop to build some library.
How to make the script exit the loop and stop execution when an error occurres?
For example, the script should stop if a directory does not exist or cmake command failed.
Should I add
IF %ERRORLEVEL% NEQ 0 (
echo "Previous command execution failed."
exit %ERRORLEVEL%
)
after each command?
Yes, you need to add some kind of error checking, since cmd doesn't have built-in exceptions. The full solution is to add after each command:
(command)
if ERRORLEVEL 1 (
echo failure message, maybe to some log
exit !ERRORLEVEL!
)
You can use the shorthand suggested in #Gerhard's comment:
(command) || (echo failure message& exit /b !ERRORLEVEL! )
In either case, beware of using %ERRORLEVEL%, as it's expanded too early. As your code correctly shows, use !ERRORLEVEL! (which requires setlocal EnabledDelayedExpansion), or use the special if ERRORLEVEL 1 format.
Related
i am trying to run below snippet of code on my windows server.
#echo off
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$" & IF %ERRORLEVEL% == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
I was expecting the only output should be INSIDE COMPILER BRANCH because, BRANCH_NAME_ID variable is referring to compiler_branch. But some reason i am also getting IN IF as well.
Ouptut:-
INSIDE COMPILER BRANCH
IN IF
Press any key to continue . . .
As per the document https://ss64.com/nt/findstr.html i notice below and wrote the script accordingly. But some reason %ERRORLEVEL% is setting to 0 in line3 of my code thought the string is not matching :-
FINDSTR will set %ERRORLEVEL% as follows:
0 A match is found in at least one line of at least one file.
1 If a match is not found in any line of any file, (or if the file is not found at all).
2 Wrong syntax
An invalid switch will only print an error message in error stream.
Am i missing something ?
Because of how the interpreter reads files (see How does the Windows Command Interpreter (CMD.EXE) parse scripts? for a massive amount of info), %ERRORLEVEL% in that line gets replaced with its current value before the line is actually run. In order to have the command run and then have the value checked correctly, put the if statement on its own line.
#echo off
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$"
IF %ERRORLEVEL% == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
If for some reason you absolutely insist on using & to chain commands together (there is no reason to ever do this and it only makes things worse imo), then you can enable delayed expansion and use !ERRORLEVEL! instead.
#echo off
setlocal enabledelayedexpansion
set BRANCH_NAME_ID=compiler_branch
if %BRANCH_NAME_ID%==compiler_branch ( echo INSIDE COMPILER BRANCH )
echo %BRANCH_SHORT_ID%|findstr /r "^[r][0-9][0-9]*_00$" & IF !ERRORLEVEL! == 0 ( echo IN IF ) ELSE ( echo INFO else)
pause
I have a script that runs through some files and copies them to another location. But the script needs to wait until the file is no longer being written to.
I tried all the solutions here:
How to check in command-line if a given file or directory is locked (used by any process)?
Process a file after a file is finished being written Windows Command Line .bat
BATCH - wait for file to be complete before picking up
But the problem is that they don't work when wrapped in a loop. It always says the file is locked. If the script it cancelled and re-run it correctly finds the file unlocked.
Am I doing something wrong or is there a trick to make this work?
For locking a test file, checkfile.txt, I do:
(
>&2 pause
) >> checkfile.txt
Then the example script to check the file is this:
#echo off
for %%i in (*.txt) do (
:loop
ping localhost -n 5 > nul
echo "check if locked"
powershell -Command "$FileStream = [System.IO.File]::Open('%%i', 'Open', 'Write'); $FileStream.Close(); $FileStream.Dispose()" >NUL 2>NUL || (goto :loop)
echo "NOT locked anymore"
)
You cannot goto in a loop as it will simply break the for loop entirely. Additionally, the exit code or errorlevel is set for the last successful command. In this case being the powershell dispose command. Simply do the loop outside of the code block:
#echo off & setlocal
for %%i in (*.txt) do call :loop %%~i
goto :EOF
:loop
powershell -Command "[System.IO.File]::Open('%1', 'Open', 'Write')">nul 2>&1 && echo %1 not locked || (
echo %1 Locked
("%systemroot%\system32\timeout.exe" /t 3)>nul
goto :loop
)
Note, the conditional operators (and &&) and (or ||) helps to evaluate the exit code without needing to do if and else statements.
I have a simple function written to check for directories:
:direxist
if not exist %~1 (
echo %~1 could not be found, check to make sure your location is correct.
goto:end
) else (
echo %~1 is a real directory
goto:eof
)
:end is written as
:end
endlocal
I don't understand why the program would not stop after goto:end has been called. I have another function that uses the same method to stop the program and it work fine.
:PRINT_USAGE
echo Usage:
echo ------
echo <file usage information>
goto:end
In this instance, the program is stopped after calling :end; why would this not work in :direxist? Thank you for your help!
I suppose you are mixing call and goto statements here.
A label in a batch file can be used with a call or a goto, but the behaviour is different.
If you call such a function it will return when the function reached the end of the file or an explicit exit /b or goto :eof (like your goto :end).
Therefore you can't cancel your batch if you use a label as a function.
However, goto to a label, will not return to the caller.
Using a synatx error:
But there is also a way to exit the batch from a function.
You can create a syntax error, this forces the batch to stop.
But it has the side effect, that the local (setlocal) variables will not be removed.
#echo off
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :halt
exit /b
:halt
call :haltHelper 2> nul
:haltHelper
()
exit /b
Using CTRL-C:
Creating an errorcode similar to the CTRL-C errorcode stops also the batch processing.
After the exit, the setlocal state is clean!
See #dbenham's answer Exit batch script from inside a function
Using advanced exception handling:
This is the most powerful solutions, as it's able to remove an arbitrary amount of stack levels, it can be used to exit only the current batch file and also to show the stack trace.
It uses the fact, that (goto), without arguments, removes one element from the stack.
See Does Windows batch support exception handling?
jeb's solution works great. But it may not be appropriate in all circumstances. It has 2 potential drawbacks:
1) The syntax error will halt all batch processing. So if a batch script called your script, and your script is halted with the syntax error, then control is not returned to the caller. That might be bad.
2) Normally there is an implicit ENDLOCAL for every SETLOCAL when batch processing terminates. But the fatal syntax error terminates batch processing without the implicit ENDLOCAL! This can have nasty consequences :-( See my DosTips post SETLOCAL continues after batch termination! for more information.
Update 2015-03-20 See https://stackoverflow.com/a/25474648/1012053 for a clean way to immediately terminate all batch processing.
The other way to halt a batch file within a function is to use the EXIT command, which will exit the command shell entirely. But a little creative use of CMD can make it useful for solving the problem.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
exit /b
:main
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" exit
exit /b
I've got both my version named "daveExit.bat" and jeb's version named "jebExit.bat" on my PC.
I then test them using this batch script
#echo off
echo before calling %1
call %1
echo returned from %1
And here are the results
>test jebExit
before calling jebExit
hello
stop
>test daveExit
before calling daveExit
hello
stop
returned from daveExit
>
One potential disadvantage of the EXIT solution is that changes to the environment are not preserved. That can be partially solved by writing the environent to a temporary file before exiting, and then reading it back in.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
for /f "eol== delims=" %%A in (env.tmp) do set %%A
del env.tmp
exit /b
:main
call :label hello
set junk=saved
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :saveEnvAndExit
exit /b
:saveEnvAndExit
set >env.tmp
exit
But variables with newline character (0x0A) in the value will not be preserved properly.
If you use exit /b X to exit from the function then it will set ERRORLEVEL to the value of X. You can then use the || conditional processing symbol to execute a command if ERRORLEVEL is non zero.
#echo off
setlocal
call :myfunction PASS || goto :eof
call :myfunction FAIL || goto :eof
echo Execution never gets here
goto :eof
:myfunction
if "%1"=="FAIL" (
echo myfunction: got a FAIL. Will exit.
exit /b 1
)
echo myfunction: Everything is good.
exit /b 0
Output from this script is:
myfunction: Everything is good.
myfunction: got a FAIL. Will exit.
Here's my solution that will support nested routines if all are checked for errorlevel
I add the test for errolevel at all my calls (internal or external)
#echo off
call :error message&if errorlevel 1 exit /b %errorlevel%<
#echo continuing
exit /b 0
:error
#echo in %0
#echo message: %1
set yes=
set /p yes=[no]^|yes to continue
if /i "%yes%" == "yes" exit /b 0
exit /b 1
I made a piece of batch-code, and I thought this will work. What I'm thinking that this code is doing? I have some plugins and I want to test if the deploy correct. So I get the pluginlink from the plugins.txt. Then I get the plugin from SVN with the java sentence. I deploy the plugin and get the feedback in test1.txt. Then I do a findStr in that file and searchs for "BUILD SUCCESSFUL" if it is there I want to add the sentence Build Gelukt and if it fails I want to add Build Fout. But I get always the answer Build Gelukt, while as you can see in the image he sends back that the build is Failed.
Whats wrong with this piece of code?
for /f "tokens=* delims= " %%a in (plugins.txt) do (
echo %%a
cd "C:\dotCMS Automatic Install"
java -cp .;"C:\dotCMS Automatic Install\svnkit.jar" Test %%a
cd %dotcms_home%
call ant deploy-plugins > test1.txt
FindStr "SUCCESSFUL" test1.txt
if %ERRORLEVEL% ==1 (echo ^<tr BGCOLOR=\"#FFFFFF\"^>^<td^>%%a^</td^>^<td^>Build Fout^</td^>^</tr^> >> C:\dotCMSResults\goedje.html ) else (echo ^<tr BGCOLOR=\"#00FF00\"^>^<td^>%%a^</td^>^<td^>Build Gelukt^</td^>^</tr^> >> C:\dotCMSResults\goedje.html)
del test1.txt
rem call ant undeploy-plugins >> test.txt
)
Classic batch problem - you are setting your ERRORLEVEL and attempting to access it using %ERRORLEVEL% within the same DO() clause. %VAR% expansion happens at parse time, and the entire FOR ... DO() statement is parsed once, so you are seeing the value of ERRORLEVEL before the statement was executed. Obviously that won't work.
jeb alluded to the answer in his comment regarding disappearing quotes. Your problem will be fixed if you setlocal enableDelayedExpansion at the top, and then use !ERRORLEVEL! instead of %ERRORLEVEL%. Also, GregHNZ is correct in that the ERRORLEVEL test should occur immediately after your FINDSTR statement.
There are other ways to handle ERRORLEVEL within parentheses that don't require delayed expansion:
The following tests if ERRORLEVEL is greater than or equal 1
IF ERRORLEVEL 1 (...) ELSE (...)
And below conditionally executes commands based on the outcome of the prior command
FindStr "SUCCESSFUL" test1.txt && (
commands to execute if FindStr succeeded
) || (
commands to execute if prior command failed.
)
The %ErrorLevel% variable applies to the immediately previous command only.
So when you do this:
echo Errorlevel: %ERRORLEVEL%
With your current code, you are getting the error level of the CD command above
Try putting your if %ERRORLEVEL% ==1 line immediately after the FindStr command, and then do the del and the cd afterward. Obviously you'll need to put the full path to the html file in your echo statement.
So I recently stumbled on the (potentially) useful %~$PATH:1 expansion, however I seem to be unable to make it work correctly. I tried to use it to make a cheap Windows version of the which command, however the syntax seems to be defeating me. My batch file looks like this:
#echo off
echo %~$PATH:1
However when I run this with for example
which cmd
all I get as output of "ECHO is off.", which means according to the docs that the %~$PATH:1 didn't find "cmd". What am I doing wrong?
Checking for files with the extensions .exe, .cmd or .bat is not enough. The set of applicable extensions is defined in the environment variable PATHEXT.
Here is my version of a which command that honors the PATHEXT variable upon search:
#echo off
rem Windows equivalent of Unix which command
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Usage: which cmdname
exit /b 1
)
call :findOnPath "%~1"
if not errorlevel 1 exit /b 0
for %%E in (%PATHEXT:;= %) do (
call :findOnPath "%~1%%E"
if not errorlevel 1 exit /b 0
)
echo "%~1" not found on PATH.
exit /b 1
:findOnPath
if not "%~$PATH:1" == "" (
echo "%~$PATH:1"
exit /b 0
)
exit /b 1
Shoot! I just figured it out! I need to use the full "cmd.exe" as a parameter instead of just "cmd". D'oh! ;] So, the complete which.cmd script looks like this:
#echo off
call :checkpath %1
call :checkpath %1.exe
call :checkpath %1.cmd
call :checkpath %1.bat
:checkpath
if "%~$PATH:1" NEQ "" echo %~$PATH:1
Yeah! Finally a which command on Windows! ;]
I have been using this one for a while, it also checks built-in commands