While working on a Windows batch script earlier today, I ran across an interesting side effect of the
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.