Wednesday, March 17, 2010

Better Batch Files Through Command Extensions

The first batch files arrived alongside the very first versions of MS-DOS and PC-DOS. If you have been around the personal computer for many years, you undoubtedly remember, perhaps not fondly, AUTOEXE.BAT. Power users probably had two or more of them, and some means by which to choose which one to use the next time they engaged their DOS boot diskette. Later came such features as batch files that displayed menus, read inputs from the beloved DOS prompt, and made processing decisions based on those inputs. More sophisticated batch jobs accepted command line arguments, just as did the commands that were built into DOS itself, and the dozens of utility programs, such as XCOPY, that came with it.

The Primitive Command Line Interface

While those early batch files accepted arguments, sometimes called parameters, the command parser was very picky. Testing the value of an argument was extremely limited, and followed a syntax that was familiar to C programmers, but alien to most other computer users, as shown in Listing 1.

IF "%1" == "DE4DATA" GOTO CHECK_1
IF "%1" == "de4data" GOTO CHECK_1
IF "%1" == "De4data" GOTO CHECK_1
IF "%1" == "De4Data" GOTO CHECK_1
 
Listing 1 shows the only way you could have any degree of case insensitivity in your command line argument tests in MS-DOS and PC-DOS.

Listing 2, below, shows a set of tests that gives more complete coverage.

IF "%1" == "DE4DATA" GOTO CHECK_1
IF "%1" == "de4data" GOTO CHECK_1
IF "%1" == "DE4data" GOTO CHECK_1
IF "%1" == "DE4Data" GOTO CHECK_1
IF "%1" == "DE4DAta" GOTO CHECK_1
IF "%1" == "DE4DATa" GOTO CHECK_1
 
IF "%1" == "dE4DATA" GOTO CHECK_1
IF "%1" == "de4DATA" GOTO CHECK_1
IF "%1" == "de4dATA" GOTO CHECK_1
IF "%1" == "de4daTA" GOTO CHECK_1
IF "%1" == "de4datA" GOTO CHECK_1
 
IF "%1" == "De4Data" GOTO CHECK_1
IF "%1" == "De4DaTa" GOTO CHECK_1
IF "%1" == "De4DatA" GOTO CHECK_1
 
IF "%1" == "DE4data" GOTO CHECK_1
IF "%1" == "DE4Data" GOTO CHECK_1

Listing 2 requires 16 tests to provide partial case sensitivity.

That is 16 lines of code to provide partial case sensitivity for one command line argument! Although the example above is extreme, it illustrates why batch programmers usually confined themselves to terse parameter values.

The Command Parser Grows Up

Windows NT brought with it a new command processor, CMD.EXE. Unlike its ancestor, COMMAND.COM, CMD.EXE is a full fledged 32 bit console mode Windows program. It can do everything that COMMAND.COM can do, and much more. One of its most useful new capabilities arises from command extensions, which are enabled by default. You can turn them off, but why?

IF "%~1" EQU ""                         GOTO DO_ALL
IF /I "%~1" EQU "DE4Data"               GOTO CHECK_1
IF /I "%~1" EQU "Home_Office"           GOTO CHECK_2
IF /I "%~1" EQU "Remote_IncrEase"       GOTO CHECK_3
IF /I "%~1" EQU "Home_Office_IncrEase"  GOTO CHECK_4
IF /I "%~1" EQU "UTIL"                  GOTO CHECK_5
IF /I "%~1" EQU "QB4_Source"            GOTO CHECK_6
Listing 3 does, in its second of only 7 lines, much more than the 16 lines shown in Listing 2, because it is fully case insensitive.

Listing 3, taken from a production batch file, illustrates several of the capabilities provided by command extensions.

  1. The second through seventh lines modify the IF command with a new /I switch, making its tests fully case insensitive.
  2. All seven lines take advantage of another command extension, the tilde (~), to strip away quotation marks around the command line argument ("%~1"). I'll say more about this shortly.
  3. The third command extension illustrated in Listing 3 is the mnemonic relational operator, EQU. Thanks to command extensions, the IF command now supports a complete set of relational operators.

Who Can Use Command Extensions?

By default, command extensions are enabled, and are available in all versions of Windows that derive from the Windows NT code base, starting with Windows NT 4. In addition to NT 4, this includes Windows 2000, Windows XP, Windows Server 2003 and 2008, Windows Vista, and Windows 7.

Why Keep the Quotation Marks?

Despite many improvements, the command parser still has a few quirks, one of which is that it sometimes gets confused by bare words that are neither internal commands, nor recognizable program or batch file names. Thus, it is usually best to keep the quotation marks around the text against which an argument is being compared. However, if the argument is enclosed in quotation marks because it contains embedded spaces, and the test, itself, is also enclosed in quotation marks, the result is that the comparison string is enclosed in two sets of quotation marks, as shown in Table 1.

Command Line Argument

“Document to Process.doc”

Old Style Comparison

If “%1” == “This is the Document.doc”

Outcome of Old Style Comparison

If ““This is the Document.doc”” == “This is the Document.doc”

New Style Comparison

If “%~1” == “This is the Document.doc”

Outcome of New Style Comparison

If “This is the Document.doc” == “This is the Document.doc”

Table 1 illustrates the behavior of old style comparisons, done without the benefit of command extensions, and the new style, which leverages them to make the test work as you would expect.

Conclusion

While graphical interfaces and tools are nice, and they have a valuable place in our tool sets, so does the lowly command line interface. Batch files are fairly easy to write, test, and debug, run fast, are ready to run without a compiler or specialized interpreter, run without fuss on any machine, and can go where a program with a graphical is a waste, such as in logon and logoff scripts and scheduled tasks, none of which has a visible user interface.

While I do my share of graphical programming, batch files remain an essential part of my production environment, and occasionally become part of packages that I deliver to clients.

Use the following resources to learn more about command extensions, and start building flexible, powerful, modern batch files.

References

  1. http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/if.mspx?mfr=true is the official documentation of the modern IF command.
  2. http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true is nominally about the new command processor, CMD.EXE, but it includes basic information about command extensions, including several ways to enable and disable them.
  3. http://www.robvanderwoude.com/local.php is a discussion of the SETLOCAL and ENDLOCAL commands, which includes one of several nifty tests that you can use to verify that command extensions are enabled.
  4. http://technet.microsoft.com/en-us/library/bb490920.aspx lists and documents the full set of relational operators that become available with command extensions enabled.