WinBatch Tech Support Home

Database Search

If you can't find the information using the categories below, post a question over in our WinBatch Tech Support Forum.

TechHome

Tutorials
plus

Can't find the information you are looking for here? Then leave a message over on our WinBatch Tech Support Forum.

A Quick Course on UDFs

USER DEFINED FUNCTIONS

User Defined Functions (UDFs) are a way to be able to write your own functions in WinBatch. UDF's can make complex code much simpler, and can also make previously impossible tasks not only possible, but easy. We'll be covering several of these later on.

Basically WinBatch has nearly a thousand separate functions to do all kinds of useful work. But alas, WinBatch cannot cover every conceivable possibility. In that case, though, it is possible to write your own function, comprised of a bunch of WinBatch functions, and then use your new function just like one of other WinBatch functions.

To make this clearer, we'll start with a runnable example right away. If you copy and paste this code into WinBatch Studio you can try it now. (You will need the 2001 version of WinBatch to do this though.)

;;;;;;;;;;;;; Beginning of Example 1 - SQUARE ;;;;;;;;;;;;;;;;

#DefineFunction Square(x)
 ans = x * x
 return(ans)
#EndFunction

a=5
b=Square(a)
Message(a,b)

a=17.55
b=Square(a)
Message(a,b)

;;;;;;;;;;;;; End of Example 1 - SQAURE ;;;;;;;;;;;;;;;;;;;;;;

Example 1 may be under-commented a tad, but if you take the time to understand how it works, then you can use this powerful new feature.

Basically the function you make can be used just like other WinBatch functions in your code. Instead of a bunch of ugly code whenever you have to do some standard task, you can just use one of your pre-defined UDFs and be done with it.

The variable space inside a UDF is completely separate from the other variables used in your script (pure local variables to the programming gurus). Only the parameters passed in on the function call are seen, and only the value in the return statement gets passed back to your script. This allows you to write a UDF once, and use it anywhere without worrying about stomping on variables already used by a script someplace.

So. What's going on here.

#DefineFunction and #EndFunction are the keywords indicating the beginning and end of the UDF.

Square is the name of the function. Function names must begin with a letter, can contain letters, numbers, and underscores, and can be up to 30 characters long.

x is a variable name representing the variable passed to the function. UDFs may have zero to sixteen parameters passed in.

At that point the UDF code executes pretty much like normal WinBatch code.

A new form of the return statement has been introduced to allow the UDF to return a value. This simply sets the return value of the function and is your way of getting the UDF results back into your main script.

Admittedly, Example 1 does not save you a lot of time or lines of code. But it was a good starting example of a bare-bones UDF. You can define a whole bunch (hundreds?) of UDFs in a script if you wish.


Part II

Keeping our math theme going for one more example, Example 2, below, computes the hypotenuse of a right triangle, given the lengths of the other two sides. Using the Pythagorean theorem which states that the square of the hypotenuse is equal to the sum of the squares of the other two sides.

Right triangles are the kind where one of the angles is exactly 90 degrees, making a perfect corner. The hypotenuse is the side opposite the 90 degree angle.

e.g.
For a right triangle with sides of 3 feet and 4 feet, the sums of the squares of the sides would be

(3*3) + (4*4) or 25, being the square of the hypotenuse.

Thus the square root of 25 is 5, and the length of the hypotenuse is 5 feet.

So this is a UDF that, given two sides, will compute the hypotenuse of a right triangle.

;;;;;;;;;;;;; Beginning of Example 2 - PYTHAGOREAN ;;;;;;;;;;;

#DefineFunction Pythagorean(a,b)
 SumOfSquares = (a*a) + (b*b)
 ans = Sqrt(SumOfSquares)
 return(ans)
#EndFunction

side1=3
side2=4
hypot=Pythagorean(side1,side2)
Message("Results",strcat(side1,@crlf,side2,@crlf,hypot))

side1=1
side2=2
hypot=Pythagorean(side1,side2)
Message("Results",strcat(side1,@crlf,side2,@crlf,hypot))


