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
invocation by name or reference
IF file EXISTs IF string equals string IF ERRORLEVEL (and their negations) FOR variable in set DO GOTO CALL
COMMAND /e:nnn /cconstruct. These are discussed in Intrinsic Commands and External Commands.
There are no procedures or functions - no
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
%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
Note that FOO.BAT is indented one column more than the master file and that the file names do not appear in the files.
@echo off call foo echo This is FOO :end :end
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
When invoked, this generates the sequence of commands
@echo off if not "%1" == "" goto foo call %0 foo goto end :foo echo This is FOO :end
Note that the two instances of
@echo off if not "" == "" goto foo call test foo if not "foo" == "" goto foo :foo echo This is FOO :end goto end :end
:endare 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.
To see the action, comment out the
MASTER.BAT @echo off call foo string echo %string% :end FOO.BAT @echo off set %1=This is FOO :end
@echo offline (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:
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 :pass2 echo %3 %4 %5 :end FOO.BAT @echo off %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 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
Note that in that example, the master file must be invoked with just its name, no extension.
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
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
command to create a delay to allow pressing the "any" key to stop the
@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
The second could be done like this:
@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
or recursively, 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
And the third 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
(Note: the use of numerical names for environment variables is not always reliable.)
@echo off goto %1 :0 echo %%1 is 0 goto end :1 echo %%1 is 1 goto end :2 echo %%1 is 2 :end
Something along the lines of
FOR( i = 1; i <= n; i++ ) can be done
using a bang counter ('!' is the "bang" character - though any character can be used, '!' is
sort of customary):
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