v Main
-> Latest
-> About
-> Photos
-> WebCam
-> Words
-> Thoughts
-> Work
-> RISC OS
-> Faith
-> Personality
-> Science
-> Marriage
-v Batch Files
-> Taglines
-> Reading
-> Graphics
-> RISC OS
-> Music
-> Links
-> Credits


> Site Map
-

 Thoughts 


Batch Files

This isn't actually a thought. Well, not as such.

This was originally some documentation that I wrote at work to assist others in making sense of batch files I'd written. It occured to me that this was fairly generic stuff, and it might make some sense to a wider audience. Having decided that, I couldn't think where it would go in the web site structure, so it has ended up here in the decidedly miscellaneous section.

Note that this page has a higher minimum width that the rest of the site to ensure that some of the lines don't wrap around.

If you're after a free download that will make WinAMP playlists from a directory structure of MP3s, you'll find it at the end.


Overview of Modular Batch files:

Contents:

* Introduction

* Structure

* Basic Structure.
* Calling a function.
* Structure of a callable function.
* Using parameters.
* Advanced parameters.

* Miscellaneous

* Displaying blank lines.
* Checking variables that do not exist.
* Checking multiple variables simultaneously.
* Differences between NT and DOS batch files.
* Use of 'FIND'.
* Reading from files.

* Optimisation

* General Optimisation.
* Use of 'FOR' loops.
* How Goto works.

* Troubleshooting

* General Troubleshooting.
* Logging.
* Display redirection.
* Debugging techniques.
* Trailing spaces.
* Total Paranoia.

* Examples

* MP3 Playlist Maker.


top Contents.

Introduction:

If you have been involved in looking after large numbers (or small numbers for that matter) of PCs, you'll probably have run into batch files at some point. If you were involved in PC administration prior to Windows 95, you almost certainly know one end of autoexec.bat from the other, and may have tinkered further.

As scripting languages go, it's pretty basic stuff, but it gets used quite a bit because it's built in to the operating system. I've ended up (without really thinking about it) writing some ridiculously large batch files partly because after a few moments thought I could see a way of getting a batch file to do what I wanted to do, but mainly because I wanted to do it on a system where very little could be taken as given. I've needed batch files that could run under DOS 6 or Win2000, and do similar but different things on both. I've needed things to run under Windows NT 4.0 prior to the service packs going on, and with just the files present that are included as part of the OS.

Having got the hang of them, they tend to end up being used by default, purely because they are a known environment.

There's also a certain sick sense of achievement in getting a batch file to do all the weird and wonderful things you can get them to do that they weren't designed to do . . .

This document does not tell you anything about how to get started with batch files. It assumes you already know vaguely what you're doing, and suggests a few things that certainly didn't occur to me straight away. It also (I think) leads to batch files that are easier to maintain, and easier for other people to read and understand what they do.

I've termed the type of batch file that this results in a "modular batch file" because it essentially treats it slightly more like a programming language rather than a script. It allows you to call functions, and have libraries of functions, possibly in a separate file or files.


top Contents.

Structure:

Basic Structure:

The way it works is to break the batch file down into modular chunks which can be 'called', and a central section which calls them. The basic structure of this is:

The file must begin as follows:

@echo off
if not %1.==. goto %1

The '@echo off' has to be the very first line, but you can add comments in anywhere after that point.

A % sign with a numeric suffix refers to the command line parameters that the batch file has recieved. So, %1 is the first parameter. What this code means therefore is that if the batch file has received a parameter, it is to treat it as a section label, and go and run that piece of batch file. This little bit of code at the start of the file can be considered as a redirector. Note: If the file is run with no parameters, no redirection takes place, and the code immediately after the redirector is run.

The rest of the batch file is then divided into two logical sections, which I will be referring to as 'Main' and 'Callable functions'. For clarity, these should be separated within the file by marker sections as shown:

@echo off
if not %1.==. goto %1
REM ^ Redirector

REM Main section
. . .
main section of code
. . .
REM End of main section

