Shell scripts are plain-text files that a command shell interprets as a series of commands to be executed in sequence. Shell scripts have a long history: The Command.com shell in MSDOS has supported simple batch-file scripting since the early 1980s. Released with Windows NT in 1993, the text-based Cmd.exe command shell supports a script language similar to the batch language in Command.com. Despite its age, Cmd.exe shell scripting is still a useful tool in the IT professional's arsenal. For example, a simple shell script with a For command can transform a command that works with only one computer or username at a time on the command line into a command that can step through a text file and execute the command for each line in the file. Because shell scripts are plain-text files that contain a series of commands, they are easy to create, modify, and understand. However, these scripts' simplicity can also be a liability. A carelessly written shell script can cause potentially serious problems. I've seen shell scripts that make incorrect assumptions about the underlying OS or environment that, at best, prevent them from working correctly, or at worst, wreak havoc on an unsuspecting system. To help you avoid such problems, I've written the following set of 10 guidelines for creating more robust scripts that can work correctly in various environments.

1. Use Environment Variables When Possible
When you use a variable in a script, you enclose the variable's name between percent signs (%), and when the shell script runs, the percent signs and the text between them will be replaced with the variable's data. Using variables makes the script's code more generic—it has a better chance of running successfully on different computers. For example, you should never "hard-code" the Windows installation directory name in a shell script. Instead, you should use the SystemRoot variable, which will always point to theWindows installation directory. So, the command

Echo %SystemRoot%

will display the contents of SystemRoot (i.e., the Windows installation directory).

In several cases, I've seen shell scripts that used C:\WINDOWS to refer to the Windows installation directory. If Windows isn't installed in C:\WINDOWS (e.g.,Windows 2000 and earlier are installed in \WINNT), the script won't work properly. Environment variables make life easier for programmers and script writers.

To see a list of environment variables, type the Set command at a command prompt. Some variables don't appear in the list generated by the Set command because they're dynamically generated by Cmd.exe. Table 1 shows a list of these variables. As with other environment variables, enclose these variables between percent signs. For example, the following line in a script will display the current date and time:

Echo %DATE% %TIME%

2. Don't Expect Legacy Command.com Batch Files to Work in Cmd.exe
If you're accustomed to writing batch files for the MS-DOS or Windows 9x/Me platforms, be aware that some batch-file commands you're used to don't exist in the newest versions of Windows. The Choice and Deltree commands are the two most common examples. Review your old batch files to make sure they work correctly in a Cmd.exe window. Table 2 shows some suggested replacements for Choice and Deltree. Most of these replacements have a different-syntax than the commands they're replacing, so you'll need to modify your scripts accordingly.

3. Use the .cmd Extension for Cmd.exe Shell Scripts
Command.com batch files require the .bat file extension. Cmd.exe can also use the .bat extension, but in Cmd.exe's more powerful scripting language, many commands aren't compatible with Command.com. Thus, a .bat file that you design to work with Cmd.exe might fail if a user tries to execute it in Command.com (e.g., in Windows 98). One way to avoid this potential problem is to use .cmd as a shell script extension. Because Command.com doesn't recognize the .cmd extension, it simply won't execute a batch file if the filename ends with .cmd.

4. Make Sure the Executables You Want Are Available
If your script relies on programs that aren't a part of the standard OS installation, you must make sure that any such programs are available. You should also document these dependencies in your scripts. One way to ensure executables are available is to put them in the same directory as the script. Then you can run them by using the syntax

"%  ~dp0exename"   ...

where exename is the name of the executable you want to run. The %~dp0 syntax returns the drive, path, and filename of the current script without quotes. Enclosing the entire string in quotes ensures that the script will still work even if the filename or the directory the script is in contains quotes.

