IF, CALL, EXIT and %ERRORLEVEL% in a .bat - windows

Can anyone please help me understand the behaviour of %ERRORLEVEL% variable and why it's not being set after a CALL while being inside an IF, i.e. the ECHO %ERRORLEVEL%.2 line?
#ECHO OFF
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
IF %ERRORLEVEL% == 0 (
ECHO %ERRORLEVEL%.1
CALL :FOO
ECHO %ERRORLEVEL%.2
IF ERRORLEVEL 42 ECHO 42.3
)
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
STDOUT
C:\Users\Ilya.Kozhevnikov\Dropbox>foo.bat
Environment variable ERRORLEVEL not defined
0.0
0.1
0.2
42.3
However, without IF the %ERRORLEVEL% variable is set as expected.
#ECHO OFF
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
REM IF %ERRORLEVEL% == 0 (
ECHO %ERRORLEVEL%.1
CALL :FOO
ECHO %ERRORLEVEL%.2
IF ERRORLEVEL 42 ECHO 42.3
REM )
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
STDOUT
C:\Users\Ilya.Kozhevnikov\Dropbox>foo.bat
Environment variable ERRORLEVEL not defined
0.0
0.1
42.2
42.3

When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, it was replaced with the value in the variable
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
#ECHO OFF
setlocal enabledelayedexpansion
SET ERRORLEVEL
VERIFY > NUL
ECHO %ERRORLEVEL%.0
IF ERRORLEVEL 1 ECHO SNAFU
IF %ERRORLEVEL% == 0 (
ECHO !ERRORLEVEL!.1
CALL :FOO
ECHO !ERRORLEVEL!.2
IF ERRORLEVEL 42 ECHO 42.3
)
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF

MC ND answered the question already well.
Here is an alternative code showing both: expanded and delayed expansion of ERRORLEVEL.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
VERIFY > NUL
ECHO !ERRORLEVEL!.0 delayed
ECHO %ERRORLEVEL%.0 expanded
IF ERRORLEVEL 1 ECHO SNAFU
IF !ERRORLEVEL! == 0 (
ECHO !ERRORLEVEL!.1 delayed
ECHO %ERRORLEVEL%.1 expanded
CALL :FOO
ECHO !ERRORLEVEL!.2 delayed
ECHO %ERRORLEVEL%.2 expanded
)
ENDLOCAL
GOTO :EOF
:FOO
EXIT /B 42
GOTO :EOF
Microsoft describes the behavior of delayed expansion in help of command set which can be read in a command prompt window after entering set /? or help set

Related

windows batch errorlevel with if

In the below script even if errorlevel is 0, Its going to if condition "if errorlevel 1"
#echo off
if exist servers.txt goto :continue
echo servers.txt file is missing
exit
:continue
set instance=%username:~2%
setlocal enabledelayedexpansion
for /f "delims=" %%i in (servers.txt) do (
pushd \\%%i\D$\%instance%\Hyperion\oracle_common 2>nul
if not errorlevel 1 (
echo %%i
echo **********************************
set ORACLE_HOME=!CD!
echo ORACLE_HOME is !ORACLE_HOME!
D:
FOR /D /r D:\%instance%\Hyperion %%a in ("jdk160_*") DO CD %%a
set JAVA_HOME=!CD!
echo JAVA_HOME is !JAVA_HOME!
echo D:\%instance%\Hyperion\oracle_common\oui\bin\setup.exe -jreLoc !JAVA_HOME! -silent -attachHome ORACLE_HOME=!ORACLE_HOME! ORACLE_HOME_NAME="REMOTE_EPM"
D:\%instance%\Hyperion\oracle_common\oui\bin\setup.exe -jreLoc !JAVA_HOME! -silent -attachHome ORACLE_HOME=!ORACLE_HOME! ORACLE_HOME_NAME=REMOTE_EPM
echo error code is:%errorlevel%
if errorlevel 2 (
echo unable to attach remote server %%i ORACLE_HOME to inventory
pause
exit
)
cd D:\%instance%\Hyperion\oracle_common\OPatch
if errorlevel 1 (
echo Failed to locate OPatch location D:\%instance%\Hyperion\oracle_common\OPatch
pause
exit
)
echo current: !CD!
opatch.bat lsinv | find "applied on"
D:\%instance%\Hyperion\oracle_common\oui\bin\setup.exe -jreLoc !JAVA_HOME! -silent -detachHome ORACLE_HOME=!ORACLE_HOME! ORACLE_HOME_NAME="REMOTE_EPM"
if errorlevel 1 (
echo Error: unable to detach remote server %%i ORACLE_HOME from inventory
pause
exit
)
popd
pause
) else (
echo ORACLE_HOME is Not found: \\%%~i\D$\%instance%\Hyperion\oracle_common
)
pause
)
endlocal
Output is:
vmhodwbrep9.oracleoutsourcing.com
**********************************
ORACLE_HOME is Y:\pwbre7\Hyperion\oracle_common
JAVA_HOME is D:\pwbre7\Hyperion\jdk160_35
D:\pwbre7\Hyperion\oracle_common\oui\bin\setup.exe -jreLoc D:\pwbre7\Hyperion\jdk160_35 -silent -attachHome ORACLE_HOME=Y:\pwbre7\Hyperion\oracle_common ORACLE_HOME_NAME="REMOTE_EP
M"
error code is:0
unable to attach remote server vmhodwbrep9.oracleoutsourcing.com ORACLE_HOME to inventory
Press any key to continue . . .
Unless command extensions are enabled, you cannot easily access ERRORLEVEL in an echo statement.
Also keep in mind that you must check your conditions in reverse because...:
IF ERRORLEVEL 1 ....
checks to see if ERRORLEVEL is greater than or equal to one. So, a series of tests would be:
IF ERRORLEVEL 5 ....
IF ERRORLEVEL 4 ....
IF ERRORLEVEL 3 ....
IF ERRORLEVEL 2 ....
IF ERRORLEVEL 1 ....
Finally, recognize that in an IF statement, %errorlevel% is *not* the same asERRORLEVEL`. You don't try it this way, but another answer does.
change
echo error code is:%errorlevel%
if errorlevel 2 (
to
call echo error code is:%%errorlevel%%
if errorlevel 2 (
OR, preferably since you have invoked delayedexpansion,
echo error code is:!errorlevel!
if errorlevel 2 (
With your current code, the entirity from
if not errorlevel 1 (
to the single ) before the endlocal line is one block statement.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, since the block starts with
if not errorlevel 1 (
then %errorlevel% will be replaced by the value of errorlevel at the time the if is encountered, that is 0, so your echo will be replaced by echo error code is:0
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Note therefore the use of CALL ECHO %%var%% which displays the changed value of var. CALL ECHO %%errorlevel%% displays, but sadly then RESETS errorlevel.
note that last statement
CALL ECHO %%errorlevel%%` displays, but sadly then RESETS errorlevel.
So your errorlevel would now be displayed correctly, but would be reset to 0 by the call.

