BATCH FILE TIPS AND UTILITIES The Eighth in a series by Mitchell A. Hoselton CONVERTING EXIT CODES TO ENVIRONMENT VARIABLES There is only one best technique for converting an exit code to an environment variable. It's worth a little effort to work through the process of "discovering" that method. The procedure is not intuitively obvious. In fact, it wasn't until 1990 that anyone reported it. Without some preparation, it is not easy to understand even after you've seen it. In this article we'll work our way up to it. Also, we can all gain some good programming experience by working through the simpler approaches. We'll start with a simple strategy and explore ways to improve it. I recommend following along by actually creating and testing these batch files yourself. Our task is to write a subroutine that saves a copy of the current exit code. I propose storing the value of the exit code in the environment. There may be a number of other ways to store the exit code, but for now, we'll only examine ways that use the DOS environment. Saving the exit code in the DOS environment converts it to a string, but for all practical purposes that is not going to matter a bit. There is one caveat. DOS stores the exit code with the resident part of COMMAND.COM. When a secondary command processor loads into memory, it does not receive a copy of the prior command processor's exit code. A newly loaded secondary command processor starts out with its exit code set to zero. Therefore, batch files must execute any subroutine written for this purpose with the CALL command. Otherwise, it will not have an exit code, other than zero, to read. This is true no matter what method we finally use to store the exit code. It is simply a fact that no subroutine called with COMMAND.COM will be able to read the exit code stored with the command processor loaded below it memory. The CALL command runs subroutines without loading a secondary command processor. The calling batch file and the subroutine both have access to the same DOS environment and the same command processor. Both can read the exit code from that command processor. The CALL command first appeared in DOS version 3.3. Trying to write any subroutine to read and save the exit code under DOS versions prior to 3.3 is, therefore, doomed to failure. THE BRUTE FORCE APPROACH The IF ERRORLEVEL test_value test is TRUE, when the exit code is greater than or equal to the test_value. A simple brute force approach for finding and saving the exit code's current value could use an ascending cascade of commands like the following. IF ERRORLEVEL test_value SET variable_name=test_value As long as test_value is less than the current exit code, the SET command executes on each line. The cascade must start with zero and move upward in integer steps through successive values of test_value. The early IF ERRORLEVEL tests are TRUE and the SET command executes each time. It sets the environment variable equal to the test_value at each step. Eventually, the sequence rises to the point where the test_value equals the exit code. The IF ERRORLEVEL test is still TRUE (remember the "or equal" part of the test). For the last time, the batch file sets the environment variable equal to the test_value. This time, moreover, test_value equals the exit code. Therefore, the environment variable, the test_value and the exit code are all equal following that SET command. When the batch file tests the next higher test_value the IF ERRORLEVEL test is FALSE for the first time. The test_value is finally larger than the exit code. The SET command does not execute again during the subroutine because the test_value continues to get larger and the IF ERRORLEVEL continues to be FALSE in each successive test. That leaves the environment holding the test_value that is equal to the exit code. LISTING 1 outlines ERROR_0.BAT, the simplest batch file subroutine that I can devise to perform this task. =========================================================================== LISTING 1 - Outline for ERROR_0.BAT @echo off SET level=0 IF ERRORLEVEL 1 SET level=1 IF ERRORLEVEL 2 SET level=2 IF ERRORLEVEL 3 SET level=3 . \ . Include tests for 4 through 252 here. . / IF ERRORLEVEL 253 SET level=253 IF ERRORLEVEL 254 SET level=254 IF ERRORLEVEL 255 SET level=255 ERROR_0.BAT takes between 1.7 and 2.2 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. (All times reported in this article are from tests made using a 33 MHz 386 PC clone.) ============================================================================ ERROR_0.BAT is a very long batch file. It executes all 255 IF ERRORLEVEL test_value tests no matter what the exit code value. Each of the early tests sets the environment variable until the test_value becomes larger than the exit code. After that point the SET commands no longer execute and the value of LEVEL does not change. In terms of speed we can do better, but it is interesting to see how well the simple brute force approach works. This test isn't going to be used all that often. We could stop right here. There are several possible improvements to this simple scheme that come readily to mind, however. It is difficult to know, without testing them, which will give us the best increase in speed. All we can do is try them. FIRST VARIATION ON THAT THEME ERROR_1.BAT includes a slight modification to ERROR_0.BAT. In an attempt to save some time, ERROR_1.BAT only executes the SET command when the test_value and the exit code are equal. That is, it drastically limits the number of SET commands executed by the batch file. It uses nested IF ERRORLEVEL test_value tests as an experiment. The question is whether the extra IF tests consume all the time saved by avoiding the extra SET commands. The outline of the contents of ERROR_1.BAT appears in LISTING 2. =========================================================================== LISTING 2 - Outline for ERROR1.BAT @echo off SET level=0 IF ERRORLEVEL 1 IF NOT ERRORLEVEL 2 SET level=1 IF ERRORLEVEL 2 IF NOT ERRORLEVEL 3 SET level=2 IF ERRORLEVEL 3 IF NOT ERRORLEVEL 4 SET level=3 . \ . Include tests for 4 through 252 here. . / IF ERRORLEVEL 253 IF NOT ERRORLEVEL 254 SET level=253 IF ERRORLEVEL 254 IF NOT ERRORLEVEL 255 SET level=254 IF ERRORLEVEL 255 SET level=255 ERROR_1.BAT takes between 1.9 and 2.3 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. ============================================================================ ERROR_1.BAT is a little slower than ERROR_0.BAT. Apparently, it is a step in the wrong direction. The conclusion is that executing an IF test takes slightly longer than executing a SET command. The only way to find out if this or any other approach saves time is to try it. The idea of limiting the number of extraneous commands, perhaps by testing a range of exit codes, now seems like an intriguing possibility. There is the germ of an idea in that. ANOTHER VARIATION ERROR_2.BAT contains a different modification of ERROR_0.BAT. It integrates the FOR IN DO command with the code from ERROR_0.BAT in an attempt to speed up the testing and shorten the file. ERROR_2.BAT also includes a quick test for exit code 255 (for those using Y.COM). LISTING 3 contains the complete text of ERROR_2.BAT instead of just an outline. ============================================================================ LISTING 3 - Text of ERROR_2.BAT @echo off SET level=255 IF ERRORLEVEL 255 GOTO end FOR %%L IN (0 1 2 3 4 5 6 7) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (8 9 10 11 12 13 14 15) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (16 17 18 19 20 21 22 23) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (24 25 26 27 28 29 30 31) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (32 33 34 35 36 37 38 39) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (40 41 42 43 44 45 46 47) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (48 49 50 51 52 53 54 55) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (56 57 58 59 60 61 62 63) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (64 65 66 67 68 69 70 71) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (72 73 74 75 76 77 78 79) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (80 81 82 83 84 85 86 87) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (88 89 90 91 92 93 94 95) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (96 97 98 99 100 101 102 103) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (104 105 106 107 108 109 110) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (111 112 113 114 115 116 117) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (118 119 120 121 122 123 124) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (125 126 127 128 129 130 131) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (132 133 134 135 136 137 138) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (139 140 141 142 143 144 145) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (146 147 148 149 150 151 152) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (153 154 155 156 157 158 159) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (160 161 162 163 164 165 166) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (167 168 169 170 171 172 173) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (174 175 176 177 178 179 180) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (181 182 183 184 185 186 187) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (188 189 190 191 192 193 194) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (195 196 197 198 199 200 201) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (202 203 204 205 206 207 208) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (209 210 211 212 213 214 215) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (216 217 218 219 220 221 222) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (223 224 225 226 227 228 229) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (230 231 232 233 234 235 236) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (237 238 239 240 241 242 243) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (244 245 246 247 248 249 250) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (251 252 253 254) DO IF ERRORLEVEL %%L SET level=%%L :end ERROR_2.BAT takes between 1.5 and 2.0 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. If the exit code is 255, it only takes 0.07 seconds. ============================================================================ Using the FOR IN DO command shortens the file and speeds it up, slightly. It's only a small improvement, but it reinforces the earlier idea. What if it only executed a few of the FOR IN DO commands? After finding the range that includes the exit code, why test anything outside that range? That germ of an idea is still growing. FINDING THE RANGE One way to include this idea is to simply add a conditional GOTO statement after each FOR IN DO command in ERROR_2.BAT. If the exit code is below the range already tested, branch to the end of the subroutine. That turns out to be another false start, however. It speeds up the test when the exit code is small, but slows it down when the exit code is large. Try it yourself. Having tried that, it's clear that the way to pick up the speed is to pick a few fairly wide ranges. Following an initial test to locate the range, branch to the FOR IN DO command at the start of that range. Terminate the test at the end of the range. An extra FOR IN DO command and some supporting unconditional GOTOs and labels are all we need to do the trick. ERROR_3.BAT demonstrates the technique. It relies in line 4 on the fact that the FOR IN DO command creates pending GOTOs. ERROR_3.BAT is about at the limit of what the brute force approach can accomplish. LISTING 4 contains the complete text of ERROR_3.BAT. =========================================================================== LISTING 4 - Text of ERROR_3.BAT @echo off SET level=255 IF ERRORLEVEL 255 GOTO end FOR %%G IN (32 64 96 125 153 181 209 237) DO IF ERRORLEVEL %%G GOTO %%G FOR %%L IN (0 1 2 3 4 5 6 7) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (8 9 10 11 12 13 14 15) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (16 17 18 19 20 21 22 23) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (24 25 26 27 28 29 30 31) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :32 FOR %%L IN (32 33 34 35 36 37 38 39) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (40 41 42 43 44 45 46 47) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (48 49 50 51 52 53 54 55) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (56 57 58 59 60 61 62 63) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :64 FOR %%L IN (64 65 66 67 68 69 70 71) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (72 73 74 75 76 77 78 79) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (80 81 82 83 84 85 86 87) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (88 89 90 91 92 93 94 95) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :96 FOR %%L IN (96 97 98 99 100 101 102 103) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (104 105 106 107 108 109 110) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (111 112 113 114 115 116 117) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (118 119 120 121 122 123 124) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :125 FOR %%L IN (125 126 127 128 129 130 131) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (132 133 134 135 136 137 138) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (139 140 141 142 143 144 145) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (146 147 148 149 150 151 152) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :153 FOR %%L IN (153 154 155 156 157 158 159) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (160 161 162 163 164 165 166) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (167 168 169 170 171 172 173) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (174 175 176 177 178 179 180) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :181 FOR %%L IN (181 182 183 184 185 186 187) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (188 189 190 191 192 193 194) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (195 196 197 198 199 200 201) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (202 203 204 205 206 207 208) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :209 FOR %%L IN (209 210 211 212 213 214 215) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (216 217 218 219 220 221 222) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (223 224 225 226 227 228 229) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (230 231 232 233 234 235 236) DO IF ERRORLEVEL %%L SET level=%%L GOTO end :237 FOR %%L IN (237 238 239 240 241 242 243) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (244 245 246 247 248 249 250) DO IF ERRORLEVEL %%L SET level=%%L FOR %%L IN (251 252 253 254) DO IF ERRORLEVEL %%L SET level=%%L :end ERROR_3.BAT takes between 0.30 and 0.49 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. If the exit code is 255, it only takes 0.07 seconds. =========================================================================== The subroutine is getting longer, but it is also getting a lot quicker. You might be hoping that adding another FOR IN DO at the beginning of each range will speed it up even more. That requires extra lines and they slow down the tests for the higher exit codes, however. It speeds up by about 35% for small exit codes but then it slows down by about 10% for the larger exit codes. It is tempting to play with all the combinations to see how this strategy might be optimized. Go ahead; give a try. ERROR_3.BAT simultaneously extends and improves the earlier ideas to limit both the test range and the number of extraneous commands. AHA - A BREAKTHROUGH Achieving the next level of performance required a breakthrough in the way programmers look at using string variables in batch files. The basic idea has been around for a long time. In this series, it first surfaced in the discussion of IF [NOT] "string1"=="string2" tests. Those quotation marks are actually part of the string being tested on each side of the == sign. The three string components (", string1, and ") are simply run together on one line to make a single string. This is concatenation. ERROR_4.BAT should open a few eyes to its possibilities. It uses the idea, with an innovative twist, of finding the range that includes the exit code. It does not use nested IF tests for this purpose. Instead, it uses concatenated strings to test the positional ranges of standard decimal notation. ERROR_4.BAT is relies on a suggestion from Michael Bein on page 316 of the February 13, 1990 edition of PC Magazine. Neil Rubenking polished up that idea and published, in the same article, the first working version of ERROR_4.BAT; he called it ERRVAR2.BAT. It tests the exit code one digit at a time. That is, it tests the hundreds digit, then the tens digit and, finally, the units digit. It still uses an ascending cascade of tests within each range. The ascending cascade of IF ERRORLEVEL test_value tests is another common thread from the brute force approach right through to this ground breaking new approach. The working version of ERROR_4.BAT, basically ERRVAR2.BAT with minor modifications, appears in LISTING 5. ============================================================================ LISTING 5 - Text of ERROR_4.BAT (also ERRVAR2.BAT) SET E= FOR %%c IN (0 1 2) DO IF ERRORLEVEL %%c00 SET E=%%c IF "%E%"=="2" GOTO over200 FOR %%d IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%d0 SET E=%E%%%d GOTO digit2 :over200 FOR %%d IN (0 1 2 3 4 5) DO IF ERRORLEVEL %E%%%d0 SET E=%E%%%d :digit2 IF "%E%"=="25" GOTO over250 GOTO digit3 :over250 FOR %%u IN (0 1 2 3 4 5) DO IF ERRORLEVEL %E%%%u SET E=%E%%%u GOTO done :digit3 FOR %%u IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%u SET E=%E%%%u :done SET level=%E% SET E= ERROR_4.BAT takes between 0.19 and 0.24 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. ============================================================================ This is a batch file worthy of very careful examination. Notice how the program concatenates the environment variable, %E%, and the FOR IN DO variables, %%c, %%d and %%u, sometimes with a added zero or two. Those concatenated strings create different values for test_value and variable_value. Note, too, that E does not contain the same string value all the time. After the first round of tests it contains one (the hundreds) digit. After the second round it contains two (the hundreds and tens) digits. By the end of the subroutine it contains all three (the hundreds, tens and units) digits of the exit code. No matter what the value of the exit code is, the amount of testing is always about the same (within a factor of two). Across the entire range of exit codes these is only a 0.05 second variation (approx. a 12% variation about the median) in the time it takes to complete the test. ERROR_4.BAT, STEP-BY-STEP ANALYSIS The program first tests the exit code to determine which century range contains it, 0-99, 100-199 or 200-255. It tests the hundreds digit in ascending order; 0, 1 and 2. It interprets the test_value, %%c00, as 000, 100 and 200, consecutively, for this purpose. It sets the environment variable, E, equal to the hundreds digit of the exit code based on that first test (using the SET E=%%c command). If the exit code is 193, say, then E=1. If the exit code is 43, instead, then E=0. Next, it tests the exit code to see which decade range contains it. This requires treating two separate cases; exit codes from 200 up and exit codes below 200. The difference is in the range of the %%d parameter. Above 200, it checks only the decades from 00 to 50. Below 200, it checks the decades from 00 to 90. It tests the decades in ascending order. The construction of test_value in the IF ERRORLEVEL test_value test is as follows. %E%%%d0 (concatenate %E%, %%d, and 0) The environment variable, E, contains the hundreds digit found earlier. The FOR IN DO variable, %%d, is the tens digit being tested. The units digit is always zero. At the end of the test, it sets E equal to the 2 digit number %E%%%d (using the SET E=%E%%%d command). This is a reflexive form of the SET command. We have not seen the SET command used this way before. It refers to the old value of E to define the new value of E. If the exit code is 193, say, then E=19. If the exit code is 43, instead, then E=04. Finally, it tests the exit code to determine the units digit. The FOR IN DO variable, %%u, is concatenated with the two digits that E already contains. It compares the resulting test_values with the exit code. The construction of test_value in the IF ERRORLEVEL test_value test is as follows. %E%%%u (concatenate %E% and %%u) The environment variable, E, contains the hundreds and tens digits of the answer and %%u is the units digit being tested. The fact that it can only test for five numbers above 250 complicates the process. It tests the unit values in the required range in ascending order. Once it finds the units digit, it has all three digits. The SET command SETs E equal to %E%%%u (using the SET E=%E%%%u command). If the exit code is 193, say, then E=193. If the exit code is 43, instead, then E=043. ERROR_4.BAT is an example of superior batch file programming. Notice its roots, though. Those FOR IN DO commands hark all the way back to ERROR_2.BAT. And ERROR_2.BAT, itself, was only a simple refinement of ERROR_0.BAT. One really good idea made all the difference. We had all the pieces of the solution. It took a collaboration between a couple of very clever batch file programmers to put it all together, however. A FEW MINOR IMPROVEMENTS As good as it is, ERROR_4.BAT is not fully optimized. It was written to demonstrate an innovative new programming technique. OK, now the technique is understood. If the goal is all out speed, several improvements are in order. ERROR_4.BAT has too many unconditional GOTO statements. We can eliminate some of the unconditional GOTOs by rearranging the testing order. It does not need to clear E at the beginning and then test ERRORLEVEL 0. Using an actual number ought to be a little faster than using one of the variables in a couple of places. That saves COMMAND.COM the time and trouble of finding and substituting a value for the variable. It saves a step to SET LEVEL at the end of the units digit test directly. Collectively, these little improvements save 0.02 to 0.03 seconds (about 12-15%). LISTING 6 contains the text of ERROR_5.BAT. It stores a full three digit version of the exit code in the DOS environment. =========================================================================== LISTING 6 - Text of ERROR_5.BAT @echo off SET E=0 FOR %%c IN (1 2) DO IF ERRORLEVEL %%c00 SET E=%%c IF NOT "%E%"=="2" GOTO tens_dig FOR %%d IN (0 1 2 3 4 5) DO IF ERRORLEVEL 2%%d0 SET E=2%%d IF NOT "%E%"=="25" GOTO units_dg FOR %%u IN (0 1 2 3 4 5) DO IF ERRORLEVEL 25%%u SET LEVEL=25%%u GOTO done :tens_dig FOR %%d IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%d0 SET E=%E%%%d :units_dg FOR %%u IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%u SET LEVEL=%E%%%u :done SET E= ERROR_5.BAT takes between 0.16 to 0.22 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. ============================================================================ Notice that ERROR_4.BAT and ERROR_5.BAT are the only subroutines in this series that produce the full three digit version of the exit code. ERROR_0.BAT, ERROR_1.BAT, ERROR_2.BAT and ERROR_3.BAT all produce answers without leading zeros. They could all be modified to produce three digit answers, if necessary, however. For some of us the versions that produce answers without leading zeros are preferred. USING THE ERROR_5.BAT SUBROUTINE WITH DOS 3.3 TO 5.0 The calling batch file must use the CALL command to execute ERROR_5.BAT as a subroutine. Users of DOS versions before 3.3 can't call it as a subroutine but they can include the code directly in their batch files. The easiest way to do that is to import the subroutine into an ASCII text editor and then write the rest of the new batch file around it. Be sure to save the file under a different name. For DOS 3.3 and later versions, the calling syntax is as follows. CALL ERROR_5 When execution resumes in the calling batch file, the environment variable named LEVEL will contain a three digit string value equivalent to the exit code. The possible values of LEVEL range from 000 to 255. Batch files can use the stored value of the exit code a couple of different ways. It can be used in an unconditional branch, for example. GOTO %LEVEL% or GOTO 043 This command will send the execution pointer to labels like :193 or :043. Conditional branches can send the execution pointer to all kinds of labels, as follows. IF NOT "%LEVEL%"=="118" GOTO %LEVEL% or IF "%LEVEL%"=="193" GOTO 193 or IF "%LEVEL%"=="043" GOTO new_label It is not possible to use the variable name in a label. Thus the following label cannot serve as the target of a GOTO command. :%LEVEL% (not a legal label) Unfortunately, there is no way to test the stored value of the exit code by ranges. To test ranges requires having the exit code itself and using some form of the IF ERRORLEVEL test_value test. There are two programming routes around that difficulty. When the subroutine returns control to the calling batch file, COMMAND.COM still holds the original value of the exit code. The tests performed by ERROR_5.BAT did not change the exit code. It is still possible to test the exit code using more IF ERRORLEVEL test_value tests. The alternative is to buy one of Ronny Richardson's books; "MS-DOS Batch File Programming - 2nd Edition" or "MS-DOS Batch File Utilities." Each of these books comes with a diskette that contains a utility of his called SETERROR.COM. The second volume includes the latest version of SETERROR.COM. MODIFICATIONS SETERROR can set the exit code to any legal value; 0 to 255. Unfortunately, it does not understand three digit strings with leading zeros. We have to modify ERROR_5.BAT, slightly, before SETERROR.COM can use the stored exit code. The revised subroutine, ERROR_6.BAT, appears in LISTING 7. The subroutines, ERROR_0.BAT, ERROR_1.BAT, ERROR_2.BAT, and ERROR_3.BAT already work with SETERROR.COM and don't need further modification. =========================================================================== LISTING 7 - Text of ERROR_6.BAT @echo off SET E= IF NOT ERRORLEVEL 10 GOTO units_dg FOR %%c IN (1 2) DO IF ERRORLEVEL %%c00 SET E=%%c IF NOT "%E%"=="2" GOTO tens_dig FOR %%d IN (0 1 2 3 4 5) DO IF ERRORLEVEL 2%%d0 SET E=2%%d IF NOT "%E%"=="25" GOTO units_dg FOR %%u IN (0 1 2 3 4 5) DO IF ERRORLEVEL 25%%u SET LEVEL=25%%u GOTO done :tens_dig FOR %%d IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%d0 SET E=%E%%%d :units_dg FOR %%u IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%u SET LEVEL=%E%%%u :done SET E= ERROR_6.BAT takes between 0.10 to 0.23 seconds to find the exit code and SET the environment variable, LEVEL, equal to the exit code. =========================================================================== ERROR_6.BAT is slightly faster than ERROR_5.BAT, for small exit codes. Of course, its slightly slower than ERROR_5.BAT for large exit codes. The difference amounts to -0.06 seconds (-37%) for exits codes less than 10 and +0.02 seconds (+9%) for exit codes over 249. Unless a particular programming scheme requires the three digit result produced by ERROR_5.BAT, ERROR_6.BAT has a slight speed advantage over ERROR_5.BAT most of the time. The 0.02 second disadvantage for ERROR_6.BAT when testing the highest exit codes is due to the conditional GOTO added near the beginning. Adding an additional test for exit code 255 at the beginning, adds an additional 0.02 seconds to the run time for all exit codes except 255. ERROR_6.BAT does not generate answers with leading zeros unless the exit code is zero. With that change in effect, SETERROR can restore the exit code to the value it had when ERROR_6.BAT last stored it. The syntax is as follows. SETERROR %LEVEL% SETERROR.COM is the only utility I know about that sets the full range of exit codes from the command line. Ronny Richardson reports that a program, called CHOICES, by Ted W. Allen, can set any exit code in the range from zero to 250. He says that it is available on many bulletin boards. I have not seen it, myself. Other utilities like Y.COM and Peter Norton's BE ASK also set exit codes. They depend on user keyboard input, however. The full range of exit code values is difficult to generate with either utility. There is nothing else quite like SETERROR.COM and it is invaluable when writing and testing routines that work with exit codes. A FINAL MODIFICATION There is one last improvement that some users might appreciate. That is to include a batch file replaceable parameter. Use it to specify the name of the environment variable where the subroutine will store the exit code. With this feature, it is easy to keep several exit codes in the DOS environment at the same time. To avoid problems, ERROR_7.BAT uses the environment variable named LEVEL when the calling batch file does not provide another name. The text of ERROR_7.BAT appears in LISTING 8. =========================================================================== LISTING 8 - Text of ERROR_7.BAT @ECHO off SET E= SET LL=LEVEL IF NOT "%1"=="" SET LL=%1 IF NOT ERRORLEVEL 10 GOTO units_dg FOR %%c IN (1 2) DO IF ERRORLEVEL %%c00 SET E=%%c IF NOT "%E%"=="2" GOTO tens_dig FOR %%d IN (0 1 2 3 4 5) DO IF ERRORLEVEL 2%%d0 SET E=2%%d IF NOT "%E%"=="25" GOTO units_dg FOR %%u IN (0 1 2 3 4 5) DO IF ERRORLEVEL 25%%u SET %LL%=25%%u GOTO done :tens_dig FOR %%d IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%d0 SET E=%E%%%d :units_dg FOR %%u IN (0 1 2 3 4 5 6 7 8 9) DO IF ERRORLEVEL %E%%%u SET %LL%=%E%%%u :done SET LL= SET E= ERROR_7.BAT takes between 0.12 to 0.24 seconds to find the exit code and SET the environment variable, LEVEL or the name on the command line, equal to the exit code. =========================================================================== The syntax for using ERROR_7.BAT is CALL error_7 variable_name or CALL error_7 ERROR_7.BAT is 0.02 seconds (-10% to -20%) slower than ERROR_6.BAT for all exit codes. MATTERS OF TIMING The best of these subroutines improve on the speed of the simple brute force approach by about a factor of ten. ERROR_3.BAT was a major improvement in the brute force approach and ERROR_6.BAT still beats it by a factor of two. It took a major change in programming strategy to get that last factor of two, however. Whether it is possible to get another factor of two improvement remains uncertain, but seems unlikely. These ratios were obtained on a fast machine with a caching hard disk controller, and a 64K SRAM memory cache. A batch file with 100 consecutive CALLs timed each subroutine. The Norton Utilities TM (Time Mark) utility performed the actual timing. On this system the batch files do not show any increase in speed when run from a RAM disk; after the first cycle everything is available in cache memory. Removing QCACHE and half a dozen other TSRs actually speeds up all the batch files. Adding an extra test for exit code 255 at the beginning of ERROR_6.BAT imposes a time penalty of 10% to 20%. Disabling the SRAM memory cache slows everything down by almost 50%. I could not disable the cache on the caching hard disk controller. With this library of related routines to choose from, batch file programmers have the option to trade speed for features. They can choose pure speed or a customized mix of features with known speed penalties. I actually use a modified version of ERROR_7.BAT. It includes a quick test for exit code 255. The time penalty is small and I like the quick response to exit code 255. Finally, LISTING 9 contains the complete text of TIMER2.BAT, the batch file I used to conduct the timing tests. TIMER2 calls the subroutine 100 times and reports the elapsed time. Command line parameters specify which subroutine to test and which value of the exit code to test. It uses TM from version 4.5 of the Norton Utilities and requires Ronny Richardson's SETERROR.COM. ============================================================================= LISTING 9 - Text of TIMER2.BAT @ECHO off REM This subroutine is TIMER2.BAT REM The syntax is TIMER2 subroutine_# exit_code IF "%2"=="" GOTO error SET oldp=%prompt% PROMPT $g ECHO. ECHO. c:\norton\adv_45\tm start "Start of TEST %2 on ERROR_%1.BAT " /L seterror %2 >nul ECHO. ECHO. FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 FOR %%t IN (x x x x x x x x x x) DO CALL error_%1 ECHO. ECHO The exit code is %level%. ECHO. c:\norton\adv_45\tm stop "Test %2 of ERROR_%1. Elapsed time is " /N/L ECHO. SET level= GOTO end :error CLS ECHO. ECHO. ECHO. ECHO The syntax is TIMER2 subroutine_# exit_code, ECHO. ECHO where ECHO subroutine_# is 0 thru 7 and indicates ECHO which version of the subroutine to test, ECHO ERROR_0.BAT thru ERROR_7.BAT, ECHO and ECHO exit_code is the value of ECHO the exit to use during the ECHO timed test. :end prompt=%oldp% set oldp= ============================================================================= The batch commands that display in LISTING 9 as ECHO. will echo blank lines to the screen. In DOS 5.0, Microsoft provided this new syntax for echoing blank lines to the screen. Just type ECHO followed immediately by a period. Do not include an intervening space. The Microsoft MS-DOS 5.0 User's Guide and Reference describes this is technique on page 458. In batch files used with earlier versions of DOS, echoing blank lines to the screen was a little tricky. It required a line with ECHO, followed by a space, followed by ASCII character 255. Entering ASCII 255 in my text editor involves holding the {ALT} key down while entering 255 on the numeric key pad. It is easier to use the new DOS 5.0 technique. The old technique still works in DOS 5.0, so there is no need to rewrite the old batch files that use it. KEY WORDS Ascending Cascade ASCII Text Editor Batch File Subroutine BE ASK - Norton Utilities Branch (GOTO) Cache Memory Caching Hard Disk Controller CALL Cascade Century Range CHOICES.COM - Ted W. Allen COMMAND.COM, Resident Part COMMAND.COM, Transient Part Concatenate Concatenated Strings Concatenation Conditional Branch Conditional GOTO Decade Range DOS Environment Environment Variable ERROR_0.BAT ERROR_1.BAT ERROR_2.BAT ERROR_3.BAT ERROR_4.BAT ERROR_5.BAT ERROR_6.BAT ERROR_7.BAT ERRVAR2.BAT - Michael Bein and Neil Rubenking Exit Code FALSE FOR IN DO Hard Disk Controller Hundreds Digit IF [NOT] ERRORLEVEL test_value IF [NOT] "string1"=="string2" Keyboard Input LEVEL, environment variable Median Memory Cache MS-DOS Batch File Programming - 2nd Edition, by Ronny Richardson MS-DOS Batch File Utilities by Ronny Richardson Nested IF ERRORLEVEL test_value Tes PC Magazine - February 13, 1990 Pending GOTO QCACHE RAM Disk Replaceable Parameter Secondary Command Processor SET SET variable_name=variable_value SETERROR.COM - Ronny Richardson SRAM Memory Cache SRAM (Static RAM) Standard Decimal Notation String Value Subroutine Tens Digit test_value TM (Time Mark) - Norton Utilities TRUE TSR Unconditional Branch Unconditional GOTO Units Digit variable_name Variable Value Variation Y.COM