5. Use Double Quotes Correctly
Double quotes (") seem to be a common source of confusion for shell-script writers. Quotes are simply a way of identifying command-line arguments that contain spaces. For example, although it might look correct, the command line

dir C:\Program Files

gives the Dir command two arguments: C:\Program and Files. To indicate that you intend C:\Program Files to be a single argument (including the space), enclose it in double quotes:

dir "C:\Program Files"

The quotation marks aren't part of the directory name. They tell the Dir command that the text between them is one argument.

Shell scripts also support command-line arguments. To access a script's command-line arguments, use the notation %n (where n is a number 0 through 9). This notation is called a replaceable parameter (or simply a parameter). For example, the script will replace the %1 parameter with the first command-line argument, %2 with the second argument, and so forth. In addition to %1 through %9, %0 is replaced with the script's name, and %* is replaced with the script's entire command line (not including the script name). I mention command-line arguments here because when Cmd.exe replaces the %1 through %9 parameters with the corresponding command-line arguments, it leaves the double quotes if they're in the argument. This fact leads to a simple observation: A script parameter will always be contained in double quotes if it contains spaces. The following simple rules are based on this observation, and if you follow them carefully, you'll virtually eliminate quoting problems from your shell scripts.

  • Don't put quotes around script parameters (%1 through %9) because they might already contain quotes. If you need to use a parameter with the If command, use characters other than double quotes (e.g., curly braces) to avoid syntax errors. The following script line will work even if the first parameter (%1) contains quotes:
If \{%1\}==\{\} Goto :HELP

The exception to this rule is when you use the syntax %~n to remove a parameter's quotes (which I explain in the next rule).

  • Don't include quotes in the contents of an environment variable. Environment variables can contain spaces without needing quotes. If you need to copy a parameter into a variable, use the syntax %~n (where n is a number 1 through 9), which returns the parameter without quotes. For example, the line
Set DATAFILE=%  ~1

will copy the script's first parameter ( without quotes) into the variable DATAFILE. The exception to this rule is if you're creating quoted text that will be passed to another command.

  • Remember to put quotes around environment variables if needed. For example, consider the following script lines:
Set TARGET=% ~2
Copy %1 "%TARGET%"

The first of these lines copies the script's second argument into the TARGET variable, removing the quotes. The second line is syntactically valid because the %1 parameter is already enclosed in quotes if it contains spaces, and the TARGET variable needs quotes because it might contain spaces.

6. Use the Setlocal and Endlocal Commands
The Setlocal command copies all environment variables, and Endlocal restores all variables to the values they had before the script started. Endlocal also deletes any variables the script created. Using both these commands makes a script more self-contained and ensures that a script "cleans up after itself" by restoring environment variables to their original values and deleting variables the script created.

Also, you can use the Setlocal Enableextensions command to make sure that command extensions are enabled. Command extensions are enhancements to a group of Cmd.exe internal commands (e.g., If, For, Call) that provide expanded capabilities beyond the Command.com commands that have the same names. Command extensions are enabled by default, but in the rare cases in which command extensions are disabled, the Setlocal Enableextensions command ensures that command extensions are enabled. To see more information about command extensions, type Cmd /? at a command prompt.

7. Use the Escape Character When Needed
Cmd.exe uses the caret (^) as an escape character that bypasses the normal meanings of reserved shell characters. For example, the ampersand (&) is the command separator—it lets you put multiple commands on one line. If you intend to use the & literally, you must "escape" its normal meaning by prefacing it with the ^. So in the line

Echo The ^& character is the command separator

the ^ causes the shell to bypass the normal interpretation of the character that follows. You need to escape the characters ( ) < > ^ & and |. You don't need to escape these characters if they occur inside a quoted string.

8. Don't Use the Exit Command Without the /b Option
Without the /b option, the Exit command closes the current shell. If someone starts a Cmd.exe session and executes a script that contains the Exit command, the current shell will abruptly close. The Exit /b command closes the current script without terminating the current shell. For more information about the Exit command, type the command Exit /? at a command prompt.

9. Watch Out for If Errorlevel
The If Errorlevel command tests the exit code of the last command that was executed. Scripts can test a program's exit code and behave accordingly. For example, consider the following script code:

Myprogram
If Errorlevel 1 Goto :ERROR

In these lines, the Goto command will execute if Myprogram.exe returns an exit code greater than or equal to 1. In other words, "If Errorlevel n" doesn't mean "if the last exit code is exactly n;" it really means, "if the last exit code is at least n." Because of this behavior, make sure to test exit codes in descending order (highest to lowest). To test for a specific exit code, use the ERRORLEVEL dynamic variable instead of If Errorlevel. Note that the If Errorlevel command is different from the ERRORLEVEL variable listed in Table 1. The If Errorlevel command is backward compatible with Command.com's If Errorlevel command; the ERRORLEVEL variable is only available in Cmd.exe.

10. Be Aware of Start's Quirks
The Start command starts a program or a command in a new console window. However, if you try to start a program from a directory that contains spaces (or if the program's name contains spaces), the Start command won't behave as you might expect. For example, the following command

Start "C:\Program Files\Microsoft
  Office\Office11\Winword.exe"

doesn't start Microsoft Word, as you might expect. Instead, the Start command will open a new Cmd.exe session with the quoted string as the title of the console window. This behavior occurs because the Start command uses the first quoted string on its command line as a console window title.

To work around this quirk, use a pair of quotes to specify a blank title, then follow the title with the program you want to run. The corrected Start command will look like this:

Start "" "C:\Program Files\Microsoft
  Office\Office11\Winword.exe"

Avoid Potential Problems
Cmd.exe shell scripts are widely used. With these guidelines under your belt, you can write more robust shell scripts and avoid common problems.