side1=31.7
side2=42.5
hypot=Pythagorean(side1,side2)
Message("Results",strcat(side1,@crlf,side2,@crlf,hypot))


;;;;;;;;;;;;; End of Example 2 - PYTHAGOREAN ;;;;;;;;;;;;;;;;;

In Example 2, note that a function called "Pythagorean" is defined and it accepts two parameters. It performs the required math operations and returns the result.

Nice little function.

Please note that yours truly was sorely tempted to include one of a number of *just dreadful* puns based on this theorem. Sanity prevailed, temptation overcome, and none of the dreadful puns appear.


PART III

Now that's nice, one may think, but these examples do not actually do much useful in normal scripting.

True. The previous examples were designed to get you familiar with the concept of UDFs and the passing of information into and out of UDFs.

For the third example, we'll work with a somewhat more useful function. The CapFirstLetter function below is designed to accept a string. It will alter the capitalization of the string so that the first letter is capitalized and the rest are lower case. This function may be handy when marching through a list of names some of which may have improper capitalization.

This example even has comments. Will wonders never cease?

;;;;;;;;;;;;; Beginning of Example 3 - CAPFIRSTLETTER ;;;;;;;;
; Define the CapFirstLetter function
; It takes one "parameter" which we will call
; word
#DefineFunction CapFirstLetter(word)

;Get the length of the passed string
len=StrLen(word)

;if length is zero, just return the null string
if len==0 then return(word)

;if length is one, just uppercase it and
;return the result
if len==1 then return(StrUpper(word))

;if there is more than one character,
;split the first off from the rest
;uppercase the first character and
;lowercase the rest.
firstchar=StrUpper(StrSub(word,1,1))
restofword=StrLower(StrSub(word,2,-1))

;glue the word back together
word=strcat(firstchar,restofword)

;return the new word
return(word)
#EndFunction

;Just by adding the above code to a script,
;you have a new function, CapFirstLetter

;Here are some examples of its use

a="tom"
b=CapFirstLetter(a)   ; returns Tom
Message(a,b)

a="sALLY"
b=CapFirstLetter(a)   ; returns Sally
Message(a,b)

a="pOrCuPiNe"
b=CapFirstLetter(a)   ; returns Porcupine
Message(a,b)

a="i"
b=CapFirstLetter(a)   ; returns I
Message(a,b)

a="123"
b=CapFirstLetter(a)   ; returns 123
Message(a,b)

a="123e20"
b=CapFirstLetter(a)   ; returns 123e20
Message(a,b)

a=""
b=CapFirstLetter(a)
Message("A null string",b)  ;returns a null string

a="FRED"
b=CapFirstLetter(a)  ; returns Fred
Message(a,b)

;that's all folks
;;;;;;;;;;;;; End of Example 3 - CAPFIRSTLETTER ;;;;;;;;;;;;;;

What with the commented source code and the previous explanations it is hoped that readers may be able to sort Example 3 out for themselves.


PART IV

The preceding verbiage pretty much covers the field for simple UDFs. However there is an additional incredibly powerful feature of UDFs. That is the ability for a UDF to call itself. In computer-ese this is called "recursion".

In computer science classes that dwell on this topic (snore) the archetypal example is the factorial calculation.

Basically a factorial of a number is that number multiplied by all the numbers smaller than itself.

Thus

Factorial(5) is equal to 5 * 4 * 3 * 2 * 1

and simply by definition,

Factorial(1) is equal to 1

Now it is true a simple FOR LOOP can easily do this calculation (and that would be...)

            a=5
            ans=1
            for x = 1 to a
               ans = ans * x
            next
            Message(a,ans)

But the point is that the nature of factorial calculations makes an excellent introduction to recursion. The main trick in recursion is to transform the problem into a smaller version of itself. In factorial calculations it goes something like this...

Factorial(5) is equal to 5 * Factorial(4)
   Factorial(4) is equal to 4 * Factorial(3)
      Factorial(3) is equal to 3 * Factorial(2)
         Factorial(2) is equal to 2 * Factorial(1)
            Factorial(1) is equal to 1
         Thus Factorial(2) is equal to 2*1 or 2
      Thus Factorial(3) is equal to 3*2 or 6
   Thus Factorial(4) is equal to 4*6 or 24
