We have few tools for debugging batch files, and those we do have are very crude by the standards of regular programming languages. We have ^C, which will sometimes stop a batch file; we have PAUSE, which we can add after some or all lines; we have COMMAND /cfoo > bar; and in DOS 6, we have the /y switch to COMMAND, which is a more convenient equivalent of adding PAUSE before every line, but does require a secondary command processor. All except the last need to have the ECHO OFF command commented out.
For very small, very simple, batch files, we can just comment out the @ECHO OFF and see what happens, but this breaks down rather quickly if the structure contains loops or is reentrant.
PAUSE stops processing and prompts for any key. If ECHO is off,
this lets us see a few of the most recent commands (with the variables expanded)
and any output from those commands. A reader suggested making the PAUSE
command a variable so that they can be turned off and on with an environment variable.
Of course, we could make the commands conditional (if !%debug%==!true pause), but
simply making a command line
and setting DEBUG to "pause" either at the command prompt or in a wrapper batch file
is more compact. It also has the advantage that we don't really have to decide what
command it is to be until run time. It has the disadvantage that setting it for
use with one batch file, and then running another before deleting the setting can
have unexpected effects on the second program. My approach to this is to use something
like %foodebug% where foo is the name of the program or part of the name. Note
that you cannot use %0 inside the variable name (that would be an obvious way to
incorporate the name without having to know it at coding time, but only one level of
variable reference can be used).
%debug%
For some years, I relied on COMMAND /cfoo > bar (where foo is the
batch file and bar is the file into which I want to redirect the
output of the test) and an AWK command that added PAUSE in front of
every line. I always do my development with the file in work named
TEST.BAT and, if there are several in work at one time, the individual
TEST.BATs are in directories named what the final file will be named.
The following ADDPAUSE.BAT file is in c:\bin, which is in the path, as
is a free version of AWK (specifically MAWK, from the DOS under UNIX
library of UNIXFORUM on Compuserve). Note that I have to comment out the @ECHO OFF
line and make sure that the last line has a CRLF at its end, both of
which must be fixed when cleaning up the program for release.
@awk "{print \"pause\"; print $0}" test.bat > testp.bat
This is now obsolete, except in special cases, thanks to the /y switch for COMMAND.COM - TESTY handles this:
@command /e:1024 /y /ctest
In some cases, I need a permanent record of the action, and for that I run the batch file in a secondary command processor with its output redirected to a file - again I am very consistent with my file names, so RUN2ND.BAT can be used to automate the syntax:
@command /e:1024 /ctest > dump.lst
Note that there are three acceptable responses to the Y/N prompt: Y, N, and ^C - the latter aborts the program (useful when you see the problem and really don't want to have to step through the rest of the file).
The /e:1024 switch ensures that the batch file will have plenty of
environment to work with. If the program loops, ^C generally stops
it. Examination of the dump file allows tracing the action, step by
step and line by line.
Note that in all these cases where ECHO OFF is deactivated, the lines
that are actually displayed are those that will actually be executed,
with all macros and variables expanded.
When I am having problems with batch files that use environment variables, I usually set up a CLEANUP.BAT file in the same directory that contains the cleanup line(s) from the file in test, so that I can comment out the clean up code and examine the environment and temporary files after the file in test has terminated or has been aborted, and then clean it up in preparation for the next pass. CLEANUP.BAT clears all the environment variables used by the program and erases all it's temporary files.
I have also found it helpful to work in a DOS window under Windows (so that, if all else fails, I can abort the program by using the "three finger salute" (Ctrl-Alt-Del) to kill the DOS window), and to point the temporary files to a RAMDRIVE. I also work out of the RAMDRIVE and keep a copy of the latest stable stage of the work on the HDD (so that if something goes terribly wrong, rebooting will remove all the likely damage, and set me back to the last stable state).
I have said that I always work in a certain way, using specific procedures and file names - that's not exactly true, and the exceptions tend to get me in trouble, so do as I say do, not as I do. The most recent example is from a couple of days ago:
DEL *.* the development directory, and
post the archive copy.
Another exception, this one valid, to my standard procedure is when
the batch file is potentially so destructive that it must be tested on
a machine that can be recovered easily - an example would be any batch
file using FDISK, FORMAT, SYS, or any other utility that can trash the
HDD. I would also be wise to test those using DEL *.* and DELTREE on
such an expendable machine -certainly not on my main system. If you
don't have the luxury of having a test machine, the best insurance is
a good set of current, tested, backups and a set of tested
recovery disks that include the software required to restore the
backups as well as to rebuild the HDD and operating system.
The names given to labels and variables can make large differences in the ease of debugging - labels must be no more than eight characters, and it is good practice to conserve environment space by keeping variable names short, but they should not be so short as to compromise clarity. Labels and variable names should also be clearly unique, but also clearly members of the sets to which they belong - for example, a subroutine can be clearly delimited with labels such as
:foo
foo code
:fooend
but if the code contains a jump to a label outside the function, as
for a return from a recursive call to the batch file, the obvious
labels are :end and :return, but :return should not be used for the
label at the end of the main program. If the jump is to the beginning
of another subroutine, the jump target should be the one that begins
the new routine. These somewhat conflicting requirements can be met
during development by the use of multiple labels at the same logical
place in the code, and cleaned up for the release version by replacing
the jumps to routine end labels with jumps to the adjacent begin
labels and converting jumps to :return into jumps to :end as here:
:foo
foo code
goto return
:fooend
:bar
bar code
:barend
:return
:end
becomes
:foo
foo code
goto end
:bar
bar code
:end
I make it a point to avoid having more than one label beginning with
:end, though long ago I used to use "end", "endd", "enddd", etc. -
but I found that I tended to confuse them. My current practice is to
use "end" only at the end of the program, especially when it is used
to implement a return from a function call, and "done" or "foodone" or
"fooend", where foo is the name of the function needing an end label,
though usually this can be avoided by considering the label to be the
beginning of something else rather than the end of something. My
practice in this area is still changing, so you may see the occasional
"endfoo", but I know it isn't the best possible practice.
** 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