
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 bug stymied me for several hours today; hopefully this short post will help someone else avoid this frustration.
May 23, 2008 at 1:00 pm
Batch files are so crippled because they have to backwards compatible all the way back to when they were introduced in DOS 1.0, which was like 1980. That makes it very hard to introduce new features.
I found this workaround recently for getting behavior like UNIX backquotes:
REM similar to: set HOSTNAME=`hostname`for /f %%i in ('hostname') do set HOSTNAME=%%iMay 31, 2008 at 4:25 pm
Doing exit /B x from a ‘function’ is fine. Doing this will set the error level.
There is a new full featured debugger for batch files. It runs in a Visual-Studio like environment. The product name is ‘Running Steps’, and it can be found in http://www.steppingsoftware.com. I find it very useful.
August 19, 2008 at 4:22 am
Hi Jonah,
I’ve the same problem you had. I tried your example above but unfortunately it doesn’t work for me. After calling script_a.bat I allways get ERRORLEVEL=0.
I’m using WinXP SP2. Do you have any idea?
greetz
modbom
August 19, 2008 at 10:22 am
If you are using the first instance of script_a (the first code block), the errorlevel will always be 0 (because the final
cdcall always succeeds, assuming %SOME_PATH% exists).The second form of script_a (the third code block) will produce an errorlevel of 1. I just tried it on my Windows XP SP-2 box here, and it worked just fine.
August 19, 2008 at 10:34 am
Mmh…
I tried the second one. I tried also a batch script foo.bat only containing:
After modifying script_b.bat to:
and running them, the output is
Exit Code = 0.It’s very curious. Nevertheless thanks a lot for your quick response.
August 19, 2008 at 11:42 am
I’m not sure why the above examples don’t work for you. You might take a look at the documentation in your command prompt for the exit command:
exit /?The set command might also be useful:
set /?Also, remove the
echo @offline (so that all the lines of the script are echoed). That would help you figure out what the last issued command is. Other than that, I can’t think of why it wouldn’t work for you. The above works just fine on my Windows XP SP2 box.August 19, 2008 at 1:01 pm
modbom- not sure but maybe this is related? Try checking the registry settings mentioned there.
August 21, 2008 at 3:39 am
Thx for our replies.
No it works, but i’m not unsure why. I thinks it was caused by a side effect of another script called long before the above mentioned. Something like
set %ERRORLEVEL%=0But now it works. Thx!