REM Separation:
GOTO STOP

REM Callable functions
. . .
callable functions
. . .
REM End of callable functions

:stop

The separation marker is required so that when the file is first called with no parameters, and the main section is run, once we get to the end of the main section we exit cleanly without inadvertently running bits from the callable functions section as well.


top Contents.

Calling a function:

Functions can be called either from the main section, or from within another function (i.e. functions can call each other, or even call themselves recursively). The syntax is the same in all cases:

REM some code to do something
 . . .
call %0 somefunction
 . . .
REM some more code

In the basic structure section, '%1' was described as the first parameter. '%0' therefore is the zeroth parameter, or the command line that was used to launch the batch file in the first place. This has some important consequences. If I type the following from a DOS prompt:

n:\batch\testfile.bat

then %0 is 'n:\batch\testfile.bat'. However, if I type:

testfile

then %0 is just 'testfile', and does not include the full path to the file. If testfile.bat makes changes to the current drive and directory, and n:\batch is not in the path, then it may crash with a file not found error. When writing a modular batch file for use from a login script, or other automated process, it is best to ensure that it receives a full path to the batch file when you start it. If it is to be run by a user, try to ensure that it makes no changes to the current drive and directory.


top Contents.

Structure of a callable function:

When the line in the above example is run, the batch file will receive a value in %1 of 'somefunction', and will then try to go to that section label. Somewhere in the callable function section, therefore we need that section as follows:

:somefunction
REM This is somefunction.
rem code to do something goes here
goto stop

While 'somefunction' is a completely self contained block, and can go anywhere within the file, it is best to keep all the callable routines in one place where they are easy to find. It may also be worth while grouping them by type.

It is important to note that all functions must finish with a 'goto stop'. The call command starts a new copy of the batch file in order to execute the function, and will not return to the original copy until the function ends. The function can do almost anything it likes in the meantime, but must end with a goto stop.

If we look at the sequence of events for a simple function, we get:

Batch file starts, no redirection, main section is run
Main section calls a function
   A second copy of the batch file is loaded and run
   Redirection takes place to the function name
   The function is run, and ends with 'goto stop'
   The second copy of the batch file quits
Main section continues from the line immediately after the call
Main section finishes with 'goto stop'
Batch file quits

Note: Although multiple copies of the batch file are being run, the environment variables are common to all copies. It is not starting a new copy of the command interpreter each time.

While you can do just about anything within a callable function, there are a few things you must not do:

* Never link back to anything in the Main section.
* Never link back to the routine that made the call to the function.
* Don't link from a function back to the same function name unless you know what you are doing.

The reason for not doing any of the above is the same in all cases - you run the risk of ending up in an endless loop.

You can of course call other functions from within a function, but you can also do a direct 'goto' to a section name in another function. As long as the whole thing ends in a 'goto stop' it will all sort itself out. Note that if a function does a 'call' to another function, a further copy of the batch file is loaded. You can (I think) go up to 256 levels deep in doing this, but getting your head around it may prove difficult. The MP3 playlist maker goes three levels deep.

The idea when writing code for a callable function is to make it into a logical black box. It should set environment variables (or return errorlevels, or modify files, or whatever it is that the function is supposed to do) in a consistent manner, and do nothing else to the state of the machine. Changing the current drive or directory in a function is a good way of causing chaos in the code that called the function. Similarly, altering common variables or clearing them can cause problems unless the function is specifically written to do that. Note that on NT you can use the 'pushd' and 'popd' commands to temporarily change to a different drive/directory, and then change back without actually knowing where you were in the first place. If your script needs to run on DOS as well, it will either need to ensure the existence of third party utilities to provide the same capabilities, or do without this.

Where functions require further parameters, bear in mind that the useable parameters start at %2, not at %1 as is normally the case. %1 will just contain the function name.


top Contents.

Using parameters:

