ERRORLEVEL vs %ERRORLEVEL% vs exclamation mark ERR

2019-01-20 01:13发布

问题:

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.

回答1:

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.