While working on a Windows batch script earlier today, I ran across an interesting side effect of the call
and exit
commands. Let's take this simple example, which we'll name script_a.bat:
@echo off
SETLOCAL
call :function
cd %SOME_PATH%
goto :functionEnd
:function
set foobar=1
if "%foobar%" == "1" exit /B 1
goto :EOF
:functionEnd
Unlike Bash, Windows batch files have no function capabilities. Clever hacks like the above can be used to fake out functions, but these hacks hide some subtle quirks. You see that exit
call within the 'function'? It only gets called if the %foobar%
variable is equal to 1 (which is always the case, in our example). Also note that we exit with an error code of 1. So, in short, this script should always return an exit code of 1. Now, let's create another batch script which we'll name script_b.bat:
@echo off
call script_a.bat
echo Exit Code = %ERRORLEVEL%
This second script is very simple. All we do is call script_a.bat, and then print its resulting return code. What do you expect the return code to be? One would expect it to be 1, but it's not! Our second script will actually print out Exit Code = 0
. Why is this?
The answer lies in the call
command. Again, unlike Bash scripts, stand-alone batch files do not create their own context when executed. But if you use the call
command, the thing you call does get its own context. How weird is that? So, let's trace the first script we wrote to figure out where the error code gets changed.
After some initial setup, we call our function (call :function
). Inside our function, we create a variable, initialize it to 1, then test to see if the value is 1. Since the value is indeed 1, the if
test succeeds, and the exit
command is called. But we don't exit the script; instead, we exit the context that was created when we called our function. Note that immediately after we call our function, we perform a cd
operation. This line of code gets executed, succeeds, and sets the %ERRORLEVEL%
global to 0.
In order to exit properly, we have to exit our initial script twice, like this:
@echo off
SETLOCAL
call :function
if "%ERRORLEVEL%" == "1" exit /B 1
cd %SOME_PATH%
goto :functionEnd
:function
set foobar=1
if "%foobar%" == "1" exit /B 1
goto :EOF
:functionEnd
See the new exit
call after our initial function call? Then, and only then, will our second script print out what we expected. This subtle behavior stymied me for several hours today; hopefully this short post will help someone else avoid this frustration.