|
|
|
Because Education Doesn't Have to Be Expensive |
Allen & Company home page Windows 95/98/ME Batch files HomeIndexIntroductionLesson 20 Lesson 20 Filling in some gaps Lesson 20 has 63 exercises and takes about 140 minutes. Open the BatchWindow now, so it's the second button on the Taskbar, as explained in the Introduction ? (a) Use a maximized window for this main page; (b) have the BatchWindow open with its button on the Taskbar; (c) preview and work through (don't just read!) each exercise, in numerical order; (d) click on dark blue items to review them in the Course Reference window, or use the Course Reference List box that appears at the top of each Lesson. In Lesson 20 we'll round off our Windows 95/98/ME Batch Scripting Course by filling in a few gaps in our Batch knowledge to add some useful new skills: * We'll look further at FOR IN DO and use it to improve our LEVEL.BAT script * We'll write a DEMO.BAT that will teach us how to: o Use a REM pipe to make choice timed delays reliable o Create loops in Batch scripts to repeat command sequences o Use SHIFT to process command-line parameters o Use CALL to execute one Batch script from another o Use fc+find to return an ERRORLEVEL (and check a string is numeric ? ) Note Each Lesson relies on your files from the previous one. It's better to use your own files, but if you can't, download a copy of files as they were at the end of Lesson 19 and clickSingle Lesson instructions. Remember you can Work Offline if you prefer. Using FOR IN DO We learnt a little about the FOR command in Lesson 13, where we used it to clear a list of variables. Let's remind ourselves of the general idea: Suppose we have a list: one two three, and we want to perform the same command on each item. Or, we might have a wildcard (such as *.TXT) and want to perform the same command on each file that matches it. We can use FOR like this: FOR 1 IN 2 DO 3 * 1 Choose an index variable that will be used to range over the list. The format of the index variable is a %% followed by a single letter (for example %%v) but the single % format (%v) will usually work in immediate mode (not in scripts). * 2 Put the list, or the wildcard, in parentheses, like this: (one two three) or this: (*.TXT) and the index variable will then take the value of each item in the list, or the name of each matching file, in turn. * 3 Add a command to be repeated for each item (or file). In each place the item (or file) is to appear as part of the command, put the index variable %%v. The index variable letter can be any uppercase or lowercase single letter, but when it appears again in the DO part, it must have the same case as was used in part 1 . Most other characters can be used for the index, for example + (to give %%+), but in scripts we'll normally use only uppercase letters (for clarity and simplicity). Exercises 50 to 56 show how to build a command that lets fc and find together return an ERRORLEVEL. It's a very good exercise in ingenious "Batch thinking". Simple lists Let's practise a few examples in immediate mode. These examples will show some of the features and vagaries of FOR that aren't shown in the brief FOR /? help. We'll use the %% format to get used to the format we need for scripts (Exercise 1): ex 1 C:\CSW>for %%i in (each item in list) do echo. %%i C:\CSW>echo. each (When FOR is used in a script, each C:\CSW>echo. item with command ECHOing OFF item C:\CSW>echo. in these intermediate lines in C:\CSW>echo. list aren't displayed at all) list C:\CSW>_ Quoted tokens in the list The list in parentheses is processed as a series of separate tokens ("token" is just a technical term meaning the smallest unit that is being considered, for now, as one chunk). Usually, any delimiter will break a list into separate tokens. Remember, Batch delimiters are Space , Comma , ; , and = . However, you can group delimited items together by "quoting" them (Exercise 2): ex 2 C:\CSW>for %%i in ("each item" "in list") do echo. %%i C:\CSW>echo. each item (FOR treats this as one token) each item C:\CSW>echo. in list (FOR treats this as one token) in list C:\CSW>_ Removing quotes We can expand a variable within the parenthesised list. So, if the variable contains a quoted string (it might be a long filename in quotes) and we need to remove the quotes, we can use FOR to reset the variable (Exercise 3): ex 3 C:\CSW>set var="to be or not to be" C:\CSW>echo. %var% "to be or not to be" C:\CSW>for %%v in (%var%) do set var=%%v C:\CSW>set var=to be or not to be C:\CSW>echo. %var% to be or not to be C:\CSW>set var= C:\CSW>_ Special treatment of / The / is treated specially in a FOR command. It separates text into tokens, just as a normal delimiter would, but it remains part of each token. It also capitalises each text token when they appear in the DO part of the command (Exercise 4): ex 4 C:\CSW>for %%i in (/one/two/three) do echo.%%i C:\CSW>echo./ONE /ONE C:\CSW>echo./TWO /TWO C:\CSW>echo./THREE /THREE C:\CSW>_ Override with "quotes" To avoid the special / treatment, enclose the list in "quotes" (Exercise 5): ex 5 C:\CSW>for %%i in ("/one/two/three") do echo.%%i C:\CSW>echo./one/two/three /one/two/three C:\CSW>_ Using a wildcard with the FOR command We can also use a wildcard as the FOR list, and it will loop through all the files that match the wildcard (Exercise 6): ex 6 C:\CSW>for %%f in (*.txt) do echo. Found the file: %%f C:\CSW>echo. Found the file: CARROLL.TXT Found the file: CARROLL.TXT C:\CSW>echo. Found the file: GREENS~1.TXT Found the file: GREENS~1.TXT C:\CSW>echo. Found the file: RHYMEF~1.TXT Found the file: RHYMEF~1.TXT C:\CSW>_ Note The FOR command doesn't locate files which have their hidden or system attribute set. This can be useful in hiding files from the FOR IN DO scan. Hiding files from FOR Use the attrib command to set the hidden attribute of CARROLL.TXT and it will be hidden from FOR (Exercise 7): ex 7 C:\CSW>attrib +h carroll.txt C:\CSW>for %%f in (*.txt) do echo. Found the file: %%f C:\CSW>echo. Found the file: GREENS~1.TXT Found the file: GREENS~1.TXT C:\CSW>echo. Found the file: RHYMEF~1.TXT Found the file: RHYMEF~1.TXT C:\CSW>attrib -h carroll.txt C:\CSW>_ Long file names from FOR with LFNFOR Notice that when we used the *.TXT wildcard with FOR, it returned the short name aliases of our text files (for example, GREENS~1.TXT for Greenspan.txt). This is the default setting. To return long file names we can use LFNFOR (Exercise 8): ex 8 C:\CSW>lfnfor on (ON stays in force until turned OFF) C:\CSW>for %%f in (*.txt) do echo. Found the file %%f C:\CSW>echo. Found the file CARROLL.TXT Found the file CARROLL.TXT C:\CSW>echo. Found the file Greenspan.txt Found the file Greenspan.txt C:\CSW>echo. Found the file RhymeForPi.txt Found the file RhymeForPi.txt C:\CSW>lfnfor off C:\CSW>_ A better LEVEL.BAT: Using FOR to replace multiple command lines In BKUP.BAT we used FOR to clear a list of variables, instead of writing a separate command to clear each one. Earlier, in Lesson 4, we wrote LEVEL.BAT, a simple script to display the current ERRORLEVEL. It looks like this: @ECHO OFF SET ERR=0 IF ERRORLEVEL 1 SET ERR=1 IF ERRORLEVEL 2 SET ERR=2 IF ERRORLEVEL 3 SET ERR=3 IF ERRORLEVEL 4 SET ERR=4 IF ERRORLEVEL 5 SET ERR=5 IF ERRORLEVEL 6 SET ERR=6 IF ERRORLEVEL 7 SET ERR=7 (or more) IF ERRORLEVEL 255 SET ERR=255 ECHO. The current errorlevel is %ERR% SET ERR= Repeated lines can often be replaced with FOR The six lines that test (in ascending order) the possible ERRORLEVELs from 1 to 6, are ideal lines to be replaced with a single FOR IN DO statement such as this: FOR %%E IN (1 2 3 4 5 6) DO IF ERRORLEVEL %%E SET ERR=%%E Immediate mode test Let's try that command in immediate mode, and watch how it works. Remember that: IF ERRORLEVEL n is true if the current ERRORLEVEL is greater-than-or-equal-to n. First, we'll use choice to set, say ERRORLEVEL 4, run the FOR command, and then confirm that ERR is set to 4 at the end (Exercise 9): ex 9 C:\CSW>choice /c:1234 [1,2,3,4]?4 (sets current ERRORLEVEL to 4) C:\CSW>for %%e in (1 2 3 4 5 6) do if errorlevel %%e set err=%%e C:\CSW>if errorlevel 1 set err=1 (true, so ERR is set to 1) C:\CSW>if errorlevel 2 set err=2 (true, so ERR is reset to 2) C:\CSW>if errorlevel 3 set err=3 (true, so ERR is reset to 3) C:\CSW>if errorlevel 4 set err=4 (true, so ERR is reset to 4) C:\CSW>if errorlevel 5 set err=5 (false, set err=5 not executed) C:\CSW>if errorlevel 6 set err=6 (false, set err=6 not executed) C:\CSW>echo. err is now %err% err is now 4 C:\CSW>_ Open LEVEL.BAT Let's add this FOR command line to LEVEL.BAT, using this one line to replace the six incremental test lines. Open LEVEL.BAT in Notepad (Exercise 10): ex 10 C:\CSW>start /min notepad level.bat C:\CSW>_ Add the new line Now let's add the new FOR line, and delete the six lines it replaces (Exercise 11): ex 11 SET ERR=0 FOR %%E IN (1 2 3 4 5 6) DO IF ERRORLEVEL %%E SET ERR=%%E IF ERRORLEVEL 1 SET ERR=1 IF ERRORLEVEL 2 SET ERR=2 IF ERRORLEVEL 3 SET ERR=3 IF ERRORLEVEL 4 SET ERR=4 IF ERRORLEVEL 5 SET ERR=5 IF ERRORLEVEL 6 SET ERR=6 IF ERRORLEVEL 7 SET ERR=7 (or more) Test the script With the changes saved, we can test the script. Set a test ERRORLEVEL with choice again. Instead of replying to choice ourselves, let's use an idea we met in Lesson 17 – we used a pipe to reply to format. We can also pipe a reply to choice. It's the way we would set a particular ERRORLEVEL in a script (Exercise 12): ex 12 C:\CSW>echo.3|choice /c:123 [1,2,3]?3 C:\CSW>level The current errorlevel is 3 C:\CSW>_ Note Watch for syntax error messages. Common mistakes with FOR are: * failing to include the IN in the FOR command line * failing to include the DO in the FOR command line * not using the same index variable letter throughout the command line Another improvement We used SET ERR=0 at the start of the script, since there is normally no point in testing for an ERRORLEVEL greater than or equal to 0. This test is always true. However, we could have written it out in full as the first test: IF ERRORLEVEL 0 SET ERR=0 Add 0 as the first test in the FOR loop We didn't because SET ERR=0 was simpler (and has the same effect since the test for 0 is always true). But if we imagine it written that way, then the line has the same pattern as the other six lines. So it can be included in the repeated FOR tests, simply by adding 0 as the first item in the list. Try it in immediate mode. Clear the current ERRORLEVEL to 0 (with command /c) then try the FOR command including a 0 as the first item in the list (Exercise 13): ex 13 C:\CSW>command /c (sets current ERRORLEVEL to zero) C:\CSW>for %%e in (0 1 2 3 4 5 6) do if errorlevel %%e set err=%%e C:\CSW>if errorlevel 0 set err=0 (always true, so ERR is set to 0) C:\CSW>if errorlevel 1 set err=1 (false, set err=1 not executed) C:\CSW>if errorlevel 2 set err=2 (false, set err=2 not executed) C:\CSW>if errorlevel 3 set err=3 (false, set err=3 not executed) C:\CSW>if errorlevel 4 set err=4 (false, set err=4 not executed) C:\CSW>if errorlevel 5 set err=5 (false, set err=5 not executed) C:\CSW>if errorlevel 6 set err=6 (false, set err=5 not executed) C:\CSW>echo. err is %err% err is 0 C:\CSW>_ Add 0 to the list Add the 0 as the first value in the FOR list. And we can leave out the SET ERR=0 line altogether (Exercise 14): ex 14 SET ERR=0 FOR %%E IN (0 1 2 3 4 5 6) DO IF ERRORLEVEL %%E SET ERR=%%E IF ERRORLEVEL 7 SET ERR=7 (or more) Test LEVEL.BAT When you've clicked File, Save to save the new version, check that it works properly. Clear the current ERRORLEVEL to 0 again, just to be sure (Exercise 15): ex 15 C:\CSW>command /c C:\CSW>level The current errorlevel is 0 C:\CSW>rc Opening Return-Code shell... Return code (ERRORLEVEL): 0 WARNING: Reloaded COMMAND.COM transient RCode C:\CSW>command /c Return code (ERRORLEVEL): 0 RCode C:\CSW>level The current errorlevel is 0 RCode C:\CSW>exit C:\CSW>_ Going further than 6 in one FOR IN DO loop Our FOR loop now sets ERR to values from 0 to 6, all in a single command line. The obvious question is: Why not add more values to the list, so making the script return even more individual results? Why not, indeed (Exercise 16): ex 16 @ECHO OFF FOR %%E IN (0 1 2 3 4 5 6 7 8 9 10) DO IF ERRORLEVEL %%E SET ERR=%%E IF ERRORLEVEL 7 SET ERR=7 (or more) IF ERRORLEVEL 11 SET ERR=11 (or more) IF ERRORLEVEL 255 SET ERR=255 ECHO. The current errorlevel is %ERR% SET ERR= Check the script again Use choice, as before, but this time set some higher ERRORLEVELs (Exercise 17): ex 17 C:\CSW>choice /c:123456789 [1,2,3,4,5,6,7,8,9]?9 C:\CSW>level The current errorlevel is 9 C:\CSW>choice /c:1234567890 [1,2,3,4,5,6,7,8,9,0]?0 C:\CSW>level The current errorlevel is 10 C:\CSW>_ How far can we go? Can we continue in this way, and simply put all the values up to 255 in one long list? No, we can't. How far can we go in this one line? The maximum number of items in the parenthesised list for this particular FOR IN DO command line is 53. The 64 token limit To understand why, we need to think in tokens. A token, as we've already mentioned, is the smallest unit of syntax that counts as one chunk. And a Batch command line in Windows 95/98/ME has a maximum limit of 64 ? tokens. Any more, and the command line will generate a Bad command or file name error. In Windows NT/2000/XP, the token limit is much higher, and it's unlikely to be a factor in a practical Batch file. How to work out the 53 answer for this line If we count the tokens in the maximum version of our FOR command-line, we can see why the limit of the parenthesised list is 53: 1 2 3 4 5 6-55 56 57 58 59 60 61 62 63 64 FOR %%E IN (0 1 .... 52 53) DO IF ERRORLEVEL %%E SET ERR=%%E The ( , ) , and = don't themselves count towards the token limit, they simply help to tell Windows how to interpret the tokens in the line. We won't bother with reporting more values We could use that line. And then use another long FOR line next, with a list that began at 54, and so on. With five such long command lines, we could handle all the possible values up to 255! But we won't, since as we've said, our script already covers the most common and useful ERRORLEVELs. Anyway, the Return-code shell is a more useful tool for working with ERRORLEVELs. And furthermore, with some more experience in writing scripts, it's possible to scan for the other values in less laborious ways if you ever need to. A demo script: Filling in a few more gaps in our knowledge And to gain a little more experience, we'll round off this Course by writing a demo script to learn some new techniques, and do some serious "Batch-mode thinking"! We've finished editing LEVEL.BAT, so close its Notepad window before we start the new file: right-click the Notepad button on the Taskbar, and click Close. Now, start a new file for our DEMO.BAT script. It will be quite short, but it will give us some useful practise with new ideas (Exercise 18): ex 18 C:\CSW>start /min notepad DEMO.BAT C:\CSW>_ The plan for DEMO.BAT In DEMO.BAT we'll learn two useful new commands: * SHIFT to shift the point of view of the %0 to %9 symbols used to access the command-line. We'll make DEMO.BAT "loop through" its parameters * CALL to call (=run) one batch script from another, and return to the original script after the called script has finished And we'll practise another bit of Batch "lateral thinking". The Batch language doesn't have many commands, so you sometimes need to exploit its commands in unusual ways. We did this in Lesson 15 to develop a "drive-ready" check, and in Lesson 16 to discover whether or not files need backing up. This time we'll figure out how DEMO.BAT can easily check whether or not its command-line parameters are numbers. Outline of labels Let's start with @ECHO OFF as usual, and put in a few labels (Exercise 19): ex 19 @ECHO OFF :LOOP through command-line parameters :DELAY the script for a second :CLEANUP ECHO. No further parameters to process Check for syntax errors Once it's saved, run the script to make sure there are no syntax errors (Exercise 20): ex 20 C:\CSW>demo No further parameters to process C:\CSW>_ Adding a few seconds delay to a script In Lesson 10 we learnt a simple way of introducing short delays into a running script with choice. The /t switch allows you to specify one reply as a default, to take effect after a delay from 1 to 99 seconds. The syntax is: choice /c: ReplyList /t DefaultReply , TimeOut All we want out of this choice command is a delay, and the reply list is irrelevant. To make the purpose clear, we used /c:delay as the reply list and we used D as the timeout default. In Lesson 10 we mentioned there was a problem with this simple form of the delay technique. Let's see the problem in action (Exercise 21): ex 21 C:\CSW>choice /c:delay /td,5 [D,E,L,A,Y]?D C:\CSW>choice /c:delay /td,5 [D,E,L,A,Y]?L C:\CSW>choice /c:delay /td,5 [D,E,L,A,Y]?^C C:\CSW>choice /c:delay /td,15>nul C:\CSW>_ Capturing STDIN for choice with a pipe Redirection to NUL suppresses the choice text output to STDOUT. But the STDIN (input) of choice is still set to the usual default STDIN(=the keyboard). In Lesson 10 we didn't know about pipes so we couldn't do anything about it. To prevent keyboard input interfering with choice, we can add a pipe in front of it, so choice's STDIN is captured by the pipe. Then it will be linked to the STDOUT of whatever command we pipe to it. We'll choose a command that has no output, and then pipe its "output" into choice. Accidental keypresses won't affect the delay anymore, because they won't be seen as input by choice. The command REM (for remarks in scripts) has no output, but as we saw in Lesson 16 when we learnt how to type the | character, REM can be used in pipe operations. So it will serve for our purpose (Exercise 22): ex 22 C:\CSW>rem | choice /c:delay /td,10>nul C:\CSW>rem | choice /c:delay /td,10>nul C:\CSW>xxx^C C:\CSW> Add a delay to our demo A one-second delay will be enough, so add it to our demo script with the new version of the delay command (Exercise 23): ex 23 @ECHO OFF :LOOP through command-line parameters :DELAY the script for a second REM | choice /c:delay /td,1>NUL :CLEANUP ECHO. No further parameters to process Check the script Changes saved? Run the demo to check the syntax is OK (Exercise 24): ex 24 C:\CSW>demo No further parameters to process C:\CSW> Note Now we know how to fix the bug in our old way of inserting a delay, we can amend the choice delay commands in our BKUP.BAT and BKSETUP.BAT scripts. To work reliably, they also need the REM | prefix inserted. Don't bother for now, you can fix them when this Lesson is over. The command-line parameter symbols We met the special symbols to access the command line in Lesson 12. In BKUP.BAT we use %1 and %2 to access the first two tokens following the script name. The full set of command-line parameter symbols available for use in scripts works like this: Symbol This symbol is expanded to %0 The exact name typed to run the script %1 The 1st item of text following the name %2 The 2nd item of text following the name And so on, through to %9 The 9th item of text following the name There are no further symbols, and so the combination: %10 is expanded as simply %1 + a literal zero These fixed symbols are fine for accessing fixed numbers of parameters. However, in DEMO.BAT we want to loop through all the parameters on the command line. And we want to do this without knowing how many there are to start with (and there might be ten or more). As we shall soon see, the command we need for this is SHIFT. Displaying a parameter First, let's put in an ECHO command to display the %1 parameter (Exercise 25): ex 25 @ECHO OFF :LOOP through command-line parameters ECHO. %%1 has the value: %1 :DELAY the script for a second REM | choice /c:delay /td,1>NUL :CLEANUP ECHO. No further parameters to process Test the new message With the new line saved, we can test how the message is expanded by running the script with some command-line parameters (Exercise 26): ex 26 C:\CSW>demo %1 has the value: No further parameters to process C:\CSW>demo six %1 has the value: six No further parameters to process C:\CSW>demo 6 %1 has the value: 6 No further parameters to process C:\CSW>demo 6 7 8 %1 has the value: 6 No further parameters to process C:\CSW> Parameters beyond the first one At the moment, our demo script doesn't "see" any parameter beyond the first. First, we'll add a temporary diagnostic line that does "see" the other parameters, so we'll know what is happening to them (Exercise 27): ex 27 :LOOP through command-line parameters ECHO. Parameters=%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 ECHO. %%1 has the value: %1 Test the diagnostic With the change saved, test the diagnostic message (Exercise 28): ex 28 C:\CSW>demo 1 2 3 4 5 6 7 8 9 10 Parameters=demo 1 2 3 4 5 6 7 8 9 %1 has the value: 1 No further parameters to process C:\CSW>_ Looping through the command line Now we'll make our script loop through the command-line parameters with SHIFT so we can see what happens (Exercise 29): ex 29 @ECHO OFF :LOOP through command-line parameters ECHO. Parameters=%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 ECHO. %%1 has the value: %1 :DELAY the script for a second REM | choice /c:delay /td,1>NUL :Shift parameter "window" down one place SHIFT PAUSE GOTO LOOP :CLEANUP ECHO. No further parameters to process Run the script again With those changes saved, test the script with the same ten command-line parameters that we used before. This time, we see them "slide" down the "window" of symbols in our diagnostic ECHO command. And each one of the parameters, in its turn, eventually occupies the %1 slot for the second ECHO command (Exercise 30): ex 30 C:\CSW>demo 1 2 3 4 5 6 7 8 9 10 Parameters=demo 1 2 3 4 5 6 7 8 9 %1 has the value: 1 Press any key to continue . . . Parameters=1 2 3 4 5 6 7 8 9 10 %1 has the value: 2 Press any key to continue . . .each time we go round the loop all parameters are SHIFTed, until Parameters=9 10 %1 has the value: 10 Press any key to continue . . . Parameters=10 %1 has the value: Press any key to continue . . . Parameters= %1 has the value: Press any key to continue . . . ^C Terminate batch job (Y/N)?y C:\CSW>_ Making the script end the loop As we watch the parameters SHIFT down the available symbols, we saw that the %1 position eventually becomes empty. If we test whether or not %1 is empty, and jump to the :CLEANUP section if it is empty, the loop will terminate properly instead of going on and on (Exercise 31): ex 31 @ECHO OFF :LOOP through command-line parameters IF (%1)==() GOTO CLEANUP ECHO. Parameters=%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 ECHO. %%1 has the value: %1 Test the loop again With that change saved, test the loop with the same ten parameters again. This time the loop ends of its own accord as soon as there are no more parameters to occupy the %1 position (Exercise 32): ex 32 C:\CSW>demo 1 2 3 4 5 6 7 8 9 10 Parameters=demo 1 2 3 4 5 6 7 8 9 %1 has the value: 1 Press any key to continue . . . Parameters=1 2 3 4 5 6 7 8 9 10 %1 has the value: 2 Press any key to continue . . .as before, until Parameters=9 10 %1 has the value: 10 Press any key to continue . . . No further parameters to process C:\CSW>demo No further parameters to process C:\CSW>_ We no longer need the PAUSE to stop the loop We used the PAUSE command so that the original loop (which was an infinite loop, since it had no way to stop) was easy to stop. Now our IF test produces an orderly exit from the loop, we no longer need the PAUSE (Exercise 33): ex 33 :Shift parameter "window" down one place SHIFT PAUSE GOTO LOOP :CLEANUP ECHO. No further parameters to process Test without the PAUSE With that change saved, test the loop with the same ten parameters again. This time the loop runs automatically. We see the parameters sliding down our diagnostic ECHO, and the script ends of its own accord, as before (Exercise 34): ex 34 C:\CSW>demo 1 2 3 4 5 6 7 8 9 10 Parameters=demo 1 2 3 4 5 6 7 8 9 %1 has the value: 1 Parameters=1 2 3 4 5 6 7 8 9 10 %1 has the value: 2 Parameters=2 3 4 5 6 7 8 9 10 %1 has the value: 3 Parameters=3 4 5 6 7 8 9 10 %1 has the value: 4 Parameters=4 5 6 7 8 9 10 %1 has the value: 5 Parameters=5 6 7 8 9 10 %1 has the value: 6 Parameters=6 7 8 9 10 %1 has the value: 7 Parameters=7 8 9 10 %1 has the value: 8 Parameters=8 9 10 %1 has the value: 9 Parameters=9 10 %1 has the value: 10 No further parameters to process C:\CSW>_ Take a short break! This is an extra long Lesson, so take a mid-session break and give yourself time to digest what we've learnt so far. Let the screen-saver kick in while you have a coffee. You'll find you work better after a brief rest to think things over. Remove the diagnostic ECHO command line For the next stage of the work on the script, we no longer need the diagnostic ECHO command. We used it merely to watch how SHIFT works (Exercise 35): ex 35 :LOOP through command-line parameters IF (%1)==() GOTO CLEANUP ECHO. Parameters=%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 ECHO. %%1 has the value: %1 Run the loop again With that line gone and the script saved, run DEMO.BAT again with our usual ten parameters (Exercise 36): ex 36 C:\CSW>demo 1 2 3 4 5 6 7 8 9 10 %1 has the value: 1 %1 has the value: 2 %1 has the value: 3 %1 has the value: 4 %1 has the value: 5 %1 has the value: 6 %1 has the value: 7 %1 has the value: 8 %1 has the value: 9 %1 has the value: 10 No further parameters to process C:\CSW>demo one two three four "five six seven" %1 has the value: one %1 has the value: two %1 has the value: three %1 has the value: four %1 has the value: "five six seven" No further parameters to process C:\CSW>_ Set ERRORLEVEL from parameter Now we have a loop in place to work through all the parameters, let's do something with them. We'll set the ERRORLEVEL to the digit in %1. We'll work in a Return-code shell to see what's happening (Exercise 37): ex 37 C:\CSW>rc Opening Return-Code shell... Return code (ERRORLEVEL): 0 WARNING: Reloaded COMMAND.COM transient RCode C:\CSW>demo 4 5 6 %1 has the value: 4 Return code (ERRORLEVEL): 1 (from choice delay) %1 has the value: 5 Return code (ERRORLEVEL): 1 (from choice delay) %1 has the value: 6 Return code (ERRORLEVEL): 1 (from choice delay) No further parameters to process RCode C:\CSW>_ We pipe %1 to choice Earlier in this Lesson, we used a pipe to reply to choice, to set an ERRORLEVEL. In the same way, in our script, we can ECHO %1 though a pipe to the choice command line we've often used to set small ERRORLEVELs. The digit in %1 will form the reply to choice, and set an appropriate ERRORLEVEL for us (Exercise 38): ex 38 :LOOP through command-line parameters IF (%1)==() GOTO CLEANUP ECHO. %%1 has the value: %1 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) ECHO.%1|choice /c:1234567890>NUL :DELAY the script for a second REM | choice /c:delay /td,1>NUL Note We usually leave a Space either side of a pipe operator. This helps readability. However, when we ECHO text into a pipe, as here, any Space at the end of the ECHO command is also ECHOed down the pipe to the STDIN of the next command. It doesn't matter in this case, since choice would ignore the Space character. But it's usually best to avoid adding Space s to such ECHO commands, unless they're really needed. Test the script again With that change saved, run DEMO.BAT again (Exercise 39): ex 39 RCode C:\CSW>demo 2 3 4 0 %1 has the value: 2 Return code (ERRORLEVEL): 2 (ECHO+choice pipe) Return code (ERRORLEVEL): 1 (choice delay) %1 has the value: 3 Return code (ERRORLEVEL): 3 (ECHO+choice pipe) Return code (ERRORLEVEL): 1 (choice delay) %1 has the value: 4 Return code (ERRORLEVEL): 4 (ECHO+choice pipe) Return code (ERRORLEVEL): 1 (choice delay) %1 has the value: 0 Return code (ERRORLEVEL): 10 (ECHO+choice pipe) Return code (ERRORLEVEL): 1 (choice delay) No further parameters to process RCode C:\CSW>exit C:\CSW>_ Note Make sure you don't accidentally enter a non-digit as one of the parameters for DEMO.BAT. If you do, the choice command we've just added will stall because only digits are on its reply list. If this does happen, you can break out of the stall by pressing Ctrl C and replying Y when asked if you want to terminate the batch job. Running one Batch script from another The display in the Return-code shell is rather confusing for this demonstration (so make sure you've closed the Return-code shell now). It would look better to have a simple display of the ERRORLEVEL after each ECHO+choice pipe (and no more). In other words, exactly the simple display our LEVEL.BAT script provides. We could just cut-and-paste the code from LEVEL.BAT directly into the DEMO.BAT script. It would work perfectly well, but instead we'll do it another way. We'll make DEMO.BAT run LEVEL.BAT for us (Exercise 40): ex 40 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) ECHO.%1|choice /c:1234567890>NUL LEVEL.BAT :DELAY the script for a second REM | choice /c:delay /td,1|NUL We go to LEVEL.BAT but we stop there With the instruction to run LEVEL.BAT saved, try the DEMO.BAT script again. As before, use four digits as its command-line parameters for DEMO.BAT. Our DEMO.BAT script does run our LEVEL.BAT script, but the flow of logic doesn't return to DEMO.BAT. As a result, our loop, which previously SHIFTed its way through all the parameters, now stops after the first digit is processed (Exercise 41): ex 41 C:\CSW>demo 2 3 4 0 %1 has the value: 2 The current errorlevel is 2 C:\CSW>_ The CALL command is what we need to use When you run a second script (LEVEL.BAT in this case) from the first (our DEMO.BAT script), control is transferred to the second script, and control stays with it. So when the second script (LEVEL.BAT) finishes, that's it – everything finishes. Sometimes, this is exactly what you will want (so it's good to know how to achieve it). However, what we want is for DEMO.BAT to run LEVEL.BAT, and then have control return to DEMO.BAT so that the loop through the parameters can continue. To do this, we can CALL the second script (LEVEL.BAT) from the first (DEMO.BAT). CALL transfers control (from DEMO.BAT) to the CALLed script (LEVEL.BAT), which then runs. When it (LEVEL.BAT) finishes, control returns to the very next line of the CALLing script (DEMO.BAT). Let's see this in action (Exercise 42): ex 42 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) ECHO.%1|choice /c:1234567890>NUL CALL LEVEL.BAT :DELAY the script for a second REM | choice /c:delay /td,1|NUL A CALLed script returns control to the CALLer With that saved, run DEMO.BAT again. This time, control returns to DEMO.BAT when LEVEL.BAT finishes. So the loop continues, and LEVEL.BAT gets CALLed again on each turn round the loop (Exercise 43): ex 43 C:\CSW>demo 2 3 4 0 %1 has the value: 2 DEMO starts and CALLs The current errorlevel is 2 LEVEL which returns to %1 has the value: 3 DEMO and next loop CALLs The current errorlevel is 3 LEVEL which returns to %1 has the value: 4 DEMO and next loop CALLs The current errorlevel is 4 LEVEL which returns to %1 has the value: 0 DEMO and next loop CALLs The current errorlevel is 10 LEVEL which returns to No further parameters to process DEMO which finishes C:\CSW>_ Fixing the zero bug DEMO.BAT is performing well, now. So let's fix the zero bug: when we enter a 0 as one of our parameters, ERRORLEVEL 10 is returned (because we used /c:1234567890 as the reply list). We can't make choice return zero by moving the "0" reply around in the list. Instead, we'll just clear the ERRORLEVEL to zero if it's 10 (or more, but it shouldn't be more unless we make a mistake with the choice syntax!). Add an IF ERRORLEVEL 10 test and clear the ERRORLEVEL if it's true (Exercise 44): ex 44 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) ECHO.%1|choice /c:1234567890>NUL :: Clear ERRORLEVEL to zero if a %1=0 did set 10 IF ERRORLEVEL 10 %COMSPEC% /c CALL LEVEL.BAT :DELAY the script for a second REM | choice /c:delay /td,1>NUL Test we've fixed it With that fix saved, test DEMO.BAT again (Exercise 45): ex 45 C:\CSW>demo 2 3 4 0 %1 has the value: 2 The current errorlevel is 2 %1 has the value: 3 The current errorlevel is 3 %1 has the value: 4 The current errorlevel is 4 %1 has the value: 0 The current errorlevel is 0 (now the bug is fixed) No further parameters to process C:\CSW>echo.%comspec% (expand COMSPEC to check it) C:\WINDOWS\COMMAND.COM C:\CSW>_ CALL looks for .COM .EXE and .BAT files When you CALL a filename, such as LEVEL.BAT, Windows checks the current folder first. If you don't specify a file extension (the .BAT part), Windows checks for a .COM file, then an .EXE file and lastly a .BAT file (with that base name). The first one found is executed by the CALL command! That's why we used the .BAT extension. CALL will search the entire system PATH But, if there is no such file found in the current folder, Windows goes on to search the entire system PATH for such a file, and runs the first one found – which might not be the one you meant! Let's see what can happen. Try it in immediate mode with the standard Windows program WINVER.EXE, which simply pops up a dialogue box that shows your Windows version (Exercise 46): ex 46 C:\CSW>path PATH=C:\WINDOWS;C:\WINDOWS\COMMAND (there may be more) C:\CSW>dir c:\winver.exe /b /s (search for winver.exe) C:\WINDOWS\winver.exe C:\CSW>call winver (CALL it and a version box pops up) C:\CSW>call c:\csw\winver Bad command or file name C:\CSW>_ A full path with CALL If we don't use a full path (including the foldername) when we CALL LEVEL.BAT then Windows will search for a matching file in all the folders in the system PATH should the file LEVEL.BAT be missing from our current folder. We want the version of LEVEL.BAT in our C:\CSW folder to run (and no other), so we'll specify the full path to it in DEMO.BAT (Exercise 47): ex 47 :: Clear ERRORLEVEL to zero if a %1=0 did set 10 IF ERRORLEVEL 10 %COMSPEC% /c CALL C:\CSW\LEVEL.BAT :DELAY the script for a second REM | choice /c:delay /td,1>NUL Test the script Changes saved? There shouldn't be any difference, but let's be sure (Exercise 48): ex 48 C:\CSW>demo 2 3 4 0 %1 has the value: 2 The current errorlevel is 2 %1 has the value: 3 The current errorlevel is 3 %1 has the value: 4 The current errorlevel is 4 %1 has the value: 0 The current errorlevel is 0 No further parameters to process C:\CSW>_ Handling non-digits We mentioned earlier that a non-digit as a parameter will stall our DEMO.BAT script. Try it now and see what happens (Exercise 49): ex 49 C:\CSW>demo 1 2 3 x %1 has the value: 1 The current errorlevel is 1 %1 has the value: 2 The current errorlevel is 2 %1 has the value: 3 The current errorlevel is 3 %1 has the value: x (press Ctrl C ) Terminate batch job (Y/N)?y (and reply Y ) C:\CSW>_ Batch thinking: Batch programming rewards ingenuity As we've learnt, the Batch programming language is a simple one. And it doesn't have many commands. But then, the 12 different notes in the Diatonic musical scale are enough for all normal music. In each case, what matters is how you put them together! Learning to write Batch files will help you to go on to learn other, more complex programming languages, for example C or Windows Script Host (both combine very nicely with Batch techniques). But you can do a lot with Batch techniques alone. More than anything else, you'll find the Batch programming language teaches you ingenuity and subtlety, because, with Batch files, a little of these goes a long way. Is it a number? Ingenuity in action A little while ago, a Batch file writer asked us: Is there an easy way, in a Batch file, to tell if a parameter or a variable is a proper number? Well, the first thing to say is that there isn't a standard Batch command to do this. So we looked through each Batch command, typing "CommandName /?" to read the brief help on each (yes, we still read it ourselves!). Eventually, we noticed this bit of information for the fc command (Exercise 50): ex 50 C:\CSW>fc /? Compares two files or sets of files and displays the differences between them. (lots of other switches, each one a /letter) /nnnn Specifies the number of consecutive lines that must match after a mismatch. C:\CSW>_ It doesn't matter what the /nnnn switch does! What does this switch do? It may seem strange, but it doesn't really matter! ... (OK, you want to know). When fc compares files and finds a difference, it writes the differences to STDOUT (normally the screen). After each difference it finds, fc needs to decide from what point the two files start to be the same again. It does this by reading on further until two lines from each file match again. Two matching lines is a good criterion that handles most cases. But the programmer who wrote fc thought: What if the user prefers a different number? So he or she added the /nnnn switch, so you can specify another number if you want. And when they added it, they had to include something else that doesn't show (but it's there!). Let's try a few fc commands in immediate mode (Exercise 51): ex 51 C:\CSW>fc hw.bat rc.bat Comparing files HW.BAT and rc.bat ****** HW.BAT @ECHO OFF ECHO Hello World ****** rc.bat @ECHO OFF ECHO.Opening Return-Code shell... command /z /k PROMPT RCode $p$g ****** C:\CSW>fc carroll.txt carroll.txt Comparing files CARROLL.TXT and carroll.txt FC: no differences encountered C:\CSW>fc nul nul Comparing files NUL and nul FC: no differences encountered C:\CSW>_ Try a few switches Now let's try some of the switches from the fc /? brief help list (Exercise 52): ex 52 C:\CSW>fc /n nul carroll.txt Comparing files NUL and carroll.txt ****** NUL ****** carroll.txt 1: Twas brillig, and the slithy toves 2: Did gyre and gimble in the wabe; 3: All mimsy were the borogoves, 4: And the mome raths outgrabe. ****** C:\CSW>fc /x nul nul FC: Invalid switch (x is an invalid switch) Comparing files NUL and nul FC: no differences encountered C:\CSW>fc /x nul nul>nul C:\CSW>_ How did fc know the /x switch was invalid? The fc command knew the /x switch was invalid because it checked it against its list of valid switches (/a, /b, /c and so on). That's obvious. So far so good. Now let's try that /nnnn switch (Exercise 53): ex 53 C:\CSW>fc /2 nul nul Comparing files NUL and nul FC: no differences encountered C:\CSW>_ How did fc know the /2 switch was valid? Now for some amateur dramatics! You play Dr Watson, and we'll play Sherlock Holmes: Dr Watson (that's you) Is there any point to which you wish to draw my attention? Sherlock Holmes To the curious incident of the /2 switch of "fc /2 nul nul" Dr Watson But nothing happened when we used that switch, Holmes. Sherlock Holmes That was the curious incident, Watson. Let's think about that, while we try a few more /nnnn switches (Exercise 54): ex 54 C:\CSW>fc /36 nul nul Comparing files NUL and nul FC: no differences encountered C:\CSW>fc /123456789 nul nul Comparing files NUL and nul FC: no differences encountered C:\CSW>fc /1 nul nul Comparing files NUL and nul FC: no differences encountered ("FC:" once) C:\CSW>fc /1n nul nul FC: Invalid switch (n isn't a digit) Comparing files NUL and nul FC: no differences encountered C:\CSW>fc /1234;5678 nul nul FC: Invalid switch ("FC:" once) Comparing files NUL and nul FC: no differences encountered ("FC:" twice) C:\CSW>_ The fc command checks its figures carefully The fc command "knows" /1 is a valid switch. How does it know? Well, it doesn't just check each switch against the possible valid letters (/a, /b, /n and so on). If a switch starts with a digit, fc assumes it's a /nnnn numeric switch. If there are more digits after the first, fc goes on to check the rest of the digits concerned, just to make sure they're valid digits. If they're not, fc complains about it. We can make use of this to "persuade" fc to check numbers for us. We'll add our possible number to an fc switch that starts with a digit, say /1, and see if fc still thinks the whole thing is a valid switch. And we know the difference between a valid switch and an invalid one is: * Only one line with the text FC: is produced by the valid switch * But two lines with FC: (one a complaint) are produced by the invalid switch In our script, we'll count the "FC:" instances by piping them through find (Exercise 55): ex 55 C:\CSW>fc /1x nul nul | find "FC:" FC: Invalid switch FC: no differences encountered (2 lines with "FC:") C:\CSW>fc /1x nul nul | find /c "FC:" 2 (find /c produces only the count of the lines) C:\CSW>fc /12 nul nul | find "FC:" FC: no differences encountered (1 line with "FC:") C:\CSW>fc /12 nul nul | find /c "FC:" 1 (again, find /c produces only the count) C:\CSW>_ For a valid number, we simply look for a 1 A valid number gives a count of 1 and an invalid number gives a count of 2. So all we need do is add one more find pipe to look for "1". If this target is found, find returns ERRORLEVEL 0 and if it's missing, find returns ERRORLEVEL 1. (remember the ERRORLEVEL rule for find: 0=F0und and 1=M1ssing) We can use this combination of fc+find+find in a double pipe to check for a valid number. If we always start the /nnnn switch of fc with a digit 1, fc will check any other characters we add to the /1 switch (Exercise 56): ex 56 C:\CSW>fc /1 nul nul | find /c "FC:" | find "1" 1 C:\CSW>level The current errorlevel is 0 C:\CSW>fc /1x nul nul | find /c "FC:" | find "1" C:\CSW>level The current errorlevel is 1 C:\CSW>_ Using with variables In a script the "numbers" we need to check will usually be in variables or in command-line parameters. We can't use parameter symbols in immediate mode, so let's practise with some variables to see how we can put this numeric check together (Exercise 57): ex 57 C:\CSW>set good=123 C:\CSW>set bad=abc C:\CSW>fc /1%good% nul nul | find /c "FC:" | find "1" 1 C:\CSW>level The current errorlevel is 0 C:\CSW>fc /1%bad% nul nul | find /c "FC:" | find "1" C:\CSW>level The current errorlevel is 1 C:\CSW>_ Add the numeric check to DEMO.BAT With all the hard work done, adding the numeric check to DEMO.BAT is easy. We need to test the %1 parameter, so we simply use /1%1 as the fc switch to compare NUL with NUL. If %1 expands to a valid number, the switch will be accepted. If it doesn't fc will generate the extra "FC:" report and our numeric test will set ERRORLEVEL 1 because the count of lines containing the target string "FC:" will be 2 (Exercise 58): ex 58 :LOOP through command-line parameters IF (%1)==() GOTO CLEANUP :: Set ERRORLEVEL 1 if %1 contains non-numerics fc /1%1 NUL NUL | find /c "FC:" | find "1">NUL ECHO. %%1 has the value: %1 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) Using ERRORLEVEL from numeric check Next we'll alter the message that displays each command-line parameter in turn. We'll make it different if the parameter is a bad number (Exercise 59): ex 59 :: Set ERRORLEVEL 1 if %1 contains non-numerics fc /1%1 NUL NUL | find /c "FC:" | find "1">NUL ECHO. %%1 has the value: %1 IF ERRORLEVEL 1 ECHO. %%1 has the value: %1 (bad number) IF NOT ERRORLEVEL 1 ECHO. %%1 has the value: %1 :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) Test the numeric check With the new lines saved, let's test the script again. Watch carefully for syntax error messages. If you see any, check your code carefully. If you can't spot typing errors, use the normal ECHO ON method to see lines displayed as they are executed (Exercise 60): ex 60 C:\CSW>demo 1 2 3 x %1 has the value: 1 The current errorlevel is 1 %1 has the value: 2 The current errorlevel is 2 %1 has the value: 3 The current errorlevel is 3 %1 has the value: x (bad number) (press Ctrl C ) Terminate batch job (Y/N)?y (and reply Y ) C:\CSW>_ Skip on bad numbers If the ERRORLEVEL from our numeric check is 1, we need to skip the line that pipes the bad number into choice. We already have a suitable label, :DELAY, that we can jump to, so add the conditional jump (Exercise 61): ex 61 IF ERRORLEVEL 1 ECHO. %%1 has the value: %1 (bad number) IF NOT ERRORLEVEL 1 ECHO. %%1 has the value: %1 :: Skip setting the ERRORLEVEL if %1 is a bad number IF ERRORLEVEL 1 GOTO DELAY :: Set ERRORLEVEL to digit in %1 (set 10 for %1=0) ECHO.%1|choice /c:1234567890>NUL :: Clear ERRORLEVEL to zero if a %1=0 did set 10 IF ERRORLEVEL 10 %COMSPEC% /c CALL C:\CSW\LEVEL.BAT :DELAY the script for a second REM | choice /c:delay /td,1>NUL Test DEMO.BAT again With those changes saved, test DEMO.BAT again with some good numbers and some bad numbers (Exercise 62): ex 62 C:\CSW>demo 3 4 5 badnum x 2 %1 has the value: 3 The current errorlevel is 3 %1 has the value: 4 The current errorlevel is 4 %1 has the value: 5 The current errorlevel is 5 %1 has the value: badnum (bad number) %1 has the value: x (bad number) %1 has the value: 2 The current errorlevel is 2 No further parameters to process C:\CSW>_ A note about STDERR messages We saw that we could apply redirection and pipes to the fc error message about an invalid /nnnn switch. That's because fc uses STDOUT for that message. However, many error messages are sent to the standard error channel, STDERR. Neither redirection nor pipes will work with messages on STDERR in ? Windows 95/98/ME. Messages on STDERR and STDOUT look the same, so the only way to tell if you can use redirection or pipes with an error message is to try. And it's worth remembering that trial and error is the way many new Batch techniques are discovered! Windows NT/2000/XP does allow you to redirect any messages on STDERR: DIR NoFile.ext >LIST.TXT 2>&1 The 2>&1 asks for channel 2 (STDERR) to be redirected to channel 1 (STDOUT). So, if NoFile.ext doesn't exist, the error message is redirected to LIST.TXT (but this won't work in Windows 95/98/ME). A final tidy up Let's finish by tidying up the two variables, GOOD and BAD, that we used with the numeric check in immediate mode (Exercise 63): ex 63 C:\CSW>set good= C:\CSW>set bad= C:\CSW>_ What we have learnt In this Lesson we have learnt how to: * Use "quotes" to group text and Space s together in FOR IN DO statements * Remove the "quotes" around text in variables with FOR IN DO * Use, and suppress, the special FOR IN DO treatment of / character * Use wildcards in FOR IN DO statements to scan through matching filenames * Hide files from FOR IN DO wildcard scan with attrib +h (set hidden attribute) * Use LFNFOR to make a FOR IN DO wildcard scan return Long File Names * Use FOR IN DO to replace multiple instances of similar commands * Use an ECHO+choice pipe to set simple ERRORLEVELs in scripts * Understand the 64 token limit in command lines (eg: a long FOR IN DO) * Add a REM | pipe to fix the bug in our choice timed delays * Loop through an unknown number of parameters by SHIFTing and testing them * CALL a second script (returning to the first when the second one finishes) * Use fc combined with a find pipe to return an ERRORLEVEL for comparisons * Use a little ingenuity to persuade fc to check whether or not a number is valid Congratulations on finishing the Course! And now, you've completed the Allen & Company Windows 95/98/ME Batch Course. Congratulations, and well done! There is more to learn, of course, but now you're equipped with a sound grasp of Batch programming basics as a good base for further study on your own. And you've had a taste of the special ingenuity (and deviousness!) that Batch programming stimulates. Note Keep your CSW files for future study. However, if you happen to lose them, you can download a copy of files as they should be at the end of Lesson 20. Any questions? If you have questions or comments about this Batch File Course, you can Contact us. Please say whether you are using Windows 95/98/ME or Windows NT/2000/XP. Your email subject line should contain: Allenware.com website. Advanced techniques For further study, there are fully-documented examples of standard Batch file techniques in our Batch Library. These StudyPacks start from the skill level where this Course ends. We hope you enjoyed the taking the Course as much as we enjoyed researching and writing it. Happy Windows Batch programming in the future! William Allen and Linda Allen © Copyright 2003-06 Allen & Company. All rights reserved © |