The methods used to make a modular batch file work fine for batch files which only have a single purpose in life. If your batch file determine all the options etc either from detection of the existing machine state, or from menu systems within the file itself, or from a config file, then you are fine. However, suppose you need to be able to take custom parameters on the command line. Using the system described above, there is a problem in that the first parameter will be assumed to be a section label within the file. To work around this, a flag is set to indicate that the batch file has initialised and is running. This flag can then be used as a check as to whether to call a function or carry on through to the main section. A typical example:

@echo off
if %running%.==yes. if not %1.==. goto %1
REM Main section
set running=yes
echo example.bat parameters: %1 %2 %3 %4
 . . .
REM end of Main section.
set running=
goto stop

 . . .
REM callable functions etc
 . . .
:stop

When the file is first run, the running flag isn't set, so redirection does not take place, and the main section receives all the parameters it is expecting. When code in the Main section calls a function, the running flag has been set, so redirection takes place to the function.

So far, so good, but there is one point to bear in mind - If you exit the batch file in a non-standard manner (i.e. CTRL+C, or a crash), or there are multiple exit points from the Main section (i.e. more than one place with 'goto stop'), then the running flag may still be set. If you then try to run the file subsequently, it will assume the parameters you have given it are section labels, and will probably crash with a 'label not found' error. You'll need to do a 'set running=' from the command line to clear this.


top Contents.

Advanced parameters:

One thing that can be tricky is how to pass a parameter so that the batch file gets it in a single variable, when the contents may contain spaces. If you do something like;

@echo off
if %running%.==yes. if not %1.==. goto %1
set dir=%1

Then if you call this with a parameter of "program files", you will end up with just "program" in the dir variable. It doesn't matter what you do with quote marks, it will split at the first space. In th above example, %2 would have been "files", but if you just set dir to be %1 %2 %3 %4 %5 %6 %7 %8 %9 (to allow for up to 8 spaces in the variable), you will end up with trailing spaces in the result.

There is a way around this - it's messy, and it will result in multiple consecutive spaces being concatenated to one space, and trailing spaces will be removed.

@echo off
if %running%.==yes. if not %1.==. goto %1
set running=yes
call %0 getdir %1 %2 %3 %4 %6 %6 %7 %8
echo %dir%
set running=
goto stop

:getdir
goto scrunch

:getdir_
set dir=%fix%
set fix=
goto stop

:scrunch
set fix=%2 %3 %4 %5 %6 %7 %8 %9
if %9.==. set fix=%2 %3 %4 %5 %6 %7 %8
if %8.==. set fix=%2 %3 %4 %5 %6 %7
if %7.==. set fix=%2 %3 %4 %5 %6
if %6.==. set fix=%2 %3 %4 %5
if %5.==. set fix=%2 %3 %4
if %4.==. set fix=%2 %3
if %3.==. set fix=%2
goto %1_

:stop

This works by only appending the parameters that are supplied, thereby avoiding appending trailing spaces.


top Contents.

Miscellaneous:

Displaying blank lines:

Note: This describes how to display blank lines. Blank looks are easily obtainable without special effort.

Possibly a basic one, but worth covering. The echo command is used to send output to the screen (or other dos device as required), but echo with no parameters just reports the current status of echo (i.e. whether to display the lines of the batch file as it is running, or just to display the output of those lines). So:

@echo off
echo
echo Hello world
echo

Will produce the following output on screen:

Echo is OFF
Hello world
Echo is OFF

To get around this, you can use a full stop immediately after the echo command to produce a blank line. There must not be a space between the echo and the full stop otherwise you'll just display a full stop on screen. So:

@echo off
echo.
echo Hello world
echo.

Will now work properly.

On which subject, it is worth while establishing a consistent scheme as to whether functions within the file that produce screen output put blank lines before their output, or after it.


top Contents.

Using variables that do not exist:

No, we're not getting into metaphysical territory here - the problem is a simple one: How do you check to see if a variable has a particular value if you are not certain that the variable has even been created at all? If you do something of the form:

If %fred%==yes echo fred

You will get a syntax error if the fred variable has not been set. This is because you end up with nothing on the left hand side of the '=='. The trick is to ensure that you always have something on both sides of the equals. The following;

