Although there are very few constructs available for controlling the flow of batch programs, these few can be used imaginatively to accomplish most of the constructs familiar to users of high level languages.
We have only
IF file EXISTs
IF string equals string
IF ERRORLEVEL
(and their negations)
FOR variable in set DO
GOTO
CALL
invocation by name or reference COMMAND /e:nnn /c construct. These are
discussed in Intrinsic Commands and
External Commands.
There are no procedures or functions - no DO or WHILE, no switch() or
CASE constructs - no return values from functions that don't exist.
None the less, all of those can be simulated or emulated, as can linked lists of commands (and perhaps even objects).
Procedures are rather easy, if you don't mind a clutter of small files
eating up your hard disk - simply make a separate batch file of each
procedure. Because most procedures would consume at least one full
cluster of disk space, and multiple files tend to become separated and
lost (especially when cloning a program to a new location and when
cleaning out dead wood from the HDD), it is usually preferable to make
the basic batch file recursive, in the sense that it calls itself
rather than in the sense that the internal code is reentrant (though
it might be). This is accomplished by CALL %0. %0 is the name of
the program as given in the command that invoked the program, and
sometimes needs to be given in full filespec form - particularly when
the program changes the default directory and the batch file is not in
the path. This trivial example shows a single procedure call. The
procedure batch file follows the master one.
MASTER.BAT
@echo off
call foo
:end
FOO.BAT
@echo off
echo This is FOO
:end
This results in the sequence of commands
@echo off
call foo
echo This is FOO
:end
:end
Note that FOO.BAT is indented one column more than the master file
and that the file names do not appear in the files.
The procedure does not need to begin with @ECHO OFF, since echoing is
already off. The two :end labels are unnecessary (there are there to
show when each ends and also as markers for an automatic text
processing program that I use for special formatting of these files).
When the two are combined into one file, and the simplest recursion method is used (see: Recursion in Batch Files), we get
@echo off
if not "%1" == "" goto foo
call %0 foo
goto end
:foo
echo This is FOO
:end
When invoked, this generates the sequence of commands
@echo off
if not "" == "" goto foo
call test foo
if not "foo" == "" goto foo
:foo
echo This is FOO
:end
goto end
:end
Note that the two instances of :end are of the same label.
Functions are harder, since there is no real way to return a value, except in a global variable (an environment variable). However, we can tell the function which variable to return the value in by passing it the variable's name as a command line argument.
MASTER.BAT
@echo off
call foo string
echo %string%
:end
FOO.BAT
@echo off
set %1=This is FOO
:end
To see the action, comment out the @echo off line (with ::) and run
MASTER.BAT. The action is similar to the above expansions.
Alternatively, we can jump to the function, passing it the name of the master file and the marker and commands to reenter the master file and have it reinvoke the master file with the return value as command line arguments, but this doesn't really have a common parallel construct in high level languages:
MASTER.BAT
@echo off
if not "%1" == "" %1 %2
foo %0 goto pass2
:pass2
echo %3 %4 %5
:end
FOO.BAT
@echo off
%1 %2 %3 This is FOO
:end
Procedures can also be handled this way, but without the return value.
The primary use for that kind of reentrancy is to cause COMMAND.COM to
forget about the master file so that it can be modified on the fly. A
trivial example, that can be used only once for each copy of the
master file is
MASTER.BAT
@echo off
if not "%1" == "" %1 %2
foo %0 goto pass2
:end
FOO.BAT
@echo off
echo :pass2 >> %0
REM use double '%' when a real '%' character is needed.
echo echo %%3 %%4 %%5 >> %0
%1 %2 %3 This is FOO
:end
Which constructs the target part of MASTER.BAT on the fly. Normally
FOO.BAT would invoke some editor to edit the master file or copy an
already edited one over it:
MASTER.BAT
@echo off
foo %0
:end
FOO.BAT
@echo off
copy baz.txt %1.bat > nul
%1
:end
BAZ.TXT
@echo off
echo This is BAZ
:end
Note that in that example, the master file must be invoked with just
its name, no extension.
DO WHILE and WHILE DO differ only in that the former executes its loop
at least once, regardless of the condition of the test variable. The
difference is that the loop test is at the beginning of a WHILE DO and
at the end of a DO WHILE loop. These examples use the KPAUSED program
from the discussion of IF ERRORLEVEL. Each also uses a DIR c:\DOS
command to create a delay to allow pressing the "any" key to stop the
loop.
@echo off
echo DO WHILE follows
dir c:\dos
echo Beginning DO WHILE loop
:do
echo Still running DO WHILE
kpaused
if not ERRORLEVEL 1 goto do
echo DO WHILE loop has ended - WHILE DO is next
pause
dir c:\dos
:while
kpaused
if errorlevel 1 goto done
echo Still running WHILE DO
goto while
:done
echo WHILE DO loop ended
:end
switch() is implemented with a string of IF tests. There are at least
three ways to implement it: with negative tests and jumps around the
case code, with positive tests and CALLs to procedures, and with GOTOs
to the variable (but there can be no default case here). The first
can be done this way (testing %1 against the first three decimal digit
characters):
@echo off
if not %1 == 0 goto t1
echo %%1 is 0
:t1
if not %1 == 1 goto t2
echo %%1 is 1
:t2
if not %1 == 1 goto default
echo %%1 is 2
:default
echo %%1 is not in the set 0, 1, 2
:end
The second could be done like this:
@echo off
if %1 == 0 call zero
if %1 == 1 call one
if %1 == 2 call two
call default
:end
ZERO.BAT
@echo off
echo %%1 is 0
:end
ONE.BAT
@echo off
echo %%1 is 1
:end
TWO.BAT
@echo off
echo %%1 is 2
:end
DEFAULT.BAT
@echo off
echo %%1 is not in the set 0, 1, 2
:end
or recursively, like this:
@echo off
if not "%1" == "" goto %1
if %1 == 0 call %0 zero
if %1 == 1 call %0 one
if %1 == 2 call %0 two
call %0 default
goto end
:zero
echo %%1 is 0
goto end
:one
echo %%1 is 1
goto end
:two
echo %%1 is 2
goto end
:default
echo %%1 is not in the set 0, 1, 2
:end
And the third like this:
@echo off
goto %1
:0
echo %%1 is 0
goto end
:1
echo %%1 is 1
goto end
:2
echo %%1 is 2
:end
(Note: the use of numerical names for environment variables is not always reliable.)
set n=%1
set i=
:loop
set i=%i%!
code to do something repetitive
if i == n goto end
goto loop
:end
This builds a string of bangs, adding one on each pass, until the I string matches
the N string, at which point it exist. This is really a DO loop that loops on <=
but it can be converted to a WHILE loop that loops while i < n by moving the IF test
to the line following the SET in the loop. In the form given, the test will crash
on the first pass if it is ahead of the SET (syntax error because I == nul). A
different approach to a bang counter is found in the section on list processing.
** Copyright 1995, 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