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:
foo
instead of
w:\bin\util\internet\foo
or
bar
instead of
\\Teddavis\c\MyFiles\bar
(yes, I really work in the Win95 environment, I just write for the MSDOS 6.22 environment).
However, unless w:\bin\util\internet is the default directory or in that list, DOS (COMMAND.COM actually) will be unable to find it because it hasn't a clue to the first part of the filespec. That list is up to the user - the default is usually
C:\DOS
%path%. It has a rigid format, both the argument to the PATH command and to the SET command must be a semicolon delimited string of drive and directory paths with or without trailing backslashes. If SET directly, the string should be in all upper case (at least it is believed that it must be for at least some versions, it is not necessary for 6.22).
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:
if errorlevel 255 goto x255
if errorlevel 254 goto x254
...
if errorlevel 2 goto x2
if errorlevel 1 goto x1
:x0
...
:x1
etc.
if not errorlevel 1 goto xok
:xfail
...
:xok
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