If %fred%.==yes. echo fred

Will always be a valid test. Note the '.'s at the end of each expression - this means that there will always be something on each side, but the outcome of the check is not affected. You can use any character, but I've used a '.' throughout from force of habit and because it doesn't affect the readability too much.


top Contents.

Checking for multiple variables:

Possibly an obvious one, but it took me a while to cotton on to this, and it makes things much more compact . . .

If you want to perform a particular action if three (or two, or more) particular variables are set to a specific value, then you can lump it all together in one if statement:

If %var1%%var2%%var3%.==yesnomaybe. goto doit

Similarly, you can append multiple IF commands together should the need arise:

if exist c:\1.txt if not exist c:\2.txt if exist c:\3.txt echo test.
In other words, when parsing an IF command, if it matches, then everything after the IF command is treated as a new command in its own right, and the parser starts again with this new command. The only restriction is on the line length.
top Contents.

Differences between NT and DOS batch files:

The two batch formats are generally the same, with the NT batch files having a few handy extra additions like 'start', 'pushdir', 'popdir' etc. Windows 2000 is the same as NT as far as I can tell. There are however some areas where the NT and DOS actually differ:

To check for the existence of c:\dirname -

Under DOS:

if exist c:\dirname\nul echo it's there

Under NT:

if exist c:\dirname echo it's there

i.e. under NT it works as you might expect, but under DOS you have to stick a weird '\nul' on the directory name. Using the '\nul' fails on NT, so batch files which are common to both need to use something of the form:

if %ver%==4 set dirfix=
if not %ver%==4 set dirfix=\nul
if exist c:\dirname%dirfix% echo it's there

The above will now work on both OSs, but is a little clumsy, so is only recommended for use in areas of a file which are run by both OSs rather than using it all the time.

A further, very important difference between NT and DOS is in terms of the case sensitivity of environment variable checking. The following code:

set ntbuild=Staff
if exist c:\alt255\server.dat set ntbuild=TC
 . . .
if %ntbuild%.==staff. echo Staff machine
if %ntbuild%.==tc. echo Teaching Centre machine

Will work ok on DOS, as it doesn't care about the case of the strings. On NT though it will not echo anything as neither

Staff.==staff.

or

TC.==tc.

will be counted as matching.

To work around this, I tend to leave all strings in lower case unless they are to be used as screen output, where I use whatever case is appropriate, and make darn sure I've been consistent. Alternatively, you can use IF /I on NT, but that won't work under DOS.

The GOTO command is also implemented slightly differntly under DOS and NT. See the section on Goto for more information.

A final difference between NT and DOS is that NT does not support the CTTY command. This means that you can only affect redirection on a 'per line' basis.:

Under DOS, you could do:

ctty nul:
echo you can't see this, can you?
 . . .
echo or this for that matter.
ctty con:

Whereas under NT, you would need to do:

echo you can't see this, can you? >nul
 . . .
echo or this for that matter. >nul

Why you would want to do this is another question.


top Contents.

Use of 'FIND':

