Variables

There are four kinds of variables in batch files: command line arguments, environment variables, ERRORLEVELs, and FOR variables. All but ERRORLEVEL, which is of type "byte", are of type "string".

Command line arguments are the strings following the program name in the command that invokes the program. In batch files they are read- only and are referred to by an escaped digit ('%' is the escape character in batch files). %0 through %9 are readily available, and any beyond %9 can be read by SHIFTing them down into the single digit range. For each SHIFT, %0 disappears, and each argument takes on the value of the one to its right on the command line. Once shifted out of the %0 - %9 range, an argument cannot be recovered.

%0 always exists (initially) and is the name of the batch file as given in the command. If given in short form, it is just the name; if given in long form, the name is preceded by the path that was used. This is extremely useful in recursive and reentrant batch files:


 @echo off
 %1 %2
 %0 goto pass2
 :pass2
  ...
 :end

Command line arguments can be used to pass any string that doesn't contain a delimiter into the batch file. When a delimiter is encountered, the argument is split into two arguments, and all information about the nature of the delimiter is lost (you have no way of knowing if it was a space, a a tab, a comma, a semicolon, or an equals sign) - quotes don't help: the opening quote mark is part of the earliest argument and the closing quote mark is part of the last one in the string:


 @echo off
 echo %1
 echo %2
when invoked with "this that", "this=that" etc. (with single or double quote marks, or without any) results in the same display

 this
 that