Thus Factorial(5) is equal to 5*24 or 120

In this case, in attempting to compute Factorial(5) is it noted that Factorial(5) is the same as 5*Factorial(4).

This is a smaller version of the same problem. So the Factorial routine simply calls itself again to compute the smaller problem.

And so on.

;;;;;;;;;;;;;; Beginning of Example 4 - FACTORIAL ;;;;;;;;;;;;
#DefineFunction Factorial(x)
;Ooop  WinBatch cannot go more than 100 levels deep
;and Factorials do not like negative numbers
;so check for errors
Terminate(x>100,"Error","Number too large")
;And Factorial does not like negative numbers
Terminate(x<1, "Error","number too small")

;If factorial of 1 is desired return 1
;that the only answer we *know*
if x == 1 then return(1)

;Otherwise return x * factorial(x-1)
;which does the "recursion"
ans = x * Factorial(x-1)

;Add 0.0 to force the answer to
;floating point mode as we can quickly get
;into big numbers
ans = ans + 0.0

;return the result
return( ans) 
#EndFunction


a=5
b=Factorial(a)
Message(a,b)


a=10
b=Factorial(a)
Message(a,b)


a=15
b=Factorial(a)
Message(a,b)

;;;;;;;;;;;;;; End of Example 4 - FACTORIAL ;;;;;;;;;;;;;;;;;;

So this is basic recursion. All well and good. The Factorial UDF calls itself to compute a smaller case of the problem. Eventually the smaller problems are solved and the deep nested UDFs begin returning and the final answer is computed.

Although perhaps interesting (snore) academically, this is really not very useful in normal scripting. However this same technique may be used elsewhere, such as in working through directories on a hard drive or plugging away at some set of registry keys. The next example will show working with a file system...


PART V

To access the true power of recursion with UDFs, it is necessary for the UDF to actually do something useful.

In this example, a recursive UDF will be used to traverse a directory tree to compute the total size of all files found.

Please note that for this particular case, WinBatch offers a built in function - DirSize - that can do this computation faster, but for many other operations, such as Xcopy and Deltree, UDFs may be used. In fact UDFs for XCopy and DelTree already exist in our UDF Function library (see next section for a how to find our UDF Library).

;;;;;;;;;;;;;; Beginning of Example 5 - GETDIRSIZE ;;;;;;;;;;;
;This file traverses the directory tree and computes
'the total size of all files on the C:\ drive. 

#DefineFunction GetDirSize(dir)

;Save original directory
origdir=DirGet()

;Change to directory to inspect
DirChange(dir)

;Display current directory for warm fuzzy feeling
BoxText(DirGet())

;Get total size of files in this directory
;and make sure it is a floating point number
;by adding 0.0 as it can get very large.
total=FileSizeEx("*.*")+0.0

;Get a list of subdirectories in this directory
;and count how many there are.
dirlist=DirItemize("*.*")
dircount=ItemCount(dirlist,@tab)

;For each subdirectory, call out GetDirSize UDF
;to determine its size.
for xx=1 to dircount
   ;Pick off a subdirectory name
   thisdir=ItemExtract(xx,dirlist,@tab)
   ;Add size of that subdirectory to our running total
   total=total+GetDirSize(thisdir)
next

;Change back to the directory we entered with
DirChange(origdir)

;Return current value of total to caller.
return total
#EndFunction


;True start of program

;Allow system and hidden files to be counted
IntControl(5,1,0,0,0)      

;Open a Box to display warm fuzzy progress to user
BoxOpen("File Size Inspector","Reading Initial Directories")