The find command is extremely handy, and is used extensively in many batch files. It accepts piped input, a search string, and produces as output just those lines in the file which contain a match for the search string (with options for case sensitivity, returning just the lines which didn't match, and others). On DOS6, and on NT, it returns an errorlevel 1 if the string wasn't found anywhere in the file. Under DOS5 however, no errorlevels are returned.

There is no way to get the later versions of FIND to run under DOS5, so the easiest thing to do is to just use code which uses the output, and doesn't rely on the errorlevel. If you absolutely have to have the errorlevel type behaviour, then use the following as a last resort. Note that although this works on DOS5 and DOS6, it fails under NT, so some further version checking would be needed to produce a global FIND routine:

type filename.dat |find "search string" /i >%temp%\tmpfind1.dat
copy %temp%\tmpfind1.dat %temp%\tmpfind2.dat >nul
if exist %temp%\tmpfind2.dat echo Search string found.
if not exist %temp%\tmpfind2.dat echo Search string not found.
del %temp%\tmpfind?.dat

Note: You only need to do this if you actually need the errorlevel type behavior. If you're just filtering spurious information from a file or using setit.bat, then you can use 'find' on dos5 with no problems.

[Further Note for those with serious anorak tendencies who want to know why this works:
Redirected output under DOS always creates the output file. So, if the string isn't found, tmpfind1.dat will exist, but be 0 bytes long. If the string is found, tmpfind1.dat will contain real data, and be larger than 0 bytes. Trying to copy a 0 byte file under DOS (but not NT) will not create the destination file (no error is reported). The existence or otherwise of tmpfind2.dat can then be taken to signify whether the search string was found in the file].


top Contents.

Reading from files:

This is a fairly common requirement. There is some information contained in a file, and you would like it in an environment variable.

Given that the batch language has no built-in ability to read from the contents of a file, you have to cheat. To do this you require two batch files in a known location that you can copy into temporary files. I generally call these setany.bat, and setit.bat;

@echo off
REM SetAny.bat
set %1=

and

@echo off
REM SetIt.bat
set

These may look fairly standard, but there is one other quirk about these files - you need to create them with an editor that does not put a carriage return at the end of the file. PFE and NoteTab are two editors I've used to create such files.

This lack of a carriage return at the end means that when you copy one of these to a temporary file, and append data to it, the data is appended to the set command in the file rather than appearing on the line below. You can then call the temporary file to read the line.

SetAny.bat is useful in situations where you want to read from files of the form;

value1
value2
value3

(Frequently in my case, this would be a list of usernames or contexts or similar.)

SetIt.bat is useful in situations where you want to read from files of the form;

variable1=value1
variable2=value2
variable3=value3

(e.g. .ini files or other configuration files etc)

To read a single value from a setit type file, you might do the following;

copy setit.bat settmp.bat
type config.ini | find "variable" /i >>settmp.bat
call settmp.bat
del settmp.bat
echo Variable now contains value: %variable%

Setany can be harder to use, because you can't search the file for the variable name, and don't necessarily know the value in advance. You therefore have to do something like;

copy setany.bat settmp1.bat
type userlist.txt >> settmp1.bat
echo @echo off>settmp2.bat
type settmp1.bat | find "set %1=" /i >>settmp2.bat
call settmp2.bat variable
del settmp2.bat
del settmp1.bat
echo Variable now contains: %variable%

This works by appending the file to a copy of setany, then retrieving just the line that contains the set command (the other lines in userlist.txt will have also been appended, and we can't call the batch file while they are still there). For tidyness, we create a temporary file with an @echo off to receive this line which we can then call safely.

This is all very well for reading the first line in the file. If we repeat the operation, we'll just get the first line again. If we want to read each line in turn, we have to be a bit more cunning.

copy userlist.txt tmplist1.txt
:loop
copy setany.bat settmp1.bat
type tmplist1.txt >> settmp1.bat
echo @echo off>settmp2.bat
set variable=
type settmp1.bat | find "set %1=" /i >>settmp2.bat
call settmp2.bat variable
if %variable%.==. goto endloop
del settmp2.bat
del settmp1.bat
echo Variable now contains: %variable%
REM presumably do some further processing of %variable% here
REM . . .
type tmplist1.txt | find "%variable%" /v >tmplist2.txt
del tmplist1.txt
ren tmplist2.txt tmplist1.txt
goto loop
:endloop
echo All lines read.

Still with me?

This works by reading the first line in the file, and then processing the file to remove that line. Then repeat until we reach a blank line.

Note that this will skip lines if a line that is read early on is contained in a later line;

fred
jim
frank
fredjim
arthur

When using the above technique to read a file like the one above, it will miss the "fredjim" line, because the find command will strip it when it removes lines containing "fred" within the file. This can usually be worked around by judicious use of the Sort command before you start processing (or otherwise ensuring that "fredjim" always occurs before "fred" - generally reverse alphabetic ordering will suffice).

This is probably best reserved for cases where you really have no option, or it's just being done for your own amusement. I have used stuff like this in anger, but I've checked the output by hand (at least check that you get the right number of lines read). The MP3 playlist generator makes use of two of these, nested. It seems to work, but I wouldn't use anything remotely this complex for work purposes.


top Contents.

Optimisation:

General Optimisation:

If you are running a batch file from a network drive, the file is not cached in memory on the PC (unless you've specifically enabled this on the client). If you are just running through the file in a standard, linear path, then this won't cause too many problems, but if you are calling functions, or doing other things which make use of 'goto' a lot, then you may find that it slows down a LOT.

Note that this is generally only a problem on NT, because DOS isn't caching anything anyway unless you've manually loaded smartdrv . . .

The workaround is to rename your modular batch file to something else (I tend to change the extension from '.bat' to '._'. Then write a simple batch file which copies the file down from the network into a temporary location, runs it, and then tidies it up again. The file n:\batch\ntpatch.bat is reasonably typical:

@echo off
copy n:\batch\ntpatch._ %temp%\ntpatch.bat >nul
call %temp%\ntpatch.bat
del %temp%\ntpatch.bat

Note1: The variant of this used in the NTinstall also includes checking that %temp% has been set correctly.

Note2: In the example above, no parameters are passed through to the main batch file. This isn't normally a problem, but it a point to be aware of . . .

Note3: As mentioned above, you don't gain much by doing this under DOS. It will work, won't be any faster, but will generate marginally less network traffic.

Note4: For batch files that get run by all users on login, this also has the advantage that the file isn't held open on the server except very briefly to copy it. This means that you can safely copy an updated file around the servers without having problems due to people in th middle of logging in having the file locked in read mode.


top Contents.

Use of FOR loops:

'FOR' loops are a very effective way of performing the same function with a range of parameters. The syntax is a little complicated, but once you've got your head round it, it is very useful. Try the following example:

set srvlist=NS1 NS2 NS3 NS4 NS5
 . . .
FOR %%s in (%srvlist%) do call %0 copyfile %%s

The double '%' signs are important - when used in batch files, they imply that a single % sign is to be used in the resulting command. If you were typing this at a command line (and there's nothing to stop you from doing so), you would type:

FOR %s in (NS1 NS2 NS3 NS4 NS5) do call example.bat copyfile %s

What this actually does is read the string in the brackets until it reaches a space. The string up to that point (not including the space) is then substituted for %s in the 'do' section, and the resulting command is run. Note that '%s' is just used as an example - you can use any letter. It then carries on working through the string. We could therefore have written this instead, which would be equivalent:

call %0 copyfile NS1 
call %0 copyfile NS2
call %0 copyfile NS3
call %0 copyfile NS4
call %0 copyfile NS5 

We have gained quite a bit by doing it as a 'FOR' loop though:

* The resulting code is usually shorter, especially for long lists.
* We can use multiple 'FOR' loops based on the same list, but have a single place at the start of the file where the list can be modified.
* We can generate the list on the fly, whereas in the expanded code, the server names are effectively hardcoded.
* If we used variables 'srv1', 'srv2' etc, we would resolve the above problem, but would still have to modify code to change the length of the list.
* I'm not entirely sure why, but it does seem faster . . .

Note: The entries in the list have to be separated by spaces only. If you've got commas or other punctuation in there, then you'll need to do some further tidying of the list before using it.


top Contents.

How goto works:

Consider the following batch file;

@echo off
goto jumpoff

:testme
echo Type1
goto stop

:jumpoff
goto testme

:testme
echo Type2
goto stop

:stop

What do you think will happen if you run such a file?

It turns out that the answer varies depending on what operating system you are running. If you are running a DOS based operating system (DOS, Win95, Win98, WinME), it will always come out Type1. If you are using an NT based OS (NT4, Win2000, presumably WinXP), you always get Type2.

This has implications for optimising things to run on NT style OSs (on DOS style ones, you're doomed). If you can ensure that you only ever goto labels that are further down the file, it should run faster.

Incidentally, functions closer to the start of the file do not run faster than ones towards the end. You'd think they might, but don't forget the goto stop - this will take longer to find the :stop marker the earlier the function is in the file, so it cancels out.

However, it is true that the overhead in calling a function gets higher the larger the file gets. This is an argument for breaking the file into smaller chunks if it gets too big. That said, files up to 100K don't have an noticeable performance degradation, as long as you ensure that they are cached in memory.


top Contents.

Troubleshooting:

General Troubleshooting:

Batch files can be fragile things. They don't tend to trap errors very well, or indeed at all. Some specific pitfalls are given in some of the other sections, in particular - path assumptions, trailing spaces, case sensitivity, endless loops can all cause it to come off the rails in various ways. Other things that will crash it, or cause er 'unpredictable behaviour' are:

* Using the same variable name for different things in the file - ensure your variable names are unique.
* Having multiple section headings with the same name. (unless you're deliberately exploiting it as a way of determining what OS type you are on - there are easier ways of doing this)
* Variable names or section headings containing spaces. Don't do it.
* Trying to call a function that doesn't exist will generate a 'Label not found' error, and then try to carry on regardless.
* Running out of environment space will almost certainly crash things.
* And last, but by no means least, the typnig mitsake. There is no compiler here checking for errors before you run.

It is important to remember that a batch file is a script - so unless the error is extremely serious, it will just generate an error message, and then carry on with the next line, possibly calling external programs or functions with missing or wildly incorrect parameters.

To resolve this, I recommend judicious use of 'echo on' and 'pause' while testing. Write the basic structure of the batch file with the bits that actually DO things commented out or echoing to the screen. That way, you can ensure that the thing actually does what it is supposed to before you actually do any damage to anything. This is especially useful if you are assembling parameters within the batch file.

A really good way of troubleshooting is to put lots of simple, explanatory comments within the file . . .


top Contents.

Logging:

Not all the modular batch files I've put together use logging, or display redirection, but those that do use it consistently. To take simple logging first:

Early on in the Main section of the file, set a variable called 'log' to point to a file. e.g.:

set log=c:\temp\logfile.log

Note that the file must be writable if logging under NT on a secured system.

Then throughout the rest of the file you can send messages into the log file via a redirected echo:

 . . .
echo Defenestration completed.>>%log%
 . . .

Note that you have to use double '>' signs. This appends the message to the file. If you just use a single '>' sign, you will overwrite the current logfile.

You can of course redirect the output of any command which support console redirection into the log file. In many cases though the output may be cryptic, so you may want to add extra comments into the log to explain what's going on. For instance, if you redirect the output of the copy command into log, and you have a few copies going on, you'll end up with a sequence of '1 file(s) copied' messages in the log file, but no indication as to what the files were. Placing an echoed log message before each copy would allow you to make sense of this.

You can of course log messages from anywhere in the batch file - main, functions, wherever.


top Contents.

Display redirection:

This is effectively an easily toggleable debug mode for a batch file. It can also be used to provide enhanced logging, and is generally only used in those big, complex files where I've already used logging. As with logging, all the files where I've used it do so in a consistent manner.

It works by defining a variable 'disp':

set disp=nul

And then commands which would generate on screen output can be set to redirect their output to %disp%:

copy file1.eg file2.eg >>%disp%

Note the use of double '>' signs again. Same reason as in the logging section.

With disp set to 'nul', everything looks tidy, but by changing the value to 'con' things are visible on the console again. Alternatively, change it to '%log%' to add extra information to the log file (note that the log variable must have been defined earlier in the file for this to work.)


top Contents.

Debugging techniques:

If your batch file gets big and complicated (and needs to be that way), it can be useful to have some more comprehensive debugging capabilities to track down problems.

The W2K Installer has the lot;

@Echo Off
if %debug%.==on. echo on
if %listfunc%.==on. if not %1.==. echo %1 %2 %3 %4 %5 %6 %7 %8 %9
if %log%.==. set log=nul
if %listfunc%.==log. if not %1.==. echo %1 %2 %3 %4 %5 %6 %7 %8 %9 >>%log%
if not %1.==. goto %1

So, what does all this do - well, by setting debug to "on", we turn echo back on again. This is a fairly crude one, but it is very useful in the W2K installer - this is running when booted from a floppy, and there really isn't room for a text editor on the disk. It's quite handy being able to turn echo on and off with a set command at the prompt, rather than having to edit the file.

Listfunc is a slightly more flexible one - set listfunc to "on", and the names of the function calls will be displayed on screen before they are called. Set listfunc to "log", and the function names will be sent to the log file as they are called. The line above this ensures that the log variable exists (it won't have been set yet when running the file for the first time).


top Contents.

Trailing spaces:

Trailing spaces are the spawn of the devil. This is putting it mildly. Consider the following:

set srv=NS28
set vol=APPS32   
set dir=APPS32
n:\public\map root v:=%srv%\%vol%:\%dir%

That'll work, right?

Wrong.

It fails because there is a trailing space (you mean you didn't notice it?) on the end of the 'set vol' line. When the code is run, the last line comes out as:

n:\public\map root v:=NS28\APPS32 :\APPS32

Which, not suprisingly, fails miserably.

If things are going wrong in this sort of way, especially if you are appending different variables, then turn 'echo on' around the offending section, and watch it very carefully. Alternatively, construct the command line in a variable, and log that variable before running it.


top Contents.

Total paranoia:

Total paranoia is good for you.

Instead of this;

if %test%.==yes. goto test
if %test%.==no. goto notest

Do this;

if %test%.==yes. goto test
if not %test%.==yes. goto notest

It's marginally more longwinded, but it means that if %test% isn't set at all, or is set to "maybe", or has a trailing space, or is weird for whatever reason, one of the two possibilities will still be run. In the first example, it is possible for it to go to neither of the functions listed, and will then carry on, possibly through sections of the batch file it wasn't meant to reach in that way.

Don't assume anything. Check that %temp% is set, exists, is writeable, use %systemroot% while on NT, and make no assumptions about things being in the path. Things run from the login script on NT inherit the environment which was present when the login script started plus the changes made by the script itself. This means that winnt, winnt\system32, and a few other local directories are not in the path.


top Contents.

Examples:

MP3 Playlist maker:

It is possible to use this without understanding in slightest how it works. You may need to modify a couple of lines in the mkm3u.bat file;

It assumes that all the files are copied to c:\batch. You can put them where you like, but if you choose somewhere other than c:\batch, you'll need to edit line 10 in mkm3u.bat with the correct location so that it knows where to copy setany.bat from.

It assumes that c:\temp is a sensible place to use for temporary files. If you want it to use somewhere else, edit line 9 in mkm3u.bat

It assumes that either - makelist.bat and mkm3u.bat are in the same directory, or mkm3u.bat is in the path.

If you want it to empty the playlist directory before it starts, uncomment line 27 in makelist.bat

To use it, run makelist with no parameters to get the help screen, or refer to the supplied readme.

Playlists will be generated on the assumption that files are named Track.mp3, in directories named after the album, which are in turn in directories named after the artist.

For each directory, it will generate a playlist called "Artist - Album.m3u" (m3u being the default playlist name for winamp). If there is more than one album inside an artist directory, it will also create a playlist called "Artist - All.m3u". It will also generate a playlist called "!All.m3u" which contains all the files in the entire structure (the ! is to ensure it gets sorted first alphabetically).

Within each playlist file, the tracks will be sorted alphabetically.

It displays name of the current playlist being produced on screen as it traverses the directory structure. It will go through the directories in reverse alphabetical order.

I'm fairly confident that this works - I've used it at home and work, but I take no responsibility for this if it crashes horribly, deletes files, or damages your machine in any way.

Download makelist.zip (2Kb)


< Thoughts. ^ Top.


Valid HTML 4.01 Copyright © 2006 Mike Sandells.
Last Modified: 12.7.2006