------------------------------------------------------------ BATCH PROGRAMMING ELEMENTS A collection of batch language enhancers in batch language Collected by Dirk van Deun Programs and longer quotations (re)published with permission ------------------------------------------------------------ "In der Beschraenkung zeigt sich erst der Meister." (Goethe) ------------------------------------------------------------ If you want extra information, if you have found a bug or developed a better routine, mail me at dvandeun at vub.ac.be ------------------------------------------------------------ Date of last revision: June 7, 1996 ------------------------------------------------------------ ----------- PROLEGOMENA ----------- Not all batch files in this collection are easy too understand for the beginner. If you are not already a batch file fanatic, it might be better to read http://student.vub.ac.be/~dvandeun/batvirus.all first, which contains quite a few useful digressions which I did not repeat here. However, all batch files in this text can be understood with the help of your MS-DOS manual, if you know these three undocumented or barely documented facts: 1. Starting a line with :: has the same function as starting a line with REM, only it executes faster, because REM is a DOS command, and DOS preprocesses it (interpreting >, <, |, etc.) before 'executing' it; a line starting with :: on the other hand, is just a label called : to DOS, and it is skipped immediately. So REM > file creates a 0 byte file, and :: > file doesn't. 2. FOR %a IN (/ABCD) is functionally equivalent with FOR %a IN (A BCD): in a FOR argument DOS splits a word preceded with a slash in two parts: the first letter, and the rest. If you do not see the use of this, note that the slash can also be used with variables and command line parameters: in FOR %a IN (/%VAR%), %VAR% is interpreted first, and then the first letter is cut off. 3. ECHO. displays an empty line; ECHO without the dot gives the status of echo: 'echo is on' or 'echo is off'. ECHO.%1 will display only the contents of %1, or an empty line if %1 is empty. Remember that this is a collection, not a subroutine library. You shall have to chose beween different methods yourself, according to your needs. At the moment I write this, I have already included 4 different approaches to dealing with simple positive integers, for example, and if I discover new methods, I will certainly include them. This is not a well-organized manual either. It's a mess. It's meant to browse through. It's cryptic in places. It's the net result of one day of copy/pasting everything I had, and the later addition of new stuff coming in by e-mail. If I had time to spare when I received new programs, I commented on them. If not, I didn't. -------- 1. Input -------- Output from batch files is trivial, but input seems almost impossible. There is a way, though. This batch file is written by Tom Lavedas (kwh@dgsys.com), also known as he who puts redirection commands where God did not intend them to be. It uses debug, which is something like cheating for the real batch file programmer, but does this only to display the prompt without cr/lf. The input function proper is 'pure batch', so the purists can, if they are content to see the prompt above, instead of beside the cursor, throw out the offending part: [ Mr. Lavedas wondered why I thought using debug was 'something like ] [ cheating'. Well, that is because debug is a programming language in ] [ itself. With debug, anything is possible, because you can take direct ] [ control over the hardware. This is against the spirit of .bat files. ] [ It can be argued in this example, however, that debug is only used as ] [ a file manipulation tool, not to assemble a procedure in machine ] [ language. ] --------------------------from a usenet article--------------------------- The first argument is the name of a file containing a prompt string. A full path name must be provided if the file is not in the current directory. After the prompt is displayed, the routine waits for input with the cursor positioned at the end of the prompt string. That is, without a carriage return/line feed. Each word in the input string, up to the number of arguments provided, is returned in the environment variables named as arguments on the CALL INPUT line. -------- :: INPUT.BAT - A generalize input routine. :: :: Syntax: :: CALL INPUT PromptFile Arg1 [Arg2 [Arg3 ...]] :: > &SCRIPT.& ECHO E100 83 E9 02 >>&SCRIPT.& FOR %%v IN (L103 P NCON W103 Q) DO ECHO.%%v < &SCRIPT.& IF EXIST %1 DEBUG %1 > NUL DEL &SCRIPT.& IF EXIST %1 SHIFT > EN#ER.BAT FC CON NUL /LB1 /N | DATE | FIND " 1: " > ENTER.BAT REM :LOOP >>ENTER.BAT ECHO SET %1=%%5 IF [%2]==[] GOTO END SHIFT >>ENTER.BAT ECHO SHIFT GOTO LOOP :END CALL EN#ER DEL EN?ER.* Figure 1 - INPUT.BAT, A full function string input routine similar to the one found in QBASIC. ------- All the standard DOS edit keys can be used while keying in input, which means they cannot be made part of the input string. That is, this approach will not return the escape key or the backspace for example. Likewise, the standard DOS delimiters (",", ";", "=" and tab) act as delimiters in the input string and are not returned. Redirection characters ("<", ">" and "|") must not be used as they will cause errors or erratic behavior. In use, the routine could be implemented something like this . . . ECHO Enter input and output file names: > {PROMPT} CALL INPUT {PROMPT} INFILE OUTFILE DEL {PROMPT} IF [%INFILE%]==[] GOTO FIL_ERR1 IF [%OUTFILE%]==[] GOTO FIL_ERR2 . . . The names of the files provided by the user will be returned in the environment variables INFILE and OUTFILE respectively. If no name or only one is provided, the INPUT routine removes the variable(s) from the environment, which permits error handling. (Note that labels have only eight significant characters, though they can be of any length. This means that a label called File_Error_1 is equivalent to File_Err999, so some care must be exercised in naming labels.) One additional note - the redirections have all been put at the start of the lines. DOS doesn't care and I find it makes for a cleaner listing. You can put them at the end, if you want. ------------------------------end of quote-------------------------------- This program, I must remark, will only work with an English language DOS version, as it expects the first word of the prompt displayed by 'date' to be 'Enter'. I have made a quick'n'dirty adaptation for the Dutch DOS version, as an example: @echo off > &SCRIPT.& ECHO E100 83 E9 02 >>&SCRIPT.& FOR %%v IN (L103 P NCON W103 Q) DO ECHO.%%v < &SCRIPT.& IF EXIST %1 DEBUG %1 > NUL DEL &SCRIPT.& IF EXIST %1 SHIFT > EN#ER.BAT FC CON NUL /LB1 /N | DATE | FIND " 1: " > voer.BAT REM :LOOP >> voer.BAT ECHO SET %1=%%6 IF [%2]==[] GOTO END SHIFT >> voer.BAT ECHO SHIFT GOTO LOOP :END CALL EN#ER DEL EN?ER.* del voer.bat The Dutch prompt is not 'Enter new date (mm-dd-yy)' but 'Voer nieuwe datum in (dd-mm-jj):', which is, by the way, one word longer, so I also had to change 'ECHO SET %1=%%5' into 'ECHO SET %1=%%6'. We will meet more problems caused by the existence of different language versions; I will always at least mention them briefly. Another procedure, XSET.BAT by Ed Schwartz (schwartz@sunco.co.kp.dlr.de): --------------------------from a personal e-mail-------------------------- Here's my *pure batch* solution to user input: Note that this batch must be termited with a Ctrl-Z immediately after the last line (no CR/LF !) and that it must be located in C:\ :: :: interactive set command :: :: syntax: xset env_var :: :: one line of user input is :: assigned to the specified :: environment variable :: :: Note: - last line must terminate :: with Ctrl-Z :: - xset.bat must be located in C:\ :: :: (c) Ed Schwartz :: @echo off if %2*==+* goto set echo [13;26;13p copy C:\xset.bat+con %temp%\_xset.bat > nul echo [13;13p call %temp%\_xset.bat %1 + :set if %2*==+* set %1= ------------------------------end of quote-------------------------------- It is evident that this method needs ANSI.SYS. The first method uses piping through date, and the second appending to a file that doesn't end on cr/lf, but the difference is cosmetic at best. The reason I include this second batch, is the clever use made of ANSI escape sequences. The second method cannot display a prompt in front of the cursor. There is a way to work around the problem that the batch file has to be situated in a known, constant directory: let the batch look for itself in all directories of the system path + the current directory, with IF EXIST %0.bat. My virus for batch files did something like that to find its own code back. Let this be an inducement to read my earlier work :-) --------- 2. Output --------- Output is, as I said, trivial, but there is one catch: you cannot display all characters with ECHO: >, < and | are interpreted by DOS as redirection commands. The same Tom Lavedas also invented a way around this problem. --------------------------from a usenet article--------------------------- Someone said that this first part of your problem can't be done. Well it's not too easy, but it can be done. The fastest way I have found to create a string containing characters that are normally unprintable, such as the '>' or escape characters, is to use the COMMAND FOR, and PROMPT statements together. For example, the following sequence outputs X > 1 to the screen by using the dollar sign equivalent of the greater than sign in the PROMPT. COMMAND/E:1999/CFOR %%v IN (1 2) DO PROMPT X $G 1$_ | FIND "$"/V At first glance it may seem like a rather circuitous way of using the PROMPT command to output the character, but this approach has several advantages. It does not disturb the existing PROMPT and can be redirected to a file or a printer port. The FOR loop is needed to get the PROMPT to "take". The FIND filters out extra lines. The $ cannot be included in the output generated in this way. The technique can also be to redefine a key or store a macro in a key if ANSI.SYS is loaded. You would use the $E[nn;nnp ANSI sequence in that case. Characters that have dollar sign equivalents are: $g > (greater-than sign) $l < (less-than sign) $b | (pipe symbol) $q = (equal sign) $$ $ (dollar sign) $_ Enter-linefeed $e ASCII escape code (code 27) $h Backspace $t Current time $d Current date $p Current drive and path $v DOS Version number $n Current drive ------------------------------end of quote-------------------------------- This method, however, also displayed 2 empty lines above X > 1 on my computer screen, so I adapted it myself to: COMMAND/E:1999/CFOR %%v IN (1 2) DO PROMPT X $G 1$_ | FIND "X > 1" (You can include redirection characters between double quotation marks.) Of course, you can also just TYPE an external file with redirection characters in it, if there is no reason against using a second file. And another method, from David B. Ferguson , a bit messy (he uses control characters in a batch file), but very pragmatic: --------------------------from a usenet article--------------------------- Gee's guys this is ridicules! How can something so simple become so complicated. Simply use the DBLQUOTE & BACKSPACE character combination to display the character you desire, take example the following useless but creative uuencoded batch file. Sorry but the BACKSPACE characters won't make it to the post. ------------------------------end of quote-------------------------------- The relevant line of the included file was (replace ^H by a real backspace): echo Press "^H"^H to continue . . . . ---------------------- 3. String manipulation ---------------------- You can easily manipulate strings as soon as you have seperated the words into single letters. Use SPLIT.BAT (written by myself). SPLIT.BAT @echo off if "%Splitting%=="T goto Splitting set Splitting=T for %%a in (/%1) do call %0 %%a goto end :Splitting if %Splitting%==T if not "%Split%==" set Split=%Split% %1 if %Splitting%==T if "%Split%==" set Split=%1 set Splitting= :end 'CALL SPLIT Hello' will set a variable SPLIT to 'H e l l o' (without the quotes). 'CALL SPLIT %variable%' will of course also work. You must empty the variable SPLIT yourself before calling this procedure, if you do not want the new letters to be appended to the old contents of the variable. I must have had a reason for this when I first programmed it, but I don't remember. [WARNING: SPLIT.BAT will not work right if there are MS_DOS wildcards (*, ?)] [in the processed string, because the FOR command will try to expand them. ] [You can test a string, e.g. %1, for wildcards with: ] [ set wildcards=T ] [ for %%a in (%1) do if [%1]==[%%a] set wildcards=F ] ---------------------- After using SPLIT.BAT you can call an other batch file with %split% as parameter. DOS will spread the letters of the variable over %1, %2, etc., and you can manipulate the separate letters by these names. An example: REVERSE.BAT @echo off set split= call split %1 reverse2 %split% REVERSE2.BAT @echo off set reverse= :loop if [%1]==[] goto end set reverse=%1%reverse% shift goto loop :end echo %reverse% For more complicated operations on strings, CAR and CDR can be useful in keeping things simple, and in straightening out incomprehensibly recursive code: (CAR and CDR are Lisp keywords, the former refering to the first element of a list, the latter to the rest) CAR.BAT @echo off set car=%1 CDR.BAT @echo off set cdr= :LOOP shift set cdr=%cdr% %1 if not '%2==' goto LOOP A quick method to convert a word to upper case makes use of the PATH command, which always converts its input to upper case (do not use SET PATH= ): UPCASE.BAT @echo off set _tmp=%path% path %1 echo %path% path %_tmp% set _tmp= Being able to convert a string to upper case is enough to allow case independent string comparison. If you really want to convert strings to lower case, here's a method. The secret is in the format of the output of dir /w: by creating your own subdirectory and using the right command line switches with dir, you can create executable lines of output, in which the first word is the name of a batch file, and the other words will be interpreted as command line parameters, and so they can be manipulated. Note: ECHO Y | DEL *.* is language dependent; if the word for 'yes' does not start with a Y in your language, that line will make your PC hang. It can however be replaced with 'FOR %%A in (*.*) DO DEL %%A'. --------------------------from a personal e-mail-------------------------- In your batch file techniques compilation, you wrote "There is no such trick to convert from upper to lower case: ..." I found a few minutes to write and say that there *is* a way to do it without parsing the string letter by letter or comparingit to a list of lower case letters. I have developed a technique to do it using the DIR command's /L (lowercase) switch. ---------- :: LOWER.BAT - A routine to convert a string to lower case. :: developed by Thomas G. Lavedas :: :: Note: Requires DOS 6.xx :: :: Syntax: LOWER STRING :: :: where STRING is any valid character string. :: @ECHO OFF FOR %%v IN (M C) DO %%vD $$$ ECHO.>$ REM>$$ REM>$$$.BAT FOR %%v IN (A B S) DO SET !%%v= FOR %%v IN (%1) DO IF [%%v]==[%1] SET !A=%1 IF [%!A%]==[] GOTO ERROR ECHO %1 | FIND "%%" | IF NOT ERRORLEVEL 1 GOTO ERROR > $$$$.BAT ECHO SET !S=%%!S%%%%1 :START %COMSPEC% NUL /C COPY $ %!A% IF NOT EXIST %!A% GOTO ERROR > $$$.BAT DIR /L/W | FIND "$$$$" CALL $$$.BAT DEL %!A% ECHO %!S% | FIND "%1"/I | IF NOT ERRORLEVEL 1 GOTO END SET !B= :LOOP FOR %%v IN (/%!A%) DO SET !A=%%v SET !B=%!B%X IF NOT [%!B%]==[XXXXXXXX] GOTO LOOP GOTO START :ERROR ECHO *** CANNOT CONVERT STRING *** :END ECHO.%!S% ECHO Y| DEL *.* > NUL CD .. RD $$$ FOR %%v IN (A B) DO SET !%%v= ------- It does parse the sting, but does not do a character by character conversion. It is able to convert eight characters at a time. The lower case version of the string is returned in the environment variable !S. The ECHO.%!S% line is purely for demonstration purposes. I have used this approach to create a PROPER routine to capitalized names. That is, I parse off the first letter to capatalize it and pass the rest of the string to LOWER.BAT to make sure it is all lower case. Though, I freely admit that its usefulness is more as part of a hobby than for any *productive* work. Like your Splitting routine, it cannot handle DOS wildcards. In fact it only handles legal file naming characters since it generates a dummy file name to effect the case conversion. That is one reason the routine is so long. It performs a number of error tests to trap strings with illegal characters. The particular implimentation shown above relies on the ERRORLEVEL returned from FIND to perform some of the testing. Therefore, it requires US DOS version 6.0 and above. The following, somewhat longer, routine is a work around for US DOS version 5.0 and up. (I seem to remember that the European versions may have different numberings.) It is possible it will work with US version 4, but I didn't have a manual handy to check. I did confirm that versions 3.3 (US) and earlier do not have the /L switch. -------- :: LOWER5.BAT - A routine to convert a string to lower case. :: developed by Thomas G. Lavedas :: :: Note: Requires DOS 5.xx and up. :: :: Syntax: LOWER5 STRING :: :: where STRING is any valid character string. :: @ECHO OFF FOR %%v IN (M C) DO %%vD $$$ ECHO.SET !D=T>0.BAT ECHO.SET !D=F>1.BAT REM>$$$.BAT FOR %%v IN (A B D S) DO SET !%%v= FOR %%v IN (%1) DO IF [%%v]==[%1] SET !A=%1 IF [%!A%]==[] GOTO ERROR > $$$.BAT ECHO %1 | FIND "%%"/C CALL $$$ IF [%!D%]==[F] GOTO ERROR > $$$$.BAT ECHO SET !S=%%!S%%%%1 :START %COMSPEC% NUL /C COPY $$$.BAT %!A% IF NOT EXIST %!A% GOTO ERROR > $$$.BAT DIR /L/W | FIND "$$$$" CALL $$$ DEL %!A% > $$$.BAT ECHO %!S% | FIND "%1"/I/C CALL $$$ IF [%!D%]==[F] GOTO END SET !B= :LOOP FOR %%v IN (/%!A%) DO SET !A=%%v SET !B=%!B%X IF NOT [%!B%]==[XXXXXXXX] GOTO LOOP GOTO START :ERROR ECHO *** CANNOT CONVERT STRING *** :END ECHO.%!S% ECHO Y| DEL *.* > NUL CD .. RD $$$ FOR %%v IN (A B D) DO SET !%%v= ------------------------------end of quote-------------------------------- ------------------------ 4. Mathematical routines ------------------------ I have several sets of mathematical routines, from simple counting batches to elaborate libraries. a) A simple counting program by Jerzy Tarasiuk (jt@fuw.edu.pl) --------------------------from a usenet article--------------------------- >>>>> "Mic" == Mic Bergen writes: Mic> I'm trying to write a sequential ping program. Basically, I want it to go Mic> something like: Mic> set n=1 Mic> while n<256 Mic> :loop Mic> ping 255.255.255.n >>ping.log Mic> set n=n+1 Mic> goto loop Unfortunately, batch files are very primitive and cannot do it. No arithmetic can be done, unless you use some batch enhancer. However, you can do it the following way: file pingmain.bat: @echo off if #%1 == # goto main if %1 == 000 goto skip ping 255.255.255.%1 >>ping.log : I don't want all values... 105 is last - change the line below if %1 == 105 exit goto skip :main echo for %%%%i in (0 1 2) do call ping_b2_ %%%%i >ping_b1_.bat echo for %%%%j in (0 1 2 3 4 5 6 7 8 9) do call ping_b3_ %%1%%%%j >ping_b2_.bat echo for %%%%k in (0 1 2 3 4 5 6 7 8 9) do call pingmain %%1%%%%k >ping_b3_.bat command /c ping_b1_ del ping_b?_.bat :skip I tested it (except I put echo instead ping) - works! Note using echo to create extra batch files isn't necessary - can use one file passing a parameter to it. ------------------------------end of quote-------------------------------- b) Counting from 00 to 99 with Ed Schwartz (schwartz@sunco.co.kp.dlr.de) --------------------------from a usenet article--------------------------- > >I have a problem which I was wondering if it could be solved by writing a >short batch command. Unfortunately I can't find the correct commands to >use. Here is the situation: > > I have a large (several actually) directory with various related > files in it. Each file is named differently and there is no apparent > method to the naming. I would like to be able to sequentially rename > each file in the following manner: > > Before: After: > > abcd.txt file01.txt > bcde.txt file02.txt > cdef.txt file03.txt > . . > . . > . . > > I figure this can be done with a rename statement and some kind of > looping structure which would rename the file with the given beginning > adding the counter variable to the end to make the sequence. > >Is this problem easily addressed using strictly batch programming? ^^^^^^ NO ! But it's possible. Some time ago I published a batch that is able to count, i.e. every CALL to that batch increments the environment variable %COUNT%. So, what you have to do: In a directory that's in your path (but *not* in the directory that contains the files to be renamed!) create the following 3 batches; then CD to the directory that contains the files to be renemed and from there simply enter XREN1.BAT 1. XREN1.BAT @echo off set count=00 for %%f in (*.*) do call XREN2.BAT %%f 2. XREN2.BAT @echo off call COUNT.BAT ren %1 file%count%.txt 3. COUNT.BAT ::::::::::::::::::::::::::::::::::::::::::::: :: COUNT.BAT :: :: :: :: Counter for Batch-Files :: :: Syntax: COUNT :: :: Every call to COUNT.BAT will increment :: :: the environment variable %COUNT% (00-99):: :: :: :: (c) Ed Schwartz :: ::::::::::::::::::::::::::::::::::::::::::::: @echo off for %%f in (/%COUNT%) do set Units=%%f for %%f in (/%COUNT%#) do if not %%f*==%Units%#* set Tens=%%f if %Tens%*==#* set Tens= set X=Units goto L%Units% :L :L0 set %X%=1 goto EXX :L1 set %X%=2 goto EXX :L2 set %X%=3 goto EXX :L3 set %X%=4 goto EXX :L4 set %X%=5 goto EXX :L5 set %X%=6 goto EXX :L6 set %X%=7 goto EXX :L7 set %X%=8 goto EXX :L8 set %X%=9 goto EXX :L9 set %X%=0 set X=Tens goto L%Tens% :EXX set count=%Tens%%Units% for %%f in (Tens Units X) do set %%f= ------------------------------end of quote-------------------------------- c) my own math library, largest in scope but never completed (I got bored when the largest difficulties had been solved; and the part I did implement was already massive overkill for my problem): It uses SPLIT.BAT, described earlier; does binary math. I never got to implementing the DIV function, so I cannot convert binary to decimal; decimal to binary however is provided. It is the only library I know of which can do (16-bit integer) multiplication. Uses 'reverse binary' as internal format, i.e. 12 = 0 0 1 1 0 0 0 0 in byte format, 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 in word. For subtraction, use addint with the 2-s complement of the second parameter. I did this in an older prototype, which only worked with byte values. Division would be repeated subtraction. And you could make simple INC and DEC functions to improve efficiency for counters. Note: some functions can work with command line parameters as well as with predefined variables; output always goes into a variable. ADD.BAT @echo off rem Adds two reverse binary bytes (acca & accb or command line rem parameters) to accx WITH carry! if "%1==" add %acca% %accb% set accx= :loop if %1==%9 goto same if %carry%==0 set accx=%accx% 1 if %carry%==1 set accx=%accx% 0 shift if not "%9==" goto loop goto end :same set accx=%accx% %carry% set carry=%1 shift if not "%9==" goto loop :end ADDINT.BAT @echo off rem Adds two reverse binary words/integers (accaL/accaH & accbL/accbH) rem to accxL/accxH set carry=0 set accxtemp=%accx% call add %accaL% %accbL% set accxL=%accx% call add %accaH% %accbH% set accxH=%accx% set accx=%accxtemp% set accxtemp= MUL.BAT @echo off rem Multiplies two reverse binary bytes (acca & accb or command rem line parameters) to accxL/accxH if "%1==" mul %acca% %accb% set accaLtemp=%accaL% set accaHtemp=%accaH% set accbLtemp=%accbL% set accbHtemp=%accbH% set accaL=0 0 0 0 0 0 0 0 set accaH=0 0 0 0 0 0 0 0 set ONE=%1 set TWO=%2 set THR=%3 set FOU=%4 set FIV=%5 set SIX=%6 set SEV=%7 set EIG=%8 if %9==1 set accaL=%ONE% %TWO% %THR% %FOU% %FIV% %SIX% %SEV% %EIG% set accxL=%accaL% set accxH=%accaH% shift if %9==1 set accbL=0 %ONE% %TWO% %THR% %FOU% %FIV% %SIX% %SEV% if %9==1 set accbH=%EIG% 0 0 0 0 0 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 %ONE% %TWO% %THR% %FOU% %FIV% %SIX% if %9==1 set accbH=%SEV% %EIG% 0 0 0 0 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 0 %ONE% %TWO% %THR% %FOU% %FIV% if %9==1 set accbH=%SIX% %SEV% %EIG% 0 0 0 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 0 0 %ONE% %TWO% %THR% %FOU% if %9==1 set accbH=%FIV% %SIX% %SEV% %EIG% 0 0 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 0 0 0 %ONE% %TWO% %THR% if %9==1 set accbH=%FOU% %FIV% %SIX% %SEV% %EIG% 0 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 0 0 0 0 %ONE% %TWO% if %9==1 set accbH=%THR% %FOU% %FIV% %SIX% %SEV% %EIG% 0 0 if %9==1 call addint if %9==1 set accaL=%accxL% if %9==1 set accaH=%accxH% shift if %9==1 set accbL=0 0 0 0 0 0 0 %ONE% if %9==1 set accbH=%TWO% %THR% %FOU% %FIV% %SIX% %SEV% %EIG% 0 if %9==1 call addint set ONE= set TWO= set THR= set FOU= set FIV= set SIX= set SEV= set EIG= set accaL=%accaLtemp% set accaH=%accaHtemp% set accbL=%accbLtemp% set accbH=%accbHtemp% set accaLtemp= set accaHtemp= set accbLtemp= set accbHtemp= MULINT.BAT @echo off rem Multiplies accaL/H and accbL/H to accxL/H; overflow does often occur rem (does unsigned mul) set orig_accaL=%accaL% set orig_accaH=%accaH% set orig_accbL=%accbL% set orig_accbH=%accbH% call mul %orig_accaL% %orig_accbL% set accbL=%accxL% set accbH=%accxH% call mul %orig_accaL% %orig_accbH% set accaL=0 0 0 0 0 0 0 0 set accaH=%accxL% call addint set accbL=%accxL% set accbH=%accxH% call mul %orig_accaH% %orig_accbL% set accaH=%accxL% call addint set accaL=%orig_accaL% set accaH=%orig_accaH% set accbL=%orig_accbL% set accbH=%orig_accbH% INVERT.BAT @echo off set invert=%1 :loop shift if "%1==" goto end set invert=%1 %invert% goto loop :end DEC2BIN.BAT @echo off rem Converts a positive decimal number to two-byte reverse binary rem equivalent (binL/binH); function uses and destroys accaL/H, accbL/H rem and accxL/H if "%1=="/splitted goto splitted if "%1==" dec2bin %dec% set split= call split %1 dec2bin /splitted %split% :splitted shift set binL=0 0 0 0 0 0 0 0 set binH=0 0 0 0 0 0 0 0 :loop set accaL=%binL% set accaH=%binH% set accbL=0 1 0 1 0 0 0 0 set accbH=0 0 0 0 0 0 0 0 call mulint set binL=%accxL% set binH=%accxH% set carry=0 if %1==0 set accx=%binL% if %1==1 call add %binL% 1 0 0 0 0 0 0 0 if %1==2 call add %binL% 0 1 0 0 0 0 0 0 if %1==3 call add %binL% 1 1 0 0 0 0 0 0 if %1==4 call add %binL% 0 0 1 0 0 0 0 0 if %1==5 call add %binL% 1 0 1 0 0 0 0 0 if %1==6 call add %binL% 0 1 1 0 0 0 0 0 if %1==7 call add %binL% 1 1 1 0 0 0 0 0 if %1==8 call add %binL% 0 0 0 1 0 0 0 0 if %1==9 call add %binL% 1 0 0 1 0 0 0 0 set binL=%accx% call add %binH% 0 0 0 0 0 0 0 0 rem (for carry) set binH=%accx% shift if not "%1==" goto loop TEST1.BAT @echo off rem Temporary test file: this file adds the decimal numbers %1 and %2 rem and prints the result in reverse binary call dec2bin %1 set firstL=%binL% set firstH=%binH% call dec2bin %2 set accaL=%binL% set accaH=%binH% set accbL=%firstL% set accbH=%firstH% call addint echo %accxL% %accxH% NOT.BAT @echo off if "%1==" not %acca% if %1==1 set accx=0 if %1==0 set accx=1 shift :loop if %1==1 set accx=%accx% 0 if %1==0 set accx=%accx% 1 shift if not "%1==" goto loop d) But that's enough sillyness. Here is some short, useful and comprehensible code for a change: (By Tom Lavedas, kwh@DGS.dgsys.com) --------------------------from a personal e-mail-------------------------- The purpose then for me in scanning Usenets, periodicals, etc. is to find ways to ease the burden of using batch files to accomplish a task, thus expanding the horizons for their use. In that vein, might I suggest that the 'ease of use' test is not exhibited by the number manipulation routines you have in your compilation. The routines are far too complex to make sense to almost anyone but the author. This makes them hard to modify or maintain. I have found that numbers can be manipulated fairly easily using FIND with the /C switch. As a simple example, I would respectfully suggest the following as a way of performing a numbered count. -------- :: A sample routine that counts using decimal numbers. :: developed by Thomas G. Lavedas :: :: Syntax: :: COUNT Number :: :: Note: LOOP is always executed at least once. :: @ECHO OFF IF [%1]==[] %0 1 > ---##--- REM > --------.BAT ECHO SET {I}=%%2 :: :LOOP {The count advances one each time through the LOOP} :: >>---##--- ECHO. > ---##---.BAT FIND ""/V/C ---##--- CALL ---##--- :: :: Start Counted process. :: ECHO Count=%{I}% :: :: End counted process. :: IF NOT [%1]==[%{I}%] GOTO LOOP :: SET {I}= DEL ---??---.* -------- It has only 12 working lines, the rest are comments, and works rather quickly. Plus, I think that many more users could understand the concept and, therefore, be able to apply it to their particular needs. ------------------------------end of quote-------------------------------- As I have replied to Mr. Lavedas, I like the sillier routines because of their silliness. However, as this file is also meant to have some utilitarian value, I add Mr. Lavedas' program to the list. The program uses 'FIND file' instead of 'FIND ---##--- REM > --------.BAT ECHO SET {I}=%%2 :LOOP >>---##--- ECHO. > ---##---.BAT FIND ""/V/C ---##--- CALL ---##--- IF NOT [%1]==[%{I}%] GOTO LOOP SET {I}= ADD.BAT @echo off if [%2]==[] echo Missing parameter(s) if [%2]==[] goto end if [%1]==[0] set RESULT=%2 if [%1]==[0] goto end if [%2]==[0] set RESULT=%1 if [%2]==[0] goto end call ct %1 copy ---##--- TMP1 > nul call ct %2 copy ---##---+TMP1 TMPZ > nul > ---##--- REM > --------.BAT ECHO SET RESULT=%%2 > ---##---.BAT FIND ""/V/C TMPZ CALL ---##--- del tmp? del ---??---.* :end This was certainly very quick and rather dirty programming (you can still see how I simply copy/pasted some lines from count.bat into add.bat). I also made a diff.bat, which calculates the difference between two integers: now sub.bat is only a few instructions away: DIFF.BAT @echo off if [%2]==[] echo Missing parameter(s) if [%1]==[%2] set RESULT=0 if [%1]==[%2] goto end if [%2]==[] goto end if [%1]==[0] set RESULT=%2 if [%1]==[0] goto end if [%2]==[0] set RESULT=%1 if [%2]==[0] goto end call ct %1 copy ---##--- TMP1 > nul call ct %2 fc /n ---##--- TMP1 >TMPX find /c ":" TMPX > TMPY.BAT echo set RESULT=%%2 > --------.bat call tmpy del tmp?.* del ---??---.* :end If you need a real sub.bat for positive integers, all you have to know on top of the difference between the two numbers, is which one is the higher. Make two files, each with as many lines as one of the numbers, and FC /b will tell you which file/number is larger. How to extract this information from the output of FC is left as an exercise for the reader (meaning: I don't feel like doing it myself right now). See chapter 5.c. for more information on text parsing. -------------------------------------------------- 5. Database management and file processing/parsing -------------------------------------------------- a) A PROLOG-like database: DATA.BAT @echo off echo call expand %1 %2 %3>>dbase.bat UNDATA.BAT @echo off find /v "%1 %2 %3" dbase.bat COMPILE.BAT @echo off if exist _*.bat del _*.bat if exist dbase.bat dbase echo ERROR: no dbase. EXPAND.BAT @echo off shift if exist _%0.BAT goto exist echo @echo off>>_%0.BAT echo set %0=F>>_%0.BAT echo set _1=>>_%0.BAT echo set _2=>>_%0.BAT :exist echo if %%1==%1 if %%2==%2 set %0=T>>_%0.BAT echo if %%1==%1 if %%2==_2 set _2=%%_2%% %2>>_%0.BAT echo if %%1==%1 if %%2==_2 set %0=T>>_%0.BAT echo if %%1==_1 if %%2==%2 set _1=%%_1%% %1>>_%0.BAT echo if %%1==_1 if %%2==%2 set %0=T>>_%0.BAT echo if %%1==_1 if %%2==_2 set _1=%%_1%% %1>>_%0.BAT echo if %%1==_1 if %%2==_2 set _2=%%_2%% %2>>_%0.BAT echo if %%1==_1 if %%2==_2 set %0=T>>_%0.BAT EVAL.BAT @echo off shift call undersco %1 if %undersco%==T set parm1=_1 if %undersco%==F set parm1=%1 call undersco %2 if %undersco%==T set parm2=_2 if %undersco%==F set parm2=%2 call _%0 %parm1% %parm2% echo echo %%%0%%>__tmp.bat call __tmp del __tmp.bat if not %parm1%==_1 if not %parm2%==_2 END :LOOP if %parm1%==_1 call car %_1% if %parm1%==_1 call cdr %_1% if %parm1%==_1 echo %1=%car% if %parm1%==_1 set _1=%cdr% if %parm2%==_2 call car %_2% if %parm2%==_2 call cdr %_2% if %parm2%==_2 echo %2=%car% if %parm2%==_2 set _2=%cdr% for %%a in (%cdr%) do goto LOOP UNDERSCO.BAT @echo off set undersco=F for %%a in (/%1X) do if %%a==_ set undersco=T :: the X takes care that input strings like A_ do not set undersco to T END.BAT :: This is an empty file: 'call end' has no effect at all, but 'end' without :: 'call' terminates a batch file. Example: C:\>data livesin Dirk Brussels C:\>data livesin Sam Brussels C:\>data livesin Thomas Ghent C:\>compile C:\>eval livesin Dirk Brussels T C:\>eval livesin Dirk _Where T _Where=Brussels C:\>eval livesin _Who Brussels T _Who=Dirk _Who=Sam C:\>undata livesin Sam Brussels C:\>compile C:\>eval livesin _Who _Where T _Who=Dirk _Where=Brussels _Who=Thomas _Where=Ghent To embed such a database in a larger application, call data, undata and compile to make a database, and call the compiled file to look up information; eval is only a nifty user interface. Another approach to saving data to and reading data from disk can be found in chapter 9. b) Think small To save one or two or any small fixed number of variables to disk, you can write SET commands to a file with a .bat extension, and CALL the file later to read the variables back: example: echo set var=%var% >_tmpfile.bat + call _tmpfile.bat c) Reading free-form ascii files You can read and parse free-form ascii files: - if they end with two cr/lf pairs (just ECHO >> two empty lines if you're not sure, but better do this to a temporary copy, or your files will grow every time the program runs); - if there are no other empty lines in the input file; you can make empty lines non-empty by numbering all lines with FIND /n /v ""; - if they do not contain a valid date on a separate line (if not sure, use the same numbering trick); by piping the file through DATE, and CALLing the output as in INPUT.BAT, but you must remember that: - the file must not contain DOS redirection characters; - you cannot differentiate between the DOS delimeters (tab, space, '=', ...); - not all DOS users have an English language DOS (see my note to INPUT.BAT); - (if your DATE prompt is 'Enter new date') you have to copy loadfix.com to ENTER.com, and use NEW.bat as the batch file which does the real work; this is necessary because the lines in the output of date begin with 'Enter new date', not with 'CALL Enter new date'. Running a .com program does not break the execution of the batch file, and loadfix.com can run any other program or batch file. I have made a program called SMAWK.BAT (Simple Miniature AWK) using these principles, which replaces words in a file. The two programs that follow were made on a Dutch language DOS; the translated version has never been tested. So if you find a bug, mail me. SMAWK.BAT @echo off if [%3]==[] echo At least 3 parameters needed: if [%3]==[] echo infile outfile to_replace (replace_by) if [%3]==[] goto end set _torepl=%3 set _replby=%4 copy %1 _smawk.in0 >nul fc /n _smawk.in0 nul | find ":" >_smawk.in echo. >>_smawk.in date <_smawk.in | find "Enter" >_smawk.bat copy c:\dos\loadfix.com enter.com >nul for %%a in (1 2 3 4 5) do set _sm%%a=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX call _smawk for %%a in (1 2 3 4 5) do set _sm%%a= copy _smawk.out %2 >nul del _smawk.* del enter.com set _torepl= set _replby= :end NEW.BAT @echo off for %%a in (1 2 3 4 5) do set _sm%%a= if [%4]==[] goto endloop if not [%4]==[%_torepl%] goto A if [%_replby%]==[] goto A set _output=%_replby% :A if [%4]==[%_torepl%] goto B set _output=%4 :B :loop shift if [%4]==[] goto endloop if not [%4]==[%_torepl%] goto D if [%_replby%]==[] goto D if [%_output%]==[] goto C set _output=%_output% %_replby% :C if not [%_output%]==[] goto D set _output=%_replby% :D if [%4]==[%_torepl%] goto F if [%_output%]==[] goto E set _output=%_output% %4 :E if not [%_output%]==[] goto F set _output=%4 :F goto loop :endloop if [%_output%]==[] echo.>>_smawk.out if [%_output%]==[] goto G echo %_output%>>_smawk.out :G :end The long SET commands stretch the environment for NEW.BAT. Secondary command.com shells only get an environment that is just large enough to hold the contents of the environment of the calling copy of the command interpretor. ------------------------------------------------------------------ 6. Getting date, time, current drive, directory and version number ------------------------------------------------------------------ Setting these variables (except version number of course) is very simple: - setting the date: date 1-1-80 - setting the time: time 5:5:5 - setting the current drive: C: - setting the current directory: CD \BATCH Putting the current values into variables is something completely different, but it can be done. There is a well-known method that works for date and time, which I will demonstrate for the date, and for the English DOS version: *** The traditional method *** CURRENT.BAT @echo off set date=%4 GETTIME.BAT @echo off echo. | date | find "Current" | call > nul Usually, instead of using '| call', output of the FIND command is piped to a .bat file, which is executed afterwards. I have shortened this a bit with '| call', but read these caveats -- if you dare: You can pipe the output of a command into CALL, and the first non-empty line of output of the first command will be executed as a DOS command: e.g. TYPE \AUTOEXEC.BAT | CALL will execute the first non-empty line of AUTOEXEC.BAT and nothing more. There is a problem, however: this method only works as the last line of a batch file or on the command line; otherwise it will not work correctly; and, if the | CALL is followed by another pipe symbol, the line will not work, and it will make that the following command is ignored too ! (try echo. | call | echo. followed by a nonsense line, followed by a valid command). There could be even more silly effects, that I haven't encountered yet. All this is totally undocumented, and I am starting to see why. June 10, 1995: another problem has been found: if a batch file, containing such a | CALL construction is CALLed, that CALL must also be the last command of its batch file. Usefulness is again reduced considerably. However, it is possible to make DOS believe that the line containing | CALL is not part of a batch file (but that it is a command entered on the command line after the prompt) by using 'COMMAND < X.BAT' instead of 'CALL X' or 'COMMAND /c X', and so you avoid the problem that the | CALL must be the last line: such a 'batch file' has to end with a line containing 'exit'. Also, this method cannot make use of command line parameters, nor can it set environment variables permanently. It can of course echo SET X=Y commands to another file, which is to be executed later, but we are bordering on The Silly here, especially because '| CALL' was originally meant as a _shortcut_ ! *** The right method *** Method one only works for the date and the time; it requires at least one extra file; and it is language/version dependent. These 2 lines, on the other hand, set the variable DATE as well as the 'traditional' method: command /e:2000 /c for %%v in (1 2) do prompt set date=$d$_ | find /v "prompt" >_tmpfile.bat for %%a in (call del) do %%a _tmpfile.bat Also, this method is language/version independent. For time, current drive, current directory, and dos version number, just change the SET command (using the table with prompt variables in chapter 2). In circumstances where you can use the '| call' trick, you can even do the whole thing in one line of code; try this on the command line: command /e:2000 /c for %v in (1 2) do prompt set date=$d$_ | find /v "prompt" | call > nul The 'set' command is executed after a pipe symbol has marked the end of the execution of the extra command.com shell, so the variable is set in the parent shell. ----------------------------------------------------- 7. General data structures and programming techniques ----------------------------------------------------- a) No self-respecting programming language can do without stack memory: 1. A simple stack for a few single words: PUSH.BAT @echo off set stack=%1 %stack% POP.BAT @echo off if [%stack%]==[] echo Error: stack empty if [%stack%]==[] goto end if [%1]==[] pop %stack% set popped=%1 set stack=%2 :loop shift if [%2]==[] goto end set stack=%stack% %2 goto loop :end Example: CALL PUSH A CALL PUSH B CALL POP echo %popped% CALL POP echo %popped% 2. An efficient stack for maximum 30 elements: MK-STACK.BAT @echo off md c:\st rem > c:\st\bottom subst e: c:\st RM-STACK.BAT @echo off :loop if exist e:bottom goto empty del e:contents.bat cd e:.. rd e:n goto loop :empty del e:bottom subst e: /d rd c:\st PUSH.BAT @echo off md e:n cd e:n set to-push=%1 :loop shift if [%1]==[] goto endloop set to-push=%to-push% %1 goto loop :endloop echo set popped=%to-push%>e:contents.bat POP.BAT @echo off if exist e:bottom set popped=[bottom-of-stack] if exist e:bottom goto end call e:contents del e:contents.bat cd e:.. rd e:n :end This stack can hold thirty multi-word values; the thirtieth will be overwritten by any subsequent push operation. This limit is imposed by the restrictions on the maximum length of a path name. The implementation assumes that there is no drive e: on your system yet. 3. Even bigger stacks This stack is less efficient, but it can grow until edlin cannot hold the complete file in memory any more. PUSH.BAT @echo off set to-push=%1 :loop shift if not [%1]==[] set to-push=%to-push% %1 if not [%1]==[] goto loop echo set popped=%to-push%>_tmp if exist _stack type _stack>>_tmp if exist _stack del _stack ren _tmp _stack set to-push= POP.BAT @echo off set popped=[bottom-of-stack] if not exist _stack goto end copy _stack _tmp.bat > nul echo 1,1d;e | edlin _stack > nul echo 2,65500d;e | edlin _tmp.bat > nul call _tmp for %%a in (_tmp.bat _tmp.bak _stack.bak) do del %%a :end b) Procedure calls Such a stack could be used to emulate procedures and functions in batch language (push a return address, goto sub, do stuff, pop return address, goto %popped%), but I prefer to use the internal DOS stack: @echo off if "%1=="/A goto A if "%1=="/B goto B rem Main Program call %0 /A call %0 /B goto END :A echo AAA goto END :B echo BBB goto END :END c) Simple repetition For simple loops like 'repeat 10 times', you do not need to use a math functions library, if you do not need to display or use (the decimal representation of) the value of the counter variable: set count= :loop set count=%count%* [...] if not %count%==********** goto loop Anyway, use recursive methods instead of repetition whenever possible, and then minimize (visible) recursion using CAR and CDR. d) Not-so-simple repetition I programmed a 'for' loop in batch language, which I called REPEAT.BAT because FOR is already a reserved word. It uses ADD.BAT and DIFF.BAT from chapter 4.d, but it can of course be made to work with any mathematical function library. The fact that it uses this ADD.BAT and DIFF.BAT makes it perfect for use with small numbers. BASIC SYNTAX: REPEAT x TO/DOWNTO y - IN BATCH FILES: CALL REPEAT x TO/DOWNTO y - WITH REDIRECTIONS: COMMAND /c REPEAT x TO/DOWNTO y The loop variable can be accessed as %_LOOP%. ---------------- REPEAT.BAT @echo off if [%4]==[] goto ERROR set _REPPATH=%PATH% path %2 set _REPDIRECTION=%PATH% set PATH=%_REPPATH% set _REPPATH= if %_REPDIRECTION%==TO set _REPAPPLIC=ADD if %_REPDIRECTION%==DOWNTO set _REPAPPLIC=DIFF set _REPDIRECTION= if [%_REPAPPLIC%]==[] goto ERROR set _FROM=%1 set _TO=%3 set _REPEATED= :BUILDCOMM set _REPEATED=%_REPEATED% %4 shift if not [%4]==[] goto BUILDCOMM echo %_REPEATED% >_repfile.bat set _LOOP=%_FROM% :LOOP call _repfile.bat if %_LOOP%==%_TO% goto end call %_REPAPPLIC% %_LOOP% 1 set _LOOP=%RESULT% goto LOOP :ERROR echo REPEAT.BAT: Expected syntax: "REPEAT TO/DOWNTO " echo WARNING: X and Y should be positive integer values. :end set _REPAPPLIC= set _FROM= set _TO= set _LOOP= set _REPEATED= del _repfile.bat set RESULT= ---------------- Examples: a. On the command line: REPEAT 1 TO 20 echo %_LOOP% REPEAT 1 TO 7 INPUT VALUE%_LOOP% COMMAND /e:5000 /c REPEAT 5 DOWNTO 0 echo %_LOOP% >> file REPEAT 1 TO 3 for %%a in (*.*) do echo %%a b. In batch files: CALL REPEAT 1 TO 20 echo %%_LOOP%% CALL REPEAT 1 TO 7 INPUT VALUE%%_LOOP%% COMMAND /e:5000 /c REPEAT 5 DOWNTO 0 echo %%_LOOP%% >> file CALL REPEAT 1 TO 3 for %%%%a in (*.*) do echo %%%%a Comments: The /e:5000 switch prevents environment space shortage; extra shells always seem to have a minimal environment. Don't forget the double percent signs around _LOOP in batch files; otherwise %_LOOP% will be interpreted too early. Always use double percent signs in FOR loop variables on the command line, and quadruple percent signs in batches ! The batch uses a temporary file, which this version places in the current directory. If you have a dedicated batch or tmp directory (I have both, and I recommend that to everybody), it is better to put the file there, so that REPEAT 1 TO 5 COPY *.* PRN will not print _REPFILE.BAT. You cannot nest repeats (without some extra programming): they would destroy each other's data. It is not necessary to use CALL after REPEAT, so you can use REPEAT 1 TO 3 A.BAT as well as REPEAT 1 TO 3 CALL A.BAT on the command line, and CALL REPEAT 1 TO 3 A.BAT as well as CALL REPEAT 1 TO 3 CALL A.BAT in batch files. ---------------- Each second example showed how to read input into an array of variables (VALUE1, VALUE2,...) with INPUT.BAT (from chapter 1). Using a repeat loop to output these values again is more difficult, because first a part of the name of the variable has to be evaluated, and then the whole variable: %value(%_loop%)% if brackets were allowed. You can provide for two levels of evaluation like this: COMMAND /e:5000 /c REPEAT 1 TO 7 ECHO ECHO %%VALUE%_LOOP%%% >_TMPFILE.BAT FOR %A IN (CALL DEL) DO %A _TMPFILE.BAT Of course, this is how you do it from the command line. Just double every percent sign when you do this in a batch file :-) e) END and ERROR An empty file called END.BAT and a file ERROR.BAT with as its contents echo Error: %1 %2 %3 %4 %5 %6 %7 %8 %9 always come in handy; not to call, but to chain to, i.e. to call without using 'call'. Both end the evaluation of a batch file in a simple and elegant way, saving the programmer from typing a lot of labels and goto's. --------------------------- 8. Generating random values --------------------------- a) Generating a number between 0 and 9 Get the time and use SPLIT.BAT to seperate the hundredth part of a second. That should be random enough. b) Generating a random file name @echo off set RANDTEMP=%TEMP% set TEMP= md random.hlp cd random.hlp echo echo %%1 >auxil.bat dir /w /l /o-e /a-d | find "auxil.bat" >auxil2.bat call auxil2 set TEMP=%RANDTEMP% set RANDTEMP= del auxil?.bat cd .. rd random.hlp I reset the variable TEMP for the duration of the execution of the batch file, because I want the file with the random name to be created in the newly created directory. The sorting order of the DIR command puts "auxil.bat" first on the line that is found by FIND, and the randomly named file that is made by DOS for the DOS pipe (the | command) second. Of course, if you want to create a file with this name in another directory, it is still prudent to check if another file with that name doesn't exist already. Replace echo %%1 with set RANDOM=%%1 for easy manipulation. c) Generating a random letter a-z Generate a number between 1 and 26 (simplest way: generate 2 numbers between 0 and 9, paste them together, and if the value is 0 or greater than 26, try again), and then take the Xth letter of the alphabet. Do not use SPLIT.BAT to split off a letter from a random file name, because you will not get an even distribution of all letters between a and z (certainly not if you would use one of the first letters). ------------------------------ 9. Object Oriented Programming ------------------------------ ... is possible. In this simple batch extension, classes and objects are stored in an elaborate directory structure. The internal state of each object is kept on disk, so there is no need to use seperate databases for permanent data storage. Develop your classes from the command line, and use 'call create', 'call kill' and 'call send' from ordinary batch files to use objects. First I'll demonstrate the functions; the implementation follows: C:\>md aproject C:\>cd aproject C:\APROJECT>init [make necessary subdirectories] C:\APROJECT>class man [define class man] C:\APROJECT>static man name [declare static variable for man] C:\APROJECT>method man set-name [edit method for man] {in the editor type SET NAME=%1 %2 %3 %4 %5} C:\APROJECT>method man get-name {in the editor type SET RESULT=%NAME%} C:\APROJECT>method man prt-name {in the editor type ECHO.%NAME%} C:\APROJECT>create man John [create a test instance of man...] C:\APROJECT>send John set-name John Van Halen [... and use it] C:\APROJECT>create man Fred C:\APROJECT>send Fred set-name Fred Ford C:\APROJECT>send John prt-name John Van Halen [it works !] C:\APROJECT>send Fred prt-name Fred Ford C:\APROJECT>class employee man [define a subclass of man] C:\APROJECT>static employee salary [extra static variable] C:\APROJECT>method employee set-sal [extra methods; it is also possible to override methods of the superclass] ... C:\APROJECT>create employee Bill C:\APROJECT>send Bill set-name Bill Buddy [use an inherited method] ... METHOD.BAT assumes that there is an editor named EDIT, which takes a file name as its command line argument, in a directory of the system path. Class, object, method and (static) variable names are restricted to eight characters. Use tree /f to get a good idea of how the objects and classes are represented on disk. WARNING: This implementation does no error checking, which makes the structure of the programs very clear; but when using them, an infinite loop is only a typo away. A. User functions: INIT.BAT @echo off mkdir classes mkdir objects CLASS.BAT @echo off mkdir classes\%1 mkdir classes\%1\methods mkdir classes\%1\statics if [%2]==[] goto end rem > classes\%1\%2 xcopy classes\%2\statics\*.* classes\%1\statics > nul :: ordinary copy doesn't copy empty files ! :end STATIC.BAT @echo off rem > classes\%1\statics\%2 METHOD.BAT @echo off edit classes\%1\methods\%2.bat CREATE.BAT @echo off set _class=%1 set _object=%2 cd classes\%_class%\statics for %%a in (*) do set %%a=(nil) cd ..\..\.. call save cd classes\%_class%\statics for %%a in (*) do set %%a= cd ..\..\.. set _object= set _class= KILL.BAT @echo off del objects\%1.bat SEND.BAT @echo off if [%_object%]==[] goto no-saving call push %_object% call save :no-saving set _object=%1 set _parms=%2 :loop shift if not [%2]==[] set _parms=%_parms% %2 if not [%3]==[] goto loop call restore call methods %_parms% call save set _object= set _parms= cd classes\%_class%\statics for %%a in (*) do set %%a= cd ..\..\.. set _class= call pop if [%_object%]==[] goto no-restoring call restore :no-restoring B. Internal procedures: SAVE.BAT echo set _class=%_class%>objects\%_object%.bat cd classes\%_class%\statics for %%a in (*.*) do echo call save-sub %%a %%%%a%% >> ..\..\..\temp.bat cd ..\..\.. for %%a in (call del) do %%a temp.bat SAVE-SUB.BAT set _varname=%1 set _to-save=%2 :loop shift if not [%2]==[] set _to-save=%_to-save% %2 if not [%2]==[] goto loop echo set %_varname%=%_to-save%>>objects\%_object%.bat set _to-save= set _varname= METHODS.BAT set _searchclass=%_class% set _method=%1 set _mparms=%2 :parmloop shift if not [%2]==[] set _mparms=%_mparms% %2 if not [%2]==[] goto parmloop :loop if not exist classes\%_searchclass%\methods\%_method%.bat goto next call classes\%_searchclass%\methods\%_method% %_mparms% goto end :next cd classes\%_searchclass% for %%a in (*) do set _searchclass=%%a cd ..\.. goto loop :end set _searchclass= set _method= set _mparms= RESTORE.BAT objects\%_object%.bat PUSH.BAT @echo off set to-push=%1 :loop shift if not [%1]==[] set to-push=%to-push% %1 if not [%1]==[] goto loop echo set _object=%to-push%>_tmp if exist _stack type _stack>>_tmp if exist _stack del _stack ren _tmp _stack set to-push= POP.BAT @echo off set _object= if not exist _stack goto end copy _stack _tmp.bat > nul echo 1,1d;e | edlin _stack > nul echo 2,65500d;e | edlin _tmp.bat > nul call _tmp for %%a in (_tmp.bat _tmp.bak _stack.bak) do del %%a :end I call this OOBL -- Object Oriented Batch Language. ----------------------------------- 10. Manipulating file date and time ----------------------------------- 1. This example shows how you can put the date stamp of a file into a variable; a similar method can be used for the time stamp (and for the file size too, by the way). GETDATE.BAT @echo off if [%1]==[] error Syntax: getdate filename if not exist %1 error No such file md _tmp_ copy %1 _tmp_\getdate2.$$$>nul cd _tmp_ echo set date=%%3>getdate2.bat dir | find "$$$">date-aux.bat call date-aux for %%a in (*.*) do del %%a cd.. rd _tmp_ ERROR.BAT @echo off echo %1 %2 %3 %4 %5 %6 %7 %8 %9 2. 'Touching' a file, i.e. setting date and time to the present, can be done by combining the file with an empty file: TOUCH.BAT @echo off rem >_empty_ copy /b %1+_empty_ %1>nul del _empty_ 3. XCOPY has the /D switch to copy only files created/modified on or after a certain date. You can use XCOPY to introduce such a date constraint for DEL and MOVE operations too. DEL-D.BAT @echo off md _tmp_ xcopy /D:%1 %2 _tmp_>nul cd _tmp_ for %%a in (*.*) do del ..\%%a for %%a in (*.*) do del %%a cd .. rd _tmp_ Move-d is similar. Masochists can try using backup/restore to be able to discriminate not only on date, but on time too. ---------- CONCLUSION ---------- My teacher always told me that a program consists of 3 main parts: input, data processing, and output. This file shows how to get input from a) the console b) disk files c) the clock, how to process a) strings b) numbers, and how to output a) to the screen b) to a file c) to the clock. Therefore I consider it proven that batch language can solve any problem. Note: for a formal proof that batch language can solve any problem: write a NOR.BAT which gets two boolean parameters. It will set the variable NOR to T if both parameters are F, else it will set NOR to F. Also define a procedure to write a variable to a file, and a procedure to read it back. Because you can construct any computation by a combination of NOR gates, the only limit left is your finite disk space. However, if you make economical use of even only 1M of disk space, your computer will happily break down before any computation filled your whole harddisk. QED --------------------------------------------------------------------------- By the same author: The Hidden Strengths of the MS-DOS Batch Language Available at http://student.vub.ac.be/~dvandeun/batvirus.all ---------------------------------------------------------------------------