DOS Batch Files: Essentials
(Unix users may find this section annoying because they
already understand the concepts, but the specific manner in which
they translate to DOS is important.)
The first thing to remember about writing DOS batch files is
you're structuring a process with two kinds of building blocks:
Dynamic ("real time") interaction with the user.
You talk to the user with the Echo command.
Anything you place after the Echo command will be written to
standard
output--and this defaults to the user's screen if there's
no redirection.
The user communicates with you by pressing keys. Normally this
is done with the DOS Choice command or ADB's BE2 utility.
Here's a quick example of how to use BE2:
Echo If you want option A, press A
Echo If you want option B, press B
Echo If you want option C, press C
Echo The default is A. You can hit the enter key
Echo or the spacebar to get the default.
BE2 "ABC"
If ErrorLevel 3 Goto OPT_C_PROCESS_LABEL
If ErrorLevel 2 Goto OPT_B_PROCESS_LABEL
Goto OPT_B_PROCESS_LABEL
I'll talk more about DOS's If, ErrorLevel, and Goto (with labels)
later on in this section.
(DOS offers a similar Utility called Choice. Unlike
BE2, it's possible for the user to "break out" of it.)
"Batch" interaction.
All batch interaction consists of executing programs and/or DOS
commands that (a) read and write to files; and/or (b) execute
other DOS batch files; and/or (c) change the DOS environment
(by setting DOS variables).
This duality is common to all forms of "scripting" and should be
quite familiar to those of you who've worked with Unix.
I briefly spoke about "return codes" in an earlier section. A
return code is a number from 0 through 255 that a program can
"return" to the DOS shell. (Unix uses note: DOS doesn't support
more than 255 as the max for a return code, whereas Unix return
codes are unsigned short integers.)
You can test return codes with the "If ErrorLevel" construct. This
can be used both at the command prompt as well as in batch files.
Most decently-written programs return 0 on successful execution,
otherwise a value higher than 0. ADB is a member of another large
class of programs that returns 1 if there's some reason to be
concerned, else a value higher than that. In ADB's case, a return
value of 1 is for warnings.
The DOS If ErrorLevel
construct is special in that it's "true" when
the return code is
greater than or equal to the tested value.
For example, if the return code is 2, then this statment will always
branch to the label FAIL:
If ErrorLevel 1 Goto FAIL
If you need to distinguish between more than a zero return code and
a nonzero one, then you need to test for the higher error levels
first.
For example, after running ADB:
If ErrorLevel 2 Goto CATASTROPIC_ERRORS
If ErrorLevel 1 Goto WARNINGS
For reasons unknown to me, Microsoft has refused to set return
codes for any DOS commands. This makes it particularly difficult
to work with many of them. For example, if you copy files from
one place to another, and there's not enough hard disk space, the
return code is still 0.
You can also test the value of DOS variables with the If statement.
If (%VARNAME%)==(VALUE) Goto VARNAME_IS_VALUE_LABEL
Unix users won't be surprised about the double equals signs, but others
should note them. The parenthesis aren't required, but if it turns
out that VARNAME hasn't been set, they will save you from getting a
syntax error.
For example:
If (%VARNAME%)==() Goto VARNAME_NOT_SET_LABEL
The If statement also allows you to check for the existence of files.
If Exist File.Dat Goto FILE_DOT_DAT_FOUND_LABEL
You can use wildcard characters in the file name (as well as path
information.)
Although the DOS If statement doesn't support logical operators
(such as "AND" and "OR") you can "cascade" your Ifs to create an
implicit conjunction:
If Exist File1.Dat If Exist File2.Dat Goto FILE12_EXIST_LABEL
(The idea of the above label is that file 1 and 2
both exist.)
The unary negation operator ("NOT") is supported.
If Not Exist File1.Dat Goto FILE1_NOT_EXIST_LABEL
"Not" can also be used in the other cases where you can use "If"
(e.g. in ErrorLevel testing, as well as in seeing whether a variable
has a value).
Once again, I should remind you that DOS variables are
case-sensitive.
DOS supports no numeric
operations--all comparisons between variables
and values are done as strings. So, for example, "1.0" is
not equal to "1".
You can use labels in DOS batch files for branching (in fact, I've
been doing this all along in the examples).
Just prefix the label name with a colon (and don't put any excess
spaces on the left). For example:
:LABEL_NAME
case-sensitive.
I suggest
that you avoid all characters except letters, digits, and
underscores.
Although a label name, like a variable name, can begin with a digit,
I personally think that this is bad form.
WARNING
Only the first 8 characters of labels matter. If you
have two labels, both of which begin with the same sequence of
8 characters, only the first will have any effect.
In fact there's no reason why your labels have to be
constants when they're used. It's
perfectly okay to do something like this:
Set DEST_LABEL=VAR1_BAD_VALUE_LABEL
If (%VAR1%)==(1) Set DEST_LABEL=VAR1_IS1_LABEL
If (%VAR1%)==(2) Set DEST_LABEL=VAR1_IS2_LABEL
If (%VAR1%)==(3) Set DEST_LABEL=VAR1_IS3_LABEL
Goto %DEST_LABEL%
This is particularly useful for creating "pseudo subroutines".
For example:
Set FILE=File1.Dat
Set RETURN_TO_LABEL=RLABEL1
Goto SUBROUTINE
:RLABEL1
Set FILE=File2.Dat
Set RETURN_TO_LABEL=RLABEL2
Goto SUBROUTINE
:RLABEL2
Goto DONE
:SUBROUTINE
If Exist %FILE% Del %FILE%
Goto %RETURN_TO_LABEL%
:DONE
WARNING
Only the first 8 characters of labels matter. If you
have two labels, both of which begin with the same sequence of
8 characters, only the first will have any effect.
(I know I said this before, but I wanted to make sure that
no one missed it. It's very difficult to spot errors that arise
from labels which are identical in the first 8 characters.)
This relatively useless DOS batch file sets the variable FILE to
two different values and then branches to the label SUBROUTINE.
If the file whose name is in the variable %FILE% exists, then
it's deleted, and we return to the label that's in the
variable name %RETURN_TO_LABEL%.
Note that I had to "walk around" the subroutine by branching to
the label DONE after both "subroutine calls" had been completed.
Once again, I should emphasize that variable names are surrounded
by percent signs when they're used but not when they're
set (assigned).
"Pseudo subroutines" can save you from having to create more DOS
batch files than you really need to get the job done. (You can
use this same technique in any language that allows labels.)
If you've run any of the DOS batch files that I've used as examples,
you might notice that the lines are written to the screen as they're
executed.
This is very useful if you're trying to debug the file, but not so
nice when the debugging is finished, and the batch file is being
executed by a user, client, or customer.
If you don't want the lines to automatically echo to the screen,
you can prefix them with an
@-sign. Let's see the previous example
again:
@Set FILE=File1.Dat
@Set RETURN_TO_LABEL=RLABEL1
@Goto SUBROUTINE
:RLABEL1
@Set FILE=File2.Dat
@Set RETURN_TO_LABEL=RLABEL2
@Goto SUBROUTINE
:RLABEL2
@Goto DONE
:SUBROUTINE
@If Exist %FILE% Del %FILE%
@Goto %RETURN_TO_LABEL%
:DONE
Note that you
can't and mustn't prefix a label with an
@-sign.
(Footnote: many people used to write DOS batch files with the
"Echo ON" and "Echo OFF" commands to turn echoing on and off. I
personally don't like to use them because you have to use "Echo
ON" every time you want to communicate with the user. I find the
@-sign to be considerably more elegant.)
You can use the Rem (remark) statment to include helpful comments
in your DOS batch files.
For example:
@Rem
@Rem Here's the first invocation of the
@Rem pseudo-subroutine:
@Rem
@Set FILE=File1.Dat
@Set RETURN_TO_LABEL=RLABEL1
@Goto SUBROUTINE
@Rem
@Rem RLABEL1: Return here after pseudo-subroutine call.
@Rem We invoke the pseudo-subroutine again.
@Rem
:RLABEL1
@Set FILE=File2.Dat
@Set RETURN_TO_LABEL=RLABEL2
@Goto SUBROUTINE
@Rem
@Rem RLABEL2: Return here after pseudo-subroutine call.
@Rem We're finshed and will branch to DONE
@Rem
:RLABEL2
@Goto DONE
@Rem
@Rem SUBROUTINE: Here's the pseudo-subroutine
@Rem
:SUBROUTINE
@If Exist %FILE% Del %FILE%
@Goto %RETURN_TO_LABEL%
@Rem
@Rem DONE: We jump here after all pseudo-subroutine
@Rem calls are completed
@Rem
:DONE
Do not use any of the special characters (percent signs, or
greater-than or less-than signs
in your comments.) This is
because Rem is still an "ordinary" DOS command.
Since labels can often lead to "spaghetti logic," I suggest that
you use them in disciplined manner. Branching should always be
forward in the file, whenever possible (except for
pseudo-subroutines).
It's good programming practice to proceed each label with a
comment "block" (as you see above) that provides the name of the
label, and the conditions under which a "Goto" will branch to it.
And while I've talked to many people who believe that labels are
to be avoided whenever possible, there's no way to dispense with
them in DOS batch
language--because there's no concept of "code
blocks."
Return
to local table of contents
Return
to global table of contents
Frames mode, or
No frames