I'm newbie in using batch on Windows and have a question about the use of errorlevel.
I referenced TechNet(Exit) and many examples on google.
Most of them used /b with %errorlevel% like this
if errorlevel 1 exit /b %errorlevel%
I wonder the difference between
if errorlevel 1 exit /b
and
if errorlevel 1 exit /b %errorlevel%
I think there are no difference because %errorlevel% is not changed. Am I wrong?
TL;DR
Most of the time there should be no difference, but technically exit /b %errorlevel% is strictly worse than exit /b if what you want is to exit without changing the error level.
Analysis
EXIT /B without the optional errorlevel parameter does not change the error level, so as a standalone command it is exactly equivalent to EXIT /B %errorlevel% provided that %errorlevel% resolves to the current error level.
But there are cases where it might not do so:
If an environment variable named ERRORLEVEL is defined then %errorlevel% always resolves to its value (which can be arbitrary), and not to the current error level.
If command extensions are disabled then %errorlevel% will never resolve to the current error level (it will still read the value of the environment variable with that name, if defined). You can verify this by starting a command prompt with CMD /E:OFF and trying ECHO %errorlevel%.
The current error level value as produced by %errorlevel% will be fixed at the time the command is parsed, not at the time execution reaches that expression. This can result in the wrong value being produced for more complex commands. Example:
copy j:\not_existing q:\not_existing & echo %errorlevel%
This will not produce the same result as
copy j:\not_existing q:\not_existing
echo %errorlevel%
because in the first case %errorlevel% will not produce the updated error level caused by the failed copy.
Related
I think i have a basic understanding of ERRORLEVEL vs %ERRORLEVEL% but !ERRORLEVEL! confuses me.
I'm making a script that calls an executable, then tasklist to see if its running, then taskkill to kill it if it is and then trying to output the errorlevels and repeating for other exe's and i'm realising i really don't understand errorlevels in batch.
I set a variable equal to !errorlevel!
then used that variable without quotation marks in an echo, and the variable changed from one uint16 to another uint16 when there was an error after the set, like its a reference to the real one instead of a copy. I want copy. Can someone explain the difference between these guys?
Update:
Here is the snippet I'm working on.
for %%P in (%executableList%) do (
echo ----------------------------------------------------------------------------------
set exeErrorlevel=0
set running=false
start %%~fP
set exeErrorlevel=!ERRORLEVEL!
rem for debugging purposes
echo %%~nP%%~xP older errorlevel %ERRORLEVEL%
echo %%~nP%%~xP newer errorlevel !ERRORLEVEL!
echo before tasklist running var is : !running!
tasklist /FI "IMAGENAME eq %%~fP" | find /I /N /C "%%~fP" >nul && set running=true
echo after tasklist is running var is: !running!
if !running! equ true (
echo %%~nP%%~xP Program is running
taskkill /F /IM %%~nP%%~xP /T
echo %%~nP%%~xP Program was killed
if !exeErrorlevel! == 0 (
echo %passString% %%~nP%%~xP process was started and killed safely
echo %passString% %%~nP%%~xP process was started and killed safely >>%outputfile%
) else (
echo %failString% %%~nP%%~xP process was killed with errorcode !exeErrorlevel!
echo %failString% %%~nP%%~xP process was killed with errorcode !exeErrorlevel! >>%outputfile%
)
) else (
if !exeErrorlevel! == 0 (
echo %passString% %%~nP%%~xP process exited safely
echo %passString% %%~nP%%~xP process exited safely >>%outputfile%
) else (
taskkill /F /IM %%~nP%%~xP /T
echo %failString% %%~nP%%~xP process abruptly exited with errorcode !exeErrorlevel!
echo %failString% %%~nP%%~xP process abruptly exited with errorcode !exeErrorlevel! >>%outputfile%
)
)
echo. >>%outputfile%
)
I need to make sure exeErrorlevel has a copy of the errorlevel at a certain point in time - I only want to capture errors from the exe, not from the success/failure of tasklist/find/taskill. I'm concerned that exeerrorlevel, because of the delayed expansion, is accessing the delayed errorlevel upon execution. perhaps that should be set exeErrorlevel=%errorlevel% instead. In the line where i echo older and newer variables usually return different integers? In all my test runs %errorlevel% seems to typically return 0 whereas !errorlevel! is consistently non zero for executables with bad exit codes.
The errorlevel
errorlevel is the name of a dynamic variable (it is not placed in the environment block but hold in memory) that stores the exit code of the previous executed process/command (if it sets that value, read here, here, here and here).
The if command allows the usage of the if errorlevel n syntax to check if the value of the errorlevel variable is greater than or equal to n, without involving the batch parser into retrieving the value of the variable.
But, if we put the batch parser to work with variable values, %errorlevel% is just a reference to the value stored in the variable, a read operation. Just the same as !errorlevel!. The main difference between the two is when the value is retrieved depending on the rules on variable expansion.
There is a great difference in using the if errorlevel or retrieving the value in the variable:
The variable read operation will check if the environment block contains a variable with the indicated name.
The if constuct will not make this test.
If you do something like set errorlevel=10, the dynamic errorlevel value will not be retrieved with %errorlevel% or !errorlevel! as the value set in the environment will hide the dynamic value. But as if errorlevel does not read the environment block but directly reads the internal variable that holds the value, it will work without problems.
The variables
The batch syntax does not include the option of having more than one variable pointing to the same value in memory in a way that if one of the variables changes its value, the other will reflect the change.
This behaviour can be simulated by proper use of the different phases in variable expansion, properly setting a variable to the name of another and forcing the batch parser to do two passes over the command so first variable is resolved to the name of the second and that to the real value.
Your problem
Simplified (non even working) code just for analysis
1 for %%P in (%executableList%) do (
2
3 start %%~fP
4 set exeErrorlevel=!ERRORLEVEL!
5
6 echo %%~nP%%~xP older errorlevel %ERRORLEVEL%
7 echo %%~nP%%~xP newer errorlevel !ERRORLEVEL!
8 ....
9 if !running! equ true (
10 taskkill /F /IM %%~nP%%~xP /T
11 if !exeErrorlevel! == 0 (
12 ....
13 ) else (
14 echo process killed with errorcode !exeErrorlevel!
15 )
16 ) else (
17 if !exeErrorlevel! == 0 (
18 ....
19 ) else (
20 taskkill /F /IM %%~nP%%~xP /T
21 echo process abruptly exited with errorcode !exeErrorlevel!
22 )
23 )
line 1: the code in the do clause, all the code, is parsed. Any %var% variable read operation is removed from the code, replaced with the value inside the variable before starting the execution. This means that if the variable changes its value you will not be able to retrieve the changed value as the read operation does not exist, only the initial value in the variable.
line 3: the executable is launched, in a separate process, without waiting for the process to end. Is it important? See next line
line 4: the current (delayed expansion used) value of the errorlevel variable is retrieved and stored in exeErrorlevel variable. BUT the value stored is NOT the errorlevel returned by the executable (separate process, not waiting for it to end, how will we know what the exit code = errorlevel is?), but the exit code of the start command.
line 6: as the %errorlevel% read operation was removed, this line will echo the value that was stored in the errorlevel variable before the do clause started to execute.
line 7: the current value of the errorlevel variable is retrieved. And here, we can have a problem. How the script being executed is named? There is a difference between .bat and .cmd. On sucess the set command in line 4 will clear (set to 0) the errorlevel variable if this is a .cmd file, but will not change the errorlevel if it is a .bat file.
lines 11, 14, 21: as seen the exeErrorlevel variable does not contain a valid value. And no, changing the lines to !errorlevel! will not retrieve the exit code of the process, but the exit code of the taskkill.
To be able to retrieve the exit code / errorlevel of a process we need to wait for it to end. If you need to start the process, if it keeps running kill it, and in both cases retrieve the exit code, directly call the executable or use start "" /wait programName, AND run the killing process in parallel (ex. start /b "" monitor.bat programName or something similar before starting the program). The main process will wait and retrieve the exit code. The monitor process handles the killing.
I am trying to write the below code, and I want it to display the error before it exits, but whenever I run it, it exits right away.
#echo off
cd "haha"
if %errorlevel% 1 (
echo Failure reason given is %errorlevel%
sleep 5
exit /b %errorlevel%
)
echo files deleted successfully!
del /Q *
pause
I have also tried timeout /t 5 doesn't seem to work.
This should work :
#echo off
cd "haha" 2>nul
if %errorlevel%==1 (
echo Failure reason given is %errorlevel%
timeout /t 5
exit /b %errorlevel%
)
The correct syntax is
if errorlevel 1 (
meaning if errorlevel is 1 or greater
OR
if %errorlevel% geq 1 (
which means the same thing.
As it stands, cmd is interpreting you code as
if 1 1 (
(assuming errorlevel is 1). This is a syntax error, so cmd shows a message. If you are running by point-click and giggle, the window will close. If you are running from the prompt, then cmd will report a syntax error.
Be warned however that executing an exit statement from the prompt will abort the cmd instance. Better to use goto :eof unless you have good reason otherwise.
timeout /t 5 should work, but will generate a countdown.
timeout /t 5 >nul should appear simply to wait. The issue is that you have to solve the if syntax first, else the timeout instruction won't be reached.
I'm experimenting with Rust. I want to compile a program, and only if it succeeds, run it. So I'm trying:
rustc hello.rs && hello
But hello.exe always runs, even if compilation fails.
If I try
rustc hello.rs
echo Exit Code is %errorlevel%
I get "Exit Code is 101".
As I understand it, the only truthy value is 0 in cmd, which 101 is clearly not, and && is lazily evaluated, so why does it run hello?
rustc.bat looks like this:
#echo off
SET DIR=%~dp0%
cmd /c "%DIR%..\lib\rust.0.11.20140519\bin\rustc.exe %*"
exit /b %ERRORLEVEL%
Very curious this. Put a CALL in front and all should be fine.
call rustc hello.rs && hello
I don't totally understand the mechanism. I know that && and || do not read the dynamic %errorlevel% value directly, but operate at some lower level. They conditionally fire based on the outcome of the most recently executed command, regardless of the current %errorlevel% value. The || can even fire for a failure that does not set the %errorlevel%! See File redirection in Windows and %errorlevel% and batch: Exit code for "rd" is 0 on error as well for examples.
Your rustc is a batch file, and the behavior changes depending on if CALL was used or not. Without CALL, the && and || operators respond only to whether the command ran or not - they ignore the exit code of the script. With CALL, they properly respond to the exit code of the script, in addition to responding if the script failed to run (perhaps the script doesn't exist).
Put another way, batch scripts only notify && and || operators about the exit code if they were launched via CALL.
UPDATE
Upon reading foxidrive's (now deleted) answer more carefully, I realize the situation is more complicated.
If CALL is used, then everything works as expected - && and || respond to the ERRORLEVEL returned by the script. ERRORLEVEL may be set to 1 early on in the script, and as long as no subsequent script command clears the error, the returned ERRORLEVEL of 1 will be properly reported to && and ||.
If CALL is not used, then && and || respond to the errorcode of the last executed command in the script. An early command in the script might set ERRORLEVEL to 1. But if the last command is an ECHO statement that executes properly, then && and || respond to the success of the ECHO command instead of the ERRORLEVEL of 1 returned by the script.
The real killer is that EXIT /B 1 does not report the ERRORLEVEL to && or || unless the script was invoked via CALL. The conditional operators detect that the EXIT command executed successfully, and ignore the returned ERRORLEVEL!
The expected behavior can be achieved if the last command executed by the script is:
cmd /c exit %errorlevel%
This will properly report the returned ERRORLEVEL to && and ||, regardless whether the script was invoked by CALL or not.
Here are some test scripts that demonstrate what I mean.
test1.bat
#echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
test2.bat
#echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem This command interferes with && or || seeing the returned errorlevel if no CALL
test3.bat
#echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem Ending with EXIT /B does not help
exit /b %errorlevel%
test4.bat
#echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem The command below solves the problem if it is the last command in script
cmd /c exit %errorlevel%
Now test with and without CALL:
>cmd /v:on
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
>test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1
>test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1
>test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>call test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>call test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>call test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>call test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1
>
Quick demo to prove the point:
#ECHO OFF
SETLOCAL
CALL q24983584s 0&&ECHO "part one"
ECHO done one
CALL q24983584s 101&&ECHO "part two"
ECHO done two
GOTO :EOF
where q24983584s.bat is
#ECHO OFF
SETLOCAL
EXIT /b %1
GOTO :EOF
works as expected...
I'm under the impression that the errorlevel provided back to command-line level scripting is exactly the value provided by a windows application to its Exit(..) call.
See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682658%28v=vs.85%29.aspx
Presumably, an exit call might be called with "-1" as an argument, in spite of the fact that the error code would seem to be an unsigned number.
Does that value get through to a calling command script?
(Not that I want it. I need a value which is clearly not a valid error number,
and isn't zero).
#ECHO OFF
SETLOCAL
CALL :negerr 1
ECHO ERRORLEVEL = %errorlevel%
GOTO :EOF
:negerr
EXIT /b -%1
You mean like this?
Lets say we want to create an empty file in windows with the following command:
type nul > C:\does\not\exist\file.txt
the directory does not exist, so we get the error:
The system cannot find the path specified
If you print out the %errorlevel% the output is:
echo %errorlevel%
0
Yet the command was not successful!
I noticed, that windows does not set the %errorlevel% of the last command if you use redirection..
Is there a way around this?
You can use the following:
C:\>type nul > C:\does\not\exist\file.txt && echo ok || echo fail
The system cannot find the path specified.
fail
C:\>echo %errorlevel%
1
I always assumed the && and || operators used ERRORLEVEL, but apparently not.
Very curious that ERRORLEVEL is set after redirection error only if you use the || operator. I never would have guessed. Nor would I ever have bothered to test if not for your excellent question.
If all you want to do is set the ERRORLEVEL upon redirection failure, then of course you can simply do:
type nul > C:\does\not\exist\file.txt || rem
The command
type nul > C:\does\not\exist\file.txt
invoked with a non-existent path is terminated at redirection failure and type is not invoked at all. It therefore has no chance of setting ERRORLEVEL. The redirection, being performed by the shell, does not set ERRORLEVEL.
One solution is to pre-initalise ERRORLEVEL with a non-zero value. It will remain unchanged upon failure and will be reset to zero (by type) upon success:
#echo off
::pre-initialise ERRORLEVEL with a value of 1:
call :SETERROR 1
type NUL > NOSUCHDIR\test.txt
IF ERRORLEVEL 1 goto ERROR
echo All is well.
goto END
:ERROR
echo Error detected.
:END
goto :eof
:SETERROR
exit /b %1
The shorft form
type NUL > NOSUCHDIR\test.txt && goto OK || goto ERROR
works because it analyses exit code, which is not the same as error level:
An exit code can be detected directly with redirection operators (Success/Failure ignoring the ERRORLEVEL) this can often be more reliable than trusting the ERRORLEVEL, which may or may not have been set correctly.
Herbert Kleebauer explained this to me in the Usenet group alt.msdos.batch.
Update:
An anonymous user suggested an alternative solution based on the COPY command:
COPY NUL: C:\does\not\exist\file.txt
This command does set ERRORLEVEL, which may be analysed by the next command in the script. Very convenient, so I thank him for the proposed edit.