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:
|
|
PATH |
The path to any file is the drive letter (or network volume), colon, backslash, and a list of backslash delimited nested directories that tells where it is. DOS provides a way for you to give it a list of such paths that you want it to search to find an executable you have invoked by typing in just its name, for example,
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
You can change the path at any time, but it is usually
initially defined in AUTOEXEC.BAT with the PATH command. It is
an environment variable, so it can be viewed or changed directly with the
SET command and accessed with
%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).
There is a limit on the maximum path length, a couple of them even, but the longer the path, the longer it takes to search. See "Wrappers" for how to manage the path for maximum performance. There is some argument over the question of short path vs. long path with everything in it. The opposition cannot provide a response to the argument that taking the long path approach severely limits the number of different program you can have and the explicitly of the directory names that can be used (to get the maximum possible number of directories into the limited PATH, directories have to have single character names). A cluttered path is never actually necessary.
|
|
COMSPEC |
Unless you have very specific needs, this one is best treated as a read-only variable. It isn't, but changing it to something that doesn't work will crash the system when the next memory hungry application terminates - you get that most dreaded of all error messages: "Unable to load COMMAND.COM, system halted."
COMSPEC holds the path to the copy of COMMAND.COM that COMMAND.COM will use to reload its transient portion in the upper part of the available memory when a program that uses that area of memory terminates. It is usually defined in the SHELL statement in CONFIG.SYS as the isolated path that follows the filespec of the command processor to use. It need not be the same copy, and in network installations, it often isn't, but it absolutely must be an identical copy of the one actually used to boot the machine.
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.