Print variable in batch file

I am trying to print a variable in parenthesised code which is assigned a value using other variable in batch file.
Here is my code
#echo off
SETLOCAL enableDelayedExpansion
CALL initialize
CALL fun
:fun (
#echo off
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
EXIT /B 0
)
:initialize (
set SOMEVAR=somevalue
EXIT /B 0
)
The output is just
..
How do i fix it so that i can assign value to somevar?
Edit1: If i now try to print in following way it does its job
ECHO ..!SOMEVAR!
But my script uses lot of %SOMEVAR%. Does that mean i need to change them all?
Note: Othervar is initialzed in other function and it does show proper value if it is echoed.
Since the code portion containing echo %SOMEVAR% is in between parenthesis, the variable is expanded before being set (consult this post for a good explanation).
There are the following options to avoid that:
to expand it like !SOMEVAR! (delayed expansion), or
to avoid the parenthesis:
#echo off
SETLOCAL EnableDelayedExpansion
CALL initialize
CALL fun
exit /B
:fun
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
EXIT /B 0
:initialize
set SOMEVAR=somevalue
EXIT /B 0
Note the additional exit /B in the above code snippet after the call statements, which prevents from falling into the code below unintentionally.
Does this work any closer to your expectations? Note that SOMEVAR will not be returned to your shell environment unless an ENDLOCAL block is used.
C:>set OTHERVAR=0123456789
C:>type g2.bat
#echo off
SETLOCAL enableDelayedExpansion
CALL:initialize
CALL:fun
EXIT /B 0
:fun (
rem #echo off
Setlocal EnableDelayedExpansion
Set "SOMEVAR=!OTHERVAR!"
ECHO ..%SOMEVAR%
GOTO :EOF
)
:initialize (
set SOMEVAR=somevalue
GOTO :EOF
)
C:>g2.bat
..0123456789

Strange behavior of batch file

