Debugging Batch Files

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

 %debug%
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).

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:

A week or two ago, I got clobbered when the standard batch file method of detecting whether two files are identical failed due to the nature of the files and I used my regular approach to build a batch file test that didn't fail that way, but had a large gotcha, which I found when I tested it over a wide range of inputs - then the general question was posted and the standard answer was given, and I replied with the fix. *But* ... the fix was at work and I was at home - I was tired and rushed, so I cut corners - I did a quick knockoff using the regular procedure, copied the file to the archive, realized that I had left out the fix for the problem with the fix, modified the copy in the archive, tested it in situ ... then posted the unfixed copy from the development directory ... oops! My standard procedure for that is to copy the archive copy back to the development directory (which I should have cleaned out after archiving the file), erase the archive copy, repair the development copy, test it in the safe directory, then copy it back to the archive, 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