;Call our UDF to compute size of C: drive
tot=GetDirSize("C:\")

; Convert to KB
totKB = INT(tot/1024)   

;Display Results
Message("Total File Size of C:\", StrCat(totkb," KB"))
Exit

;;;;;;;;;;;;;; End of Example 5 - GETDIRSIZE ;;;;;;;;;;;;;;;;;

In Example 5, the job of computing the size of the files in a directory is broken down on a directory by directory basis. The function, using the WinBatch FileSizeEx function, computes the size of the files in the current directory.

It then gets a list of all the subdirectories in the current directory and runs each of them, in turn, through the same GetDirSize UDF (recursively). Each subdirectory level down does the same.


PART VI

Building on the previous example, one might note that it would be nice to have commas in the final number to be displayed to improve readability. Adding commas to numbers is messy messy code. Perfect for a UDF. Get it working once and use a comma adding UDF everywhere.

Thus we take the previous code, drop in an AddComma UDF (written again by yours truly), make a minor modification to the previously existing code and, walla, there we have it.

;;;;;;;;;;;;;; Beginning of Example 6 - ADDCOMMAS ;;;;;;;;;;;;


;This file traverses the directory tree and computes the total
;size of all files on the C:\ drive. 

;UDF AddCommas to format our final answer for display (only)
#DefineFunction AddCommas(passednumstring)
 ; Grab everything to the left of the decimal (if any)
 intpart=ItemExtract(1,passednumstring, ".")

 ;Grab everything to the right of the decimal
 fractpart=ItemExtract(2,passednumstring,".")

 ;Compute how many comma separated groups we need
 groups= ((strlen(intpart)-1) / 3 ) +1

 ;Initialize an answer variable to a null string
 answer=""

 ;Figure out what each group looks like
 ;and add each group to the answer variable with
 ;a comma
 for y=1 to groups
    if y==1
       ptr=( (strlen(intpart)-1) mod 3 ) +1
       answer=strsub(intpart,1, ptr )
       ptr=ptr +1
    else 
       answer=strcat(answer, ",", strsub(intpart, ptr , 3))
       ptr=ptr+3
    endif
 next

 ;Now that we have the numbers to the left of the decimal
 ;point "comma-ized" properly, see if there is any
 ;fractional part to worry about.

 ;If there is a fractional part, glue it onto the end
 ;of he "comma-ized" number along with a decimal
 if fractpart!="" 
    answer=strcat(answer,".",fractpart)
 endif

 ;Return the comma-ized answer
 return (answer)

#EndFunction



;Define the function that does all the real work
#DefineFunction GetDirSize(dir)

;Save original directory
origdir=DirGet()

;Change to directory to inspect
DirChange(dir)

;Display current directory for warm fuzzy feeling
BoxText(DirGet())

;Get total size of files in this directory
;and make sure it is a floating point number
;by adding 0.0 as it can get very large.
total=FileSizeEx("*.*")+0.0

;Get a list of subdirectories in this directory
;and count how many there are.
dirlist=DirItemize("*.*")
dircount=ItemCount(dirlist,@tab)

;For each subdirectory, call out GetDirSize UDF
;to determine its size.
for xx=1 to dircount
   ;Pick off a subdirectory name
   thisdir=ItemExtract(xx,dirlist,@tab)
   ;Add size of that subdirectory to our running total
   total=total+GetDirSize(thisdir)
next

;Change back to the directory we entered with
DirChange(origdir)

;Return current value of total to caller.
return total
#EndFunction


;True start of program

;Allow system and hidden files to be counted
IntControl(5,1,0,0,0)      

;Open a Box to display warm fuzzy progress to user
BoxOpen("File Size Inspector","Reading Initial Directories")

;Call our UDF to compute size of C: drive
tot=GetDirSize("C:\")

; Convert to KB
totKB = INT(tot/1024)   

;Display Results
totKB = AddCommas(totKB)
Message("Total File Size of C:\", Strcat(totKB," KB"))
Exit

;;;;;;;;;;;;;; End of Example 6 - ADDCOMMAS ;;;;;;;;;;;;;;;;;;

That, in a nutshell, is an introduction to UDFs. There is more documentation available in the manuals and help files.

If you have questions, you are encouraged to use our online WinBatch Technical Support Forum at http://forum.winbatch.com.


Article ID:   W14984
File Created: 2010:02:17:11:58:06
Last Updated: 2015:07:22:08:52:07