I have two cmd files.
child.cmd:
#echo off
exit 1
parent.cmd:
#echo off
cmd /C child.cmd
if %errorlevel% EQU 0 (
echo OK
) else (
echo ERROR
)
If to run parent.cmd, then ERROR will be printed.
But if a little change parent.cmd, then OK will be printed:
#echo off
if "YES" EQU "YES" (
cmd /C child.cmd
if %errorlevel% EQU 0 (
echo OK
) else (
echo ERROR
)
)
Why OK is printed in the second example?
inside a code block you need delayed expansion to access %variables%:
#echo off &setlocal enabledelayedexpansion
if !errorlevel! EQU 0 (
You can also use this syntax without delayed expansion:
if errorlevel 1 if not errorlevel 2 ( echo error )

windows batch parentheses scope

How can I set result variable in a scope surrounded with parantheses ('if' or 'for'-loop). The result is correct (>> RESULT: aaa = bbb), when procedure is called directly, and fails when used in for-loop or if-statement (>> RESULT: ccc = ).
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
echo RESULT: ccc = %ccc%
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
Thanks!!
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
inside a code block (=surrounded with parantheses) you need delayed expansion and !variables!, not %variables%:
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
setlocal enabledelayedexpansion
echo RESULT: ccc = !ccc!
endlocal
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal

Windows Batch For Loop: having trouble with the following code

set checker=0
for %%a in (%namelist%) do (
:startLoop
findstr "completed" %%a_Logs.txt
IF ERRORLEVEL 1 (
IF %checker%==120 (
set checker=0
goto endLoop
)
set /a checker=%checker%+1
#ping 127.0.0.1 -n 1 -w 1000 > nul
findstr "ERROR" %%a_Logs.txt
IF ERRORLEVEL 1 (
echo Waiting 1 second before rechecking (Max 2 mins)
echo time elapsed %checker% seconds
echo.
goto startLoop
)
findstr "ERROR" %%a_Logs.txt
IF NOT ERRORLEVEL 1 (
echo ERROR: %%a Error found
goto endLoop
)
)
findstr "completed" %%a_Logs.txt
IF NOT ERRORLEVEL 1 (
echo %%a completed
)
:endLoop
)
The above piece of code is to do the following:
Parse the variable namelist(where the contents are separated by spaces)
Check if "completed" is present in the %%a_Logs.txt file
If it is present, then iteration over, If it is not, then check for the string "ERROR" in same file
If ERROR is present, then output ERROR MSG and end iteration
If ERROR is not found, keep rechecking for the next 120 seconds before ending iteration
I keep getting the following output
FINDSTR: Cannot open %a_Logs.txt
You are attempting to GOTO a label within a FOR loop - that simply doesn't work. The moment a FOR loop executes GOTO, the loop is terminated, and the FOR context is lost. So your %%a FOR variable is no longer defined. A similar issue happens with IF statements, as described at (Windows batch) Goto within if block behaves very strangely.
You also have a problem when you attempt to expand %checker% within the same parenthesized code block that sets the value. That expansion occurs at parse time, and the entire block is parsed at once. So the value you see will always be the value that existed before the block was entered. The solution is to enable delayed expansion and use !checker! instead of %checker%.
Personally, I would probably make significant changes to your code. But I believe the following minimal changes can make your code work, assuming there are no other bugs:
enable delayed expansion
Move your DO loop code to a routine outside of the loop, and then have the loop CALL that routine with %%a as a parameter. CALL does not break the loop.
Substitute %1 for %%a in the routine
Substitute exit /b for goto endLoop. Also put exit /b at end of the routine
Make sure the code does not fall into the routine when the FOR loop finishes. I used a GOTO after the FOR loop
Substitute !checker! for %checker%
EDIT -The ) in the ECHO statement must be escaped
Here is the modified code (untested)
setlocal enableDelayedExpansion
set checker=0
for %%a in (%namelist%) do call :startLoop %%a
goto continue
:startLoop
findstr "completed" %1_Logs.txt
IF ERRORLEVEL 1 (
IF !checker!==120 (
set checker=0
exit /b
)
set /a checker=checker+1
#ping 127.0.0.1 -n 1 -w 1000 > nul
findstr "ERROR" %1_Logs.txt
IF ERRORLEVEL 1 (
echo Waiting 1 second before rechecking (Max 2 mins^)
echo time elapsed !checker! seconds
echo.
goto startLoop
)
findstr "ERROR" %1_Logs.txt
IF NOT ERRORLEVEL 1 (
echo ERROR: %1 Error found
exit /b
)
)
findstr "completed" %1_Logs.txt
IF NOT ERRORLEVEL 1 (
echo %1 completed
)
exit /b
:continue
I think the labels inside your for loop are messing it up. I just tried it moving the contents of the loop into a separate "subroutine" and that gets rid of the error you mention.
Try this:
set checker=0
for %%a in (foo bar baz) do (
call :loop %%a
)
goto :eof
:loop
set basename=%1
:startLoop
findstr "completed" %basename%_Logs.txt
IF ERRORLEVEL 1 (
IF %checker%==120 (
set checker=0
goto endLoop
)
set /a checker=%checker%+1
#ping 127.0.0.1 -n 1 -w 1000 > nul
findstr "ERROR" %basename%_Logs.txt
IF ERRORLEVEL 1 (
echo Waiting 1 second before rechecking (Max 2 mins)
echo time elapsed %checker% seconds
echo.
goto startLoop
)
findstr "ERROR" %basename%_Logs.txt
IF NOT ERRORLEVEL 1 (
echo ERROR: %basename% Error found
goto endLoop
)
)
findstr "completed" %basename%_Logs.txt
IF NOT ERRORLEVEL 1 (
echo %basename% completed
)
:endLoop
goto :eof

Resources