Make Homepage | Add To Favorites | Print Page | Submit News | Feedback | Contact | 

Your Technical Computer Information Resource!  
     
  Batch Guide  
TACKtech Corp. > Articles > Software > Programming

Batch Guide (TTID #307)

Author: Terry Newton   Views: 32,042 /  Created: January 21, 2004
Article mirrored with permission of author. View Original Article.


This is an attempt at explaining the MSDOS batch language. It is not complete, just the basics. For more information on individual commands refer DOS's built-in HELP command. Some familiarity of DOS is assumed, you should already know what directories are and how to use common commands like CD, MD, DEL, RENAME etc.


Table of Contents

All of the examples assume English DOS 6, some may run under DOS 5 but don't count on it. DOS 6 adds features which are very useful to batch programmers, like CHOICE and a version of FIND that returns an errorlevel.


Elements of the Batch Programming Language

The best way to learn batch is to experiment while referring to the HELP command, it explains everything. Batch programs are essentially sequences of DOS commands that 'type themselves' when the batch file is run.

Batch files consist of control elements, internal DOS commands and external commands or programs. With a little ingenuity one can duplicate most of the functions of a general purpose language, but doing any kind of math is tricky, there are no arithmetic commands at all. For the types of things batch is used for, this is not much of a problem.

Variables

Batch uses the percentage sign (%) to mark variables. The set command is used to assign and clear variables. When DOS reads the batch file, strings like copy %var1% %var2% will be expanded to read say copy source.fil dest.fil (assuming that's what they're set to) before feeding the line to the command interpreter. %% is reduced to % when interpreted, so the at-the-prompt commandfor %a in (1 2 3) do echo %a has to be written in a batch file as for %%a in (1 2 3) do echo %%a.

Variable names have eight significant characters (I think) and are always stored as upper case (except for the 'windir' variable added by Windows). [Testing under Win95 shows that variable names can be longer than 8 characters and all characters are significant, can't say about Dos 6.] In addition to user-set variables, the entire command line is passed to the batch as the read-only variables %0 to %9. %0 is the actual batch name as typed, the rest are parameters. The shift command moves everything down by one allowing parameters past the ninth to be retrieved (this wipes out the %0 parameter so if used for the batch to call itself it must be saved to another variable).

The following batch illustrates the use of variables...

@echo off
set var1=Hello
set var2=World
echo %var1% %var2%!
set var1=
set var2=
 

Explanation - the first line prevents the commands from being displayed, the @ in @echo off keeps that line from displaying. The set command is used to set two variables to "Hello" and "World". Next, echo is used to display the two variables to the screen followed by "!" producing the classic Hello World! display. Finally the two variables are cleared to keep them from needlessly consuming environment space.

Speaking of environment, you should specify more space than stock DOS gives you by using a CONFIG.SYS line similar to...

shell=c:\command.com /e:1024 /p

The number after /e: specifies how much space to set aside, usually 1024 is plenty. You can also execute command /e:5000 /c progname if you need a bunch of space. This can be included inside the batch itself. For quickies, especially if you wish to restore all variables to their original state after testing something, just enter command /e:5000. Type exit to quit the command shell and return to the previous environment.

Redirection and Pipes

Normally, input is taken from the keyboard and output goes to the console. Redirection allows input and output to refer to a file or device instead. Pipes allow the output of one program to be used as input to another program. These symbols only work with programs that read from "standard input" and write to "standard output" but fortunately this includes most DOS commands.

  • The < symbol causes file to be fed to the program as input.
  • The > symbol causes the program's output to be sent to the following file or device.
  • The >> symbol causes the program's output to be appended to the file or device.
  • The | symbol (the pipe) causes the output of the preceding program to be sent to the following program.

The following example shows how to use redirection with the FIND command...

@echo off
find %1<%2>nul
if not errorlevel 1 echo %2 contains %1
 

If saved as say DOESIT.BAT, entering doesit "Word" text.fil will print text.fil contains "Word" if the file TEXT.FIL contains the string "Word" (at least under DOS 6). Since we're not interested in the actual output of the FIND command, it is redirected to the nul device.

Labels, Conditions and Branching

A label is any line that begins with a colon. Comments in batch code can be made by using a double-colon, this is better than using the REM command because labels are processed before redirection symbols. ::<remark> causes no problems but rem <remark> produces errors.

The goto command is used to transfer control to another part of the batch file. For example...

:: test goto's
@echo off
goto start
*** This text can be anything ***
*** because it will never run ***
:start
echo Done.
 

The decision mechanism in batch is the if command. It can be used to compare one string to another, determine if a file exists or determine the errorlevel returned by a previous command. If the evaluation is true, the rest of the command is executed. The not modifier reverses the evaluation results. Examples...

if not %var%.==. goto got_var
if exist MYFILE.DAT goto got_file
if errorlevel 5 echo Errorlevel is equal or greater than 5
if not errorlevel 4 echo Errorlevel is less than 4
 

Notice the periods in the if %var%.==. example, they keep empty variables from producing syntax errors. '%var%. is equal to . only if the string is empty. The way the command is worded it branches to :got_var only if %var% is not empty. Read the wording in the if errorlevel command, any errorlevel equal or greater than 5 is evaluated as true.

Subroutines, CALL and FOR

Most of the elements are in place, but still no subroutines. These can be tackled either by defining a return value and GOTO'ing the routine, which GOTO's to the value of the return variable upon completion. For example...

set return=next1
goto subroutine
:next1
-- bla bla --
goto end
:subroutine
-- bla bla --
goto %return%
:end
 

A more elegant way is to use a branch at the top of the batch that branches to the subroutine. This can be combined with the for and call commands to perform a sequence of steps. Here is an example that prints the names of files in the current directory matching filespecs on the command line...

@echo off
if %1.==Sub. goto %2
for %%a in (%1 %2 %3 %4 %5) do call %0 Sub printname %%a
goto end
:printname
echo %3
:end
 

Launching programs

One of the most useful aspects of batch is its ability to set up custom environments for running programs. Here is a typical made-up scenario: Suppose you have a game that requires you to change to say the C:\GAMES\MUT then run the program MUTANT with the command line parameter /NSB. Rather than typing all that, just put it into a batch and place it in a path directory.

The path - this is a list of directories held in the %PATH% variable that DOS uses to find programs without having to specify the directory it's in. For example, the default path in a simple setup might read (from autoexec.bat)...

path C:\DOS;C:\WINDOWS;C: 

Each directory is separated by a semicolon. When a command is typed first DOS tries the current directory. If not there it begins searching each directory listed in the path line. It is convenient to place batch files in a path directory. If you haven't done so yet, make the directory C:\BATCH (for example) and add it to the path line, as in...

path C:\DOS;C:\WINDOWS;C:\;C:\BATCH
 

Any batch file now placed in C:\BATCH can be run from anywhere without having to change directories. Now, back to the mythical example, let's put all of the required commands into a file called say MUTANT.BAT and place it in C:\BATCH (on the path now)...

@echo off
c:
cd \games\mut
mutant /NSB
 

The first line turns off command echo (contained at the beginning of almost all batch files), the next line ensures the C drive is active, then it changes to the correct directory using the cd command and runs the game with the correct command line parameter.

Here is an example for a virus scanner that does not change the current directory, but instead calls a program in another directory. In this particular example, if the batch is called without parameters defaults are supplied instead...

@echo off
set parm=%1 %2 %3 %4 %5 %6 %7 %8 %9
if %1.==. set parm=. /nomem /sub
c:\scanners\scan\scan.exe %parm%
set parm=
 

The specific commands, directories and parameters will vary depending on the software and where it is installed. The command line is stored in the variable %parm%, if the first parameter %1 is empty, %parm% is set to a useful default, for SCAN this translates to "scan the current and all subdirectories under it, do not scan memory". If this batch was saved as say SCN.BAT into a path directory (got that C:\BATCH directory yet?) any directory branch may be scanned by just typing SCN instead of having to type the directory\command parameters.

Launching Windows 95 programs and associated files...

Windows 95 has a built-in utility for launching Windows programs and files that are associated with Windows programs directly from batch, a very useful addition! Previously this required special utilities to "bridge the gap". The basic syntax is simple...

start /w filename [command line arguments]

The full filename with directory information must be specified if not in the current directory or on the path, as usual. If filename is a long filename or contains spaces, enclose it in quotes. The /w option causes the batch to wait for the file to be closed before continuing with the batch, otherwise batch processing continues in parallel. Other start options include /m to run minimized and /max to run full screen. For example...

start /w /max "C:\Program Files\Myprogram\readme.txt"

Anything that can be double-clicked to run can be launched with start. One very useful application of this is running Windows files from inside a dos-based zip file viewer like AView that allows different viewers to be set up for various extensions. Here is a general purpose viewer for using with dos programs...

:: RUNAFILE.BAT (for example)
@echo off
start /w %1

Windows will figure out what to launched based on the selected file's extension.

Menus for programs

The CHOICE command that comes with DOS 6 is handy for creating custom menus for DOS programs as well as simple Yes/No questions. Here is a simple menu for a few games...

@echo off
:menu
c:
cd\games
cls
echo    GAMES MENU
echo    ==========
echo.
echo    1 - Doom
echo    2 - Raptor
echo    3 - FlightSim
echo    4 - Lotus
echo.
echo    Q - Quit
choice /c:1234Q>nul
if errorlevel 5 goto done
if errorlevel 4 goto lotus
if errorlevel 3 goto flightsim
if errorlevel 2 goto raptor
if errorlevel 1 goto doom
echo CHOICE missing
goto done
 
:doom
cd doom
doom
goto menu
 
:raptor
cd raptor
rap
goto menu
 
:flightsim
d:
cd\fs4
fs4
goto menu
 
:lotus
cd lotusdrv
lotus
goto menu
 
:done
 

The idea is simple, set up initial conditions, in this case change to the C: drive and the \games directory, print a menu using ECHO commands (use echo. to print a blank line) then run the CHOICE command with the parameter /c:1234Q (the valid choices) and redirect it to >nul so it doesn't mess up the screen. After the user makes a decision, the errorlevel returned depends on which one was picked, selecting Raptor for example returns errorlevel 2. They are listed backwards because "if errorlevel 2 do something" really means "if the errorlevel is 2 or greater do something".

ANSI codes can be used to spruce up menus like these, even decent interfaces can be constructed in color using nothing but batch code. Several menu utility programs are also available that are called from batch.

Using menuing systems...

Several freeware, shareware and commercial menuing programs are available that let you create nice-looking menus for your system. They're not as fun as doing it entirely in batch but they can be more productive.

A more elaborate program is something called GO-MENU from an archive called DOSVAR20 from Pinnacle Software. The shareware program was something for manipulating strings (and was never even run) but GO-MENU.EXE is great. Here is its help screen when run with "/?"...

      GO-MENU v1.21A -- Copyright (C) 1991,92 Pinnacle Software (Montreal)
 
       Purpose:   Displays a menu of up to 10 items
       Author:    Tim Campbell (Dial 514-345-9578; GEnie T.CAMPBELL11)
       Format:    GO-MENU [menu-file-name] [nnn] [save-file-name]
       Parms:     Specify nnn to save & read last selection
                  nnn is a save index from 0 to 255
                  save-file-name is the file that holds up to 256 saves
                  Program can continue if create or read of save file fails
       Defaults:  menu-file-name = GO-MENU.MNU
                  save-file-name = GO-MENU.SAV
       Menu File: Line 1    Menu heading
                  Line 2+   Menu lines
                  Note:     Lines starting with ; are ignored
       Returns:   1 - 10 depending on menu selection
                  200    if user pressed Esc in menu
                  250    for help request (this display)
                  255    for program error
 

Here is an example of how I use it, extracted from my master system batch...

@echo off
 
--- stuff ---
 
:menu
c:
cd go-menu user.mnu
if errorlevel 11 goto exit
if errorlevel 10 goto boot1
if errorlevel 9 goto pic
if errorlevel 8 goto tape
if errorlevel 7 goto scanners
if errorlevel 6 goto editfiles
if errorlevel 5 goto sysinfo
if errorlevel 4 goto house
if errorlevel 3 goto setevars
if errorlevel 2 goto prompt
if errorlevel 1 goto windows
goto menu
 
:windows
win
goto menu
 
:prompt
command /e:2048
goto menu
 
--- more menus etc ---
 

This is the text file USER.MNU to define the menu text...

 My Computer System 
    Run Windows
    Run DOS Prompt
    Set Environment
    HouseKeeping Utilities
    System Information
    Files / Environment
    Scanners
    Backup
    PIC Menu
    Reboot
 

I have no idea if GO-MENU is freeware, shareware or isn't even supposed to be used but it does work well. You can probably find it in the SimTel archives. Look for "DOSVAR20.ZIP". Many others are available, enough to cause mental overload actually. Some of them can create very elaborate screens once you master their convulted syntax (I haven't...).


Obtaining User Input

The CHOICE command is fine for asking limited-choice questions but it is no-good for getting real strings like a filename. There are two approaches that can be taken - use an external COM file like SENVAR.COM that directly sets a variable entered by the user. Or you can do it completely in batch. I've seen a couple of variations to this technique, one is contained in Dirk Van Deun's Collection of Batch Techniques file. The one I've been using originated from a computer magazine, I've modified it to put the temp files in C:\DOS to avoid accidentally overwriting 'ENTER.BAT'. You might need to change the directory, or just eliminate it altogether.

@echo off
:: based on batch from PC Magazine June 27, 1995 page 248
:: this version puts temps in C:\DOS dir and shortens var names
:: User input is returned in variable STR
:input
> C:\DOS\en#er.bat fc con nul /lb1 /n|date|find "    1:  "
> C:\DOS\enter.bat echo set str=
>>C:\DOS\enter.bat echo :loop
>>C:\DOS\enter.bat echo if not '%%str%%==' set str=%%str%% %%5
>>C:\DOS\enter.bat echo if '%%str%%==' set str=%%5
>>C:\DOS\enter.bat echo shift
>>C:\DOS\enter.bat echo if not '%%5==' goto loop
call en#er.bat
del C:\DOS\enter.bat
del C:\DOS\en#er.bat
 

Simply "call input.bat" (assuming that's what it's named) and the user string is returned in the %str% variable. If this routine is included in the batch program itself, set a return variable and call it like a subroutine as in...

echo Enter filename...
set return=here
goto input
:here
echo You entered %str%
set return=
goto done
 
:: above input routine
:input
:: ...
goto %return%
 
:done
 

A simpler way to call it is to use a universal branch at the top of the batch...

@echo off
if .%1==.Loop goto %2

then when you want input do a...

echo Enter filename...
call %0 Loop input
:: filename in %str%
 

When using batch input routines, do not enter redirection symbols or other stuff that messes up DOS, especially < > and |.


How to set up SHELL and ANSI

To use batch effectively you should check your CONFIG.SYS file for proper settings. To use color you need something like:

DEVICE=C:\DOS\ANSI.SYS

or if UMBs are available (have a DOS=HIGH,UMB line) use:

DEVICEHIGH=C:\DOS\ANSI.SYS

If this line is not present add it with the other DEVICE's, might help if it's first but that probably doesn't matter. When ANSI.SYS is active it interprets escape codes that set screen colors, move the cursor and all kinds of useful things. Type in HELP ANSI.SYS at a dos prompt for a detailed list of the available commands.

Among the commands is the ability to re-define any key to output an entire sequence of keys and commands upon typing the file. Files containing these sequences are known as Key Macros or ANSI Bombs, depending on the commands they contain. I use key macros to redefine my function keys to useful dos commands. I am very used to hitting control-x instead of typing E X I T Return. Just be aware of the potential problem and don't TYPE strange files. Use something like LIST. Other versions of ANSI are available that do not allow key redefinition if this makes you nervous. ThunderByte's anti-virus driver will also prevent key redefinition after it's called, just define what you want defined before calling TBDRIVER.

To prevent out-of-environment errors when running batch files you should also have a SHELL statement in CONFIG.SYS to specify a larger-than-normal environment, something like:

shell=c:\command.com /e:1024 /p

Some computers have COMMAND.COM only in the DOS directory, if there is no COMMAND.COM in the root change 'c:\command.com' to 'c:\dos\command.com' or wherever it is. If a 'set comspec=c:\command.com' line is present it should match the path\filename given in the shell command.


Processing Lists of Data

A nagging problem in batch programming is how to take a list of items in a file and do anything useful with it. One solution is to use Ed Schwartz' @.COM program or a similar external utility, but there is a way to do it entirely with batch if certain precautions are taken.

The trick is to take the Dos file LOADFIX.COM and copy it to the filename ENTER.COM then use the DATE command to read the data file and output it as a batch. LOADFIX is a seldom-used command that simply loads and runs program above the first 64K of memory. It doesn't seem to have many uses as intended, but since it merely runs its parameters and it's a COM file it can be very useful for data processing from batches since it can simulate a call command without using 'CALL'. How is this useful? The DATE command outputs the string 'Enter new date (mm-dd-yy): ' then waits for input. If the input is not a valid date, it re-displays the prompt and gets more input until either a valid date or an empty line is entered. This has the effect of prepending 'Enter new date (mm-dd-yy): ' in front of each line of an input file. If LOADFIX were renamed to ENTER then each line will attempt to run NEW and whatever was in the original list shows up as parameter %3 and up. The obvious disadvantage of this technique is the input list cannot contain a valid date! Other than that it looks like a very promising technique.

The following demo batch creates a list of subdirectories then processes the list one item at a time...

:: demo - process subdirectories - by Terry Newton
:: overwrites ENTER.COM DIRFILE$ DIRFILE$.BAT NEW.BAT
@echo off
:: check and branch to subroutine
if .%1==.DoDir goto DoDir
:: prepare ENTER.COM file (change c:\dos\ if different)
copy c:\dos\loadfix.com enter.com > nul 
:: prepare a list of fully qualified subdirectories
dir /s /ad /b > dirfile$
:: add a blank line to the end so DATE will exit
echo.>>dirfile$
:: run each line through date to make the list-batch
type dirfile$|date|find "Enter" > dirfile$.bat
:: get rid of the original list
del dirfile$
:: prepare NEW batch to call main program for
:: each item of the list
echo @echo off > new.bat
echo %0 DoDir %%3 >> new.bat
:: now call the list-processing batch
call dirfile$.bat
:: delete the temp files and get out
del dirfile$.bat
del enter.com
del new.bat
goto done
:: This subroutine is called by NEW.BAT for each item
:: in the list plus the blank line at the end
:DoDir
if .%2==. goto done
:: here for each subdirectory with name in %2
echo Looking at subdirectory %2...
::
:done

Be sure that you do not have a NEW.BAT or ENTER.COM program in the current directory, they will be overwritten. In the above example, the DIR /S switch ensures that no item will be a valid date, but if /S were omitted there would be a chance a directory name might just happen to also be a date and cause your system date to be set incorrectly, so be careful when using this technique.

Examples of this technique...

RESETARC.BAT and RESETALL.BAT are a couple of batch files I made for dealing with a tape backup program that doesn't reset the archive attributes on the files it backs up. Yuk! RESETARC resets the archive bits on files in the current directory and all subdirectories below it, RESETALL resets the archive bits on every file on every drive (edit for your system). Although that's an oddball kind of function they make simple templates to use for doing other oddball functions in every directory, just substitute your commands in place of the attrib command.

DIZZY.BAT builds a file containing the contents of all FILE_ID.DIZ files in and under a specified directory. I never knew I had so much stuff!

Windows 95 considerations...

The subroutine demos will work under Windows 95 provided a compatible loadfix.com exists and can be found, and no long directory names are involved. Windows 95 leaves out many old dos commands including loadfix.com, if you haven't already, look for them at Microsoft's site or on the install CD. The main 95 difference for these batches is the /b switch of the DIR command outputs long names, an advantage or disadvantage depending on the application. Most dos commands can handle long filenames (provided they're enclosed by quotes) so if copying or other simple tasks it's no problem. If running an old dos app that does not understand long filenames then about all that can be done is copy the target file to another file first then call the dos app on the shorter name. I have no trick long-to-short name routines but I'm sure it can be done.

The demo code conversions are not too bad, at least for the first six spaces. The main difference is as many parms possible are passed in the code that calls the subroutine, and the subroutine reconstructs the parms into a variable containing the directory name.

:: demo - process subdirectories - by Terry Newton
:: overwrites ENTER.COM DIRFILE$ DIRFILE$.BAT NEW.BAT
:: windows 95 compatible
@echo off
:: check and branch to subroutine
if .%1==.DoDir goto DoDir
:: prepare ENTER.COM file (looks in 2 places, hardcode if different)
if exist c:\dos\loadfix.com copy c:\dos\loadfix.com enter.com>nul
if exist c:\windows\command\loadfix.com copy c:\windows\command\loadfix.com enter.com>nul
:: prepare a list of fully qualified subdirectories
dir /s /ad /b > dirfile$
:: add a blank line to the end so DATE will exit
echo.>>dirfile$
:: run each line through date to make the list-batch
type dirfile$|date|find "Enter" > dirfile$.bat
:: get rid of the original list
del dirfile$
:: prepare NEW batch to call main program for
:: each item of the list
echo @echo off > new.bat
echo %0 DoDir %%3 %%4 %%5 %%6 %%7 %%8 %%9>> new.bat
:: now call the list-processing batch
call dirfile$.bat
:: delete the temp files and get out
del dirfile$.bat
del enter.com
del new.bat
goto done
:: This subroutine is called by NEW.BAT for each item
:: in the list plus the blank line at the end
:DoDir
if .%2==. goto done
:: here for each subdirectory
set dirname=
:DoDirparms
set dirname=%dirname%%2
if .%3==. goto DoDirgotit
set dirname=%dirname% % nullmarker%
shift
goto DoDirparms
:DoDirgotit
echo Looking at subdirectory %dirname%...
::
:done

List processing without loadfix...

Having to locate a possibly non-existent loadfix.com is a hassle to say the least, especially these days. If the list isn't very large and processing time isn't critical, there is another way to work through a list using only common dos commands. The technique is to create a list ending with a blank line, run through date to create a temp batch, so far just like the previous method but instead of creating enter.com, create an enter.bat that sets the variable. When the temp batch runs, only the first enter line will execute, so after it returns use the find /v command to remove the last item processed, then loop until the empty line is encountered. Each item takes a bit longer to process because of the removal step, but in applications where an appreciable time is spent on each item, this doesn't matter much, better to have the simplicity.

A demo that collects and appends all .DIZ files in and below a directory into a single file...

:: find all .DIZ files in and below current directory
:: and append them into a single DIZFILES.TXT file.
:: coded by Terry Newton
@echo off
:: branch if need be
if .%1==.DoFile goto dofile
:: empty the output file
rem > dizfiles.txt
:: create a list of files (/-p in case pause in dircmd)
dir /s /b /-p *.DIZ > lstfile$
:: end with blank line
echo.>> lstfile$
:: run through date to make tempbat
type lstfile$ | date | find "Enter" > lstfile$.bat
:: create an enter.bat to be run by listfile$.bat
echo %0 DoFile %%4 %%5 %%6 %%7 %%8 %%9 > enter.bat
:loop
:: run temp batch
lstfile$.bat
:: ends up here with filename at parm2 and up
:dofile
:: check for exit condition
if .%2==. goto done
:: derive filename from parms
:: (only needed for long names, otherwise refer to %2)
set fname=
:doparms
set fname=%fname%%2
if .%3==. goto gotparms
set fname=%fname% % nullmarker%
shift
goto doparms
:gotparms
:: give operater something to look at
echo Working on %fname%...
:: add filename and file to output file
>> dizfiles.txt echo -------- %fname% --------
>> dizfiles.txt type "%fname%"
>> dizfiles.txt echo.
>> dizfiles.txt echo.
:: done with that file, so remove entry from lstfile$.bat
type lstfile$.bat | find /v "%fname%" > lstfile$.bat
:: and loop
goto loop
:done
:: finished, remove temp files
del enter.bat
del lstfile$.bat
del lstfile$

This demo incorporates Windows-95 compatibility fixes, if your application does not have filenames with spaces in them several lines of code can be saved, so if using Windows 3.1 or Dos, make the "dofile" part like this instead...

:dofile
:: check for exit condition
if .%2==. goto done
:: give operater something to look at
echo Working on %2...
:: add filename and file to output file
>> dizfiles.txt echo -------- %2 --------
>> dizfiles.txt type %2
>> dizfiles.txt echo.
>> dizfiles.txt echo.
:: done with that file, so remove entry from lstfile$.bat
type lstfile$.bat | find /v "%2" > lstfile$.bat
:: and loop
goto loop

Not totally sure (never tried it..) but the quotes around possibly long filenames will probably confuse older doses, something else to watch out for if making batch code that must run on multiple dos versions. One thing for sure, without the quotes it fails under 95 with a "too many parameters" error the first time a space is encountered in a filename.

In all of these demos, lists of filenames are used but the list does not have to be that, it can be a list of anything so long as only the last line is empty and no redirection characters (< > | ) or separators (; , = ) involved. Redirection not enclosed by quotes create errors and unpredictibly-named files, separators are converted to spaces. However processing lists of filenames does seem to be the obvious use.


Creating and running other programs from batch

Often times batch simply is not powerful enough or too slow to do the things that need to be done, this is when other program interpreters like QBasic and even debug can come in very handy. Using debug and assembly code in general is beyond the scope of this text and me also, I learn a few commands and work with them. QBasic isn't hard to learn though, and you do not need to learn everything to be able to use it from batch files. Reading and writing files, doing some mathematical computations (but read on), fancy screen displays and interfaces and many other cool tricks can be accomplished using batch code that writes and runs temporary basic programs. If you look around my batch site you'll find many examples.

User input? The all-batch solution is ok but here's another way...

@echo off
echo>$inp$.bas bad$="<|>=,;":on error goto done
echo>>$inp$.bas ? "Enter something: ";
echo>>$inp$.bas line input a$:if a$="" goto wrbat
echo>>$inp$.bas for i=1 to len(bad$)
echo>>$inp$.bas if not instr(a$,mid$(bad$,i,1))=0 then a$="(error)"
echo>>$inp$.bas next i
echo>>$inp$.bas wrbat:open "$inp$.bat" for output as #1
echo>>$inp$.bas ? #1,"set input=";a$
echo>>$inp$.bas done:close #1:system
qbasic /run $inp$.bas
call $inp$.bat
del $inp$.ba?
echo You entered %input%

How it works... the batch code writes out a temporary basic program and runs it, the basic code prints a prompt, collects a line of user input, checks it for characters that can cause errors and replaces the input line with "(error)" if present, then writes a temporary batch that sets a variable to the input line. After the basic completes, the batch calls the temp batch to set the input variable, deletes the temp files and displays the input variable.

Ok a couple of further explorations... first off if you haven't figured it out, dos doesn't care much where redirection occurs in a command, it makes things look neater if the redirection is immediately after the echo so the remainder resembles what actually gets written, handy anytime creating files with batch but leave "echo." as "echo.>>file". Writing out strings that contain redirection symbols is not a problem, the quotes hide them from dos. The main trick is avoiding errors from < and > characters in math expressions. Fortunately just about any math equation using such characters can be rewritten with not and sgn to avoid the problem. For example, if a > b then... won't work, instead use something like if sgn(a-b)=1 then... In the input example it wasn't if instr(...)<>0 but rather if not instr(...)=0.

Some more basic to batch conversions...

if a < 5 then...          if sgn(5 - a) = 1 then...
if a <> 5 then...         if not a = 5 then...
if a <= b then...         if not sgn(a - b) = 1 then...

When writing any file from batch (this applies to batch, qbasic or anything) the cardinal rules are %% becomes %, variables are substituted with their contents, and the characters < > and | must be enclosed in quotes or not used at all.

Here is another useful newly hacked-out example of using qbasic from batch...

:: CHSTRING.BAT - changes all occurences of one string to
:: another, if newname is not specified overwrites filename
:: under win 95 strings can contain spaces and separators
:: requires at least dos 6 and qbasic.exe
@echo off
:: verify that it has enough parms
if .%3==. echo CHSTRING "matchstring" "newstring" filename [newname]
if .%3==. goto end
:: verify that file is present
if exist %3 goto fileok
echo file %3 not found
goto end
:fileok
:: verify quotes
echo %1|find """">nul
if errorlevel 1 echo no quotes around string(s)
if errorlevel 1 goto end
echo %2|find """">nul
if errorlevel 1 echo no quotes around string(s)
if errorlevel 1 goto end
:: create a custom qbasic program
echo>chstr$$.bas :on error goto done
echo>>chstr$$.bas ls1=len(%1):open "%3" for input as #1
echo>>chstr$$.bas open "newfl$$$" for output as #2
echo>>chstr$$.bas doit:line input #1,a$:if a$="" goto wline
echo>>chstr$$.bas chline:la=len(a$):if sgn(ls1-la)=1 goto wline
echo>>chstr$$.bas b$="":c$="":p=instr(a$,%1):if p=0 goto wline
echo>>chstr$$.bas if not p=1 then b$=left$(a$,p-1)
echo>>chstr$$.bas if not p+ls1=la then c$=right$(a$,la-p-ls1+1)
echo>>chstr$$.bas a$=b$+%2+c$:goto chline
echo>>chstr$$.bas wline:print #2,a$:goto doit
echo>>chstr$$.bas done:close #1:close #2:system
:: run and delete it
qbasic /run chstr$$.bas
del chstr$$.bas
:: move output to appropriate filename
if not .%4==. move newfl$$$ %4>nul
if .%4==. move newfl$$$ %3>nul
:end

How it works... (I hate this part:) The initial batch lines verify the parameters for correctness and if not display various messages. Once it's satisfied with the parms it creates a temporary qbasic program that changes all occurences of "match string" to "new string", writing the results to a temp file. After running and deleting the qbasic program, it checks to see if an output filename was specified, if so it copies the temp output file to that otherwise overwrites the specified file with the changes.

The strings must be enclosed in quotations, under Windows 95 they can contain spaces and separators like commas, under Dos 6 the strings must be all one word without any strange characters, sorry... another Win95 difference, but I like this one. A related feature is specifying long filenames using quotes, many lfn-unaware batches still work if the filenames are quoted to keep the parameters intact.

This is a batch guide, not a qbasic guide so I won't explain the basic part much, qbasic has an extensive on-line help facility that explains what every command does, rather I'll point out conversions needed to make it into a batch. In line 1 of the qbasic code, the ":" before "on error" is there to keep Windows 95 from interpreting the statement as "echo on" and writing "echo is on" to the file instead of the errortrap. Dos 6 doesn't have this "feature" but the fix is easy enough. In line 5, the natural form of the comparison would be "if ls1 < la" but that would create an error situation, so the equivalent comparison "if sgn(ls1-la)=1" is used instead. Line 7.. "if not p=1" instead of "if p > 1" (not equivalent but p is never <0 so it works). Line 8.. "if not p+ls1=la" instead of "if p+ls1 < la" (again not equivalent but...).

Intermixing Perl and Batch...

The following was tested using the Perl for Win32 interpreter, it should work with other Perl 5 complient interpreters if there are any others. To be useful, one usually has to make a batch file for each perl script to run, but the language definition allows for extra code before and after the script code. This makes it very easy to make a perl-to-batch "compiler" that encapsulates the script and allows it to run like a stand-alone program...

:: 'compiles' perl scripts into batch files
:: by Terry Newton, Feb 98
@echo off
if .%2==. echo perl2bat perlfile batfile
if .%2==. goto end
if exist %1 goto compile
echo can't find input file %1
goto end
:compile
echo compiling %1 to %2...
echo>%2 @echo off
echo>>%2 set $bat=%%0.bat
echo>>%2 if not exist %%$bat%% set $bat=%%0  
echo>>%2 perl -x %%$bat%% %%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9
echo>>%2 set $bat=
echo>>%2 goto p2b_end
type %1>>%2
echo>>%2 __END__
echo>>%2 :p2b_end
:end

Up to nine command-line parameters are passed to the script, you can modify the output to include other batch processing that may be needed before and after running the script. Since the batch execution thread never touches the script code there are no limitations on what it contains, so long as it doesn't have a line that begins with ":p2b_end" and I don't think that'll be a problem (if it is edit the two occurences to something else).

How it works... the batch must write out other batch instructions that determine the filename (varies if ran from a command line or from windows) then call the perl interpreter (modify if not "on the path") to run itself using a switch that tells the interpreter to ignore any junk at the beginning. No redirection is needed, so none of those hassles, the only conversion is all the "%" characters in the output are doubled. When written out the loader looks like...

@echo off
set $bat=%0.bat
if not exist %$bat% set $bat=%0  
perl -x %$bat% %1 %2 %3 %4 %5 %6 %7 %8 %9
set $bat=
goto p2b_end
[simple perl script]
#!perl
print "Hello World!\n";
__END__
:p2b_end

Neat trick. However it doesn't run from a path directory, for that more batch processing would be necessary. If it becomes too difficult then it's easier just to run with a separate batch...

@echo off
perl c:\whatever\perlscr.pl %1 %2 %3 %4 %5 %6 %7 %8 %9

... and be done with it, even if it is yet another file.

Making decisions based on the output of a program

Often it is necessary to test the output of a program that does not return an errorlevel. The trick is to use the FIND command to search for a specific string in the program's output.

The general method for Dos 6 and above is...

program | find "string" > nul
if errorlevel 1 goto notfound
rem string was found
goto endfind
:notfound
rem string was not found
:endfind

Note - if the letter case of "string" is not known, use find /i "string" instead of find "string".

For checking for more than one string, send the program's output to a temporary file and test that...

program > tempfile
find "string1" < tempfile > nul
if not errorlevel 1 goto 1found
find "string2" < tempfile > nul
if not errorlevel 1 goto 2found
rem no strings found
goto endfind
:1found
rem string1 found
goto endfind
:2found
rem string2 found
:endfind
del tempfile

Dos 5 makes it more complicated, because FIND didn't return an errorlevel until 6. If you're not sure of the target OS a universal approach takes advantage of COPY's refusal to copy empty files...

program | find "string" > temp1
copy temp1 temp2 > nul
del temp1
if not exist temp2 goto notfound
del temp2
rem string found
goto endfind
:notfound
rem string not found
:endfind

Any command or utility that writes to standard out can be used with these methods. A typical example is determining if two files are identical (assumes English, dos 6 or better)...

fc file1 file2 | find "FC: no differences" > nul
if errorlevel 1 goto notequal
rem files are identical
goto donefc
:notequal
rem files are different
:donefc

I've used common names for the temporary files for clarity, but keep in mind that the user might have files with those names which might be overwritten. Better to use cryptic names like [$tmp1$].


Copyright 1998 Terry Newton


Your Name:


Your E-Mail: (required)


Friend's E-Mail: (required)




View Our World Wide Web Customer Privacy Policy
  Featured Articles  
  Quick Links  
  Top Affiliates  
.......