If it is important to pass a delimiter, usually the '=' character, the only ways are to assume it, and add it back in unconditionally; to use some extra flag argument to indicate that it should be inserted, or to split the string into elements and intersperse them with flags. In the following examples, these are illustrated in that order.
Note: the third example has been omitted for two reasons: the '=' case require that each possible location be handled as a special case, which sends the complexity through the roof, and even the supposedly simple cases of ',' and ';' require extra complexity to work around an interesting bug in COMMAND.COM (you can't compare a string containing any delimiter with another string containing any delimiter (though you can compare strings not containing delimiters and null strings with strings containing delimiters)).


 @echo off
 echo %1=%2   invoked with this=that
or

 @echo off
 if "%1" == "flag" echo %2=%3    invoked with flag this=that
 if "%1" == "flag" echo %2 %3

Environment variables are strings associated with a variable's name - in fact, a string associated with a string. Nearly all characters (including delimiters, even spaces) except the '=' sign are permitted in both the name and value. These variables reside in the current environment area of memory, and changes to the environment are accessible to child programs, but not to parent programs. One string is set equal to another by means of the SET command, and the value is read by placing the name between a pair of '%' characters.


 set foo=bar
 echo %foo%

One common problem with these variables is due to the inclusion of unwanted spaces. This is a byproduct of the ability to include spaces anywhere in the strings on both sides of the '=' sign - the name ends at the '=' sign and the value begins immediately after it. The usual remedy for this sort of thing is the use of quoted strings, but quoted strings exist in only a few places in batch file syntax (notably in remarks, where they are meaningless, and in strings to be ECHOed) - everywhere else, the quote marks are simply additional characters. These are the only kinds of variables to which the user can assign a constant value or have carry over from batch file to other programs. They are frequently used inside batch files as scratch pad memory.

Names of environment variables can contain most characters except the equals sign and lower case letters, but can't be counted on to work right unless they begin with a letter character. I'm not certain of the limits to the truth of that statement, but I had to give up using numbers because they didn't work right. Any environment set with the SET command will have its name in all upper case and any reference to the name with the %name% syntax will reference only the upper case name. Normally this is more of a feature than a wart - you don't have to pay attention to the case of the name, just of the value - but Windows sets a variable that gives the Windows directory path, and it has a lower case name: "windir", which makes it inaccessible from batch files. This fragment sets "WINDIR" to the contents of "windir":


 set | find "windir" > }{.bat
 echo set windir=%%1> windir.bat
 call }{
 del }{.bat
 del windir.bat

There are a few built-in environment variables that are always available:


ERRORLEVEL is the exit code of the last executable program to be invoked, whether from the batch file, or before the batch file was invoked. The ERRORLEVEL remains set to whatever it is until another executable exits. DOS clears this to zero if the executable does not return any exit code. The only thing we can do with ERRORLEVEL is to compare it with a number, and then only with an "equals or is greater than" test. This latter "feature" causes no end of trouble for unaware batch programmers. There are three ways to deal with it:

test in inverse order -
if errorlevel 255 goto x255
  if errorlevel 254 goto x254
   ...
  if errorlevel 2 goto x2
  if errorlevel 1 goto x1
  :x0
   ...
  :x1
   etc.
test for "ERRORLEVEL is less than" by using NOT

  if not errorlevel 1 goto xok
  :xfail
   ...
  :xok
or isolate individual values or ranges with a double test

  if errorlevel 1 if not errorlevel 2 goto x1
  if errorlevel 4 if not errorlevel 7 goto x4
  if errorlevel 1 goto xelse
  :x0
   ...
  :x1
   ...
  :x4
   ...
  :xelse
   ...
  etc.

Note that since ERRORLEVEL is of type "byte", its range is 0 through 255. Note also that there is no '=' in the syntax (it would be incorrect if used because the test is not one of equality).

The second example above, and its complement


  if errorlevel 1 goto xfail
  :xok
   ...
  :xfail
   ...
  etc. 
are perhaps the most useful because ERRORLEVEL 0 indicates that the program terminated successfully, assuming that the program actually returned an exit code. However, it must be kept in mind that "success" was defined by the author of the executable and it's meaning may not be quite what the programmer of the batch file would expect - for example,

 XCOPY *.foo c:\temp /a
returns 0 if any files matching *.foo exist in the default directory, whether it actually copies any or not (it wouldn't, unless at least one had its archive attribute bit set). In the example case, the user might expect that ERRORLEVEL 0 would indicate that something had been copied and that ERRORLEVEL 1 would indicate some kind of failure to copy files, and a careful reading of the documentation for XCOPY implies that this is the case - nevertheless, reality is that there is no ERRORLEVEL that indicates that some files matching the pattern were found but none were copied because none satisfied the auxiliary test (the /a switch). DOS is like that - the wise batch file programmer is careful to test each element of the program's action separately.

It is possible to convert the ERRORLEVEL to a string by sequentially comparing it with the numbers 0 through 255 and keeping the last one that matches. There are shorter and structurally less complex ways to do this, but the example here is an exact match for the task of sequential comparison. TEST is the program that sets the errorlevel (third line).


 @echo off
 if "%1" == "}{" goto %2
 test %1
 set flag=run
 set t=
 set return=0
 for %%a in (0 1 2) do call %0 }{ pass2 %%a
 goto end
 :pass2
 set t=%3
 if %3 == 0 set t=
 for %%b in (0 1 2 3 4 5 6 7 8 9) do call %0 }{ pass3 %t%%%b
 goto end
 :pass3
 set t=%3
 if %3 == 0 set t=
 for %%c in (0 1 2 3 4 5 6 7 8 9) do call %0 }{ pass4 %t%%%c
 goto end
 :pass4
 if %flag% == done goto end
 if ERRORLEVEL %3 set return=%3
 if %3==255 goto pass4a
 goto end
 :pass4a
 set flag=done
 :end

This is not exactly practical code since it has to execute 299 times regardless of what the ERRORLEVEL is. I have omitted the structure required to abort the program when the ERRORLEVEL is found because it makes the code extremely difficult to understand - this is a straight forward counter using multiple levels of recursion. Admittedly recursion is a somewhat advanced technique but it should be fairly clear that this code runs the first FOR command three times, the second ten times for each of those three, and the third ten times for each of the times the second one runs - three hundred times in all (0 through 299). It will work either with or without the leading zero suppression provided by the "if %3 == 0 set t=" lines.

One thing to keep in mind when writing sequential comparison code is that the comparison is between the least significant byte of the pattern and the only byte of the ERRORLEVEL - 256 matches 0, 257 matches 1 etc. - the upper byte(s) of the pattern are ignored.

FOR variables are given a temporary value of each element specified or given in the set part of the "for item in set do" statement. Inside batch files they are always in the form of a double '%' followed by a letter:


 for %%a in (1 2 3 4) do echo %%a

 for %%a in (*.*) do dir %%a
These variables have no existence outside the FOR statement.

  ** Copyright 1995,1996, Ted Davis - all rights reserved ** 

Input and feedback from readers are welcome. NOTE: the subject of the message must contain the word "batch" for the message to get past the spam filter.

Back to the Table of Contents page

Back to my personal links page - back to my home page