| Batch File Functions for NT4/2000/XP/2003 |
Function Library
Welcome to my collection of batch file functions. Currently it only consists
of date and time functions. However, I hope to add a few more categories
one day (don't hold your breath though). The functions have mainly been
written in response to usenet posts to the alt.msdos.batch.nt
and microsoft.public.win2000.cmdprompt.admin
newsgroups. They are all 'ready to use', simply copy and paste them into
your projects. Each function's argument list is well documented and each
function is written so that its variables will not clash with your existing
variables or cause any other side effects.
Date and Time Functions
Why Functions?
First of all, my idea of a batch file function is a routine that can be
called with a list of argument(s) and which cannot unintentionally modify
variables outside of its scope. Such functions provide, but are not limited
to, the following benefits:-
- Faster development
- Code is easier to modify/update
- Improved readability
- Reusable code
- Easier to debug
- Reduced debugging
This idea requires several features of high level programming languages
that are not available in the batch programming language - namely pointers
and the ability to return values. Fortunately, these features can be simulated.
For all but the simplest batch file, I thoroughly recommend the use of
functions.
Function Format
All the functions are based on the following template:-
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:<Function Name> <Argument list>
::
:: By: <Author/date/version information>
::
:: Func: <Function description>
::
:: Args: <Argument description>
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
setlocal ENABLEEXTENSIONS
<Body of function>
endlocal&<Set return values>&goto :EOF
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
| <Function name> |
Name of the function, eg GetDate |
| <Argument list> |
List of arguments. This list only serves as a reminder
of how to call the function (everything after the function name is
ignored by the command interpreter) |
| <Author/date/version information> |
Self explanatory |
| <Function description> |
Brief explanation of what the function does and which
platforms it has been designed for (NT4, W2K or XP) |
| <Argument description> |
Detailed description of the functions arguments |
| <Body of function> |
Self explanatory |
| <Set return values> |
This is where the function 'returns' local values back
to the calling routine |
Passing Arguments
The arguments passed to batch functions are always by value. Basically,
a copy of the variable is passed to the function. Any changes made to
that argument will not alter the contents of the original variable. This
is similar to how arguments are passed in the C computer language, or
Visual Basic when the ByVal keyword is used. Below is a very simple example
of how to pass an argument by value and how that value is then read by
the function.
1. @echo off
2. set str=Hello_World
3. call :demo1 %str%
4. goto :EOF
5.
6. :demo1
7. echo/%1
8. goto :EOF
Line 3 passes the value 'Hello_world' to the 'demo1' function. Line 7
references it using '%1'. If two or more arguments were passed, they could
be referenced using '%2', '%3' etc.
So that functions can modify the original copy of an argument passed
to it, pointers are required. As mentioned above, this high level language
feature can be simulated in batch. This is achieved simply by passing
the name of the variable to the function. To read the original value of
an argument passed by reference, wrap the argument in double percents
and expand it using the CALL statement as shown in this second example:-
01. @echo off
02. set str=Hello_World
03. call :demo2 str
04. goto :EOF
05.
06. :demo2
07. setlocal
08. call set arg1=%%%1%%
09. echo/%arg1%
10. endlocal&goto :EOF
Line 03 calls the 'demo2' function passing the name of a variable by
value. Line 08 then expands the argument and assigns its value to 'arg1'.
For a detailed explanation of variable expansion, see the thread in alt.msdos.batch.nt
titled "Variable
expansion (W2KSP2)".
Protecting out of Scope Variables
In order to create truely reusable functions, you must ensure that they
do not unintentionally modify variables outside of their own scope. This
is accomplished by enclosing your function's main routine with the SETLOCAL
and ENDLOCAL statements as shown in the example above.
SETLOCAL saves and makes a copy of the current environment which then
becomes the active environment. Now, any changes your function makes to
variables only last until an ENDLOCAL statement is issued.
Returning values
To assign a value to a variable whilst inside the function and make the
assignment 'stick', in other words return a value, use the SET command
between ENDLOCAL and GOTO :EOF. Note the ampersand (&) is used to
separate multiple commands on a single line. The following example demonstrates
how to return a value. It makes a call to the function named 'Area', the
first two arguments are by value, the third by reference.
01. @echo off
02. set x=2
03. set y=3
04. call :Area %x% %y% result
05. echo/The area is: %result%
06. goto :EOF
07.
08. :Area %width% %height% result
09. setlocal ENABLEEXTENSIONS
10. set /a res=%1*%2
11. endlocal&set %3=%res%&goto :EOF
Note that line 04 could equally have read: call :Area
2 3 result
Proceeding the function name in line 08 is the argument list (bearing
in mind the command interpreter ignores everything after the function
name). This argument list serves no purpose other than to act as a reminder
of how to call the function. I've adopted the convention of wrapping arguments
passed by value in percent symbols and wrapping optional arguments in
square brackets. Arguments by reference are not wrapped at all.
By Value or Reference
The rule of thumb is to pass arguments by value unless you need to modify
the original values, in which case pass by reference. The 'Area' function
above demonstrates this rule as the first and second arguments (width
and height) were passed by value, but in order to assign the result of
the calculation to the 'result' variable, it was passed to the function
by reference.
Sometimes it's necessary to pass arguments by reference even though read-only
access is required. For example, when passing two or more arguments where
at least one of them contains whitespace, the function would not be able
to determine which argument was which if they had been passed by value.
Calling by reference can also be used to pass arguments containing poison
characters (such as | < > ^ " & %).
Protecting Arguments
When passing two or more arguments by reference where those arguments
need to be read (as opposed to only assigning a new value), care must
be taken not to destroy any of the arguments before they have all been
loaded. The 'Swap' example demonstrates this problem:-
01. @echo off&setlocal
02. set a=one
03. set b=two
04. echo/Before call :swap a b [%a% %b%]
05. call :Swap a b
06. echo/ After call :swap a b [%a% %b%]
07. call :Swap b a
08. echo/ After call :swap b a [%a% %b%]
09.
10. goto :EOF
11.
12. :Swap
13. setlocal ENABLEEXTENSIONS
14. call set a=%%%1%%
15. call set b=%%%2%%
16. endlocal&set %1=%b%&set %2=%a%&goto :EOF
The above example displays:-
Before call :swap a b [one two]
After call :swap a b [two one]
After call :swap b a [one one]
Notice the second call to the 'Swap' function failed to swap the contents
of the variables 'a' and 'b'. This was caused by variable names outside
of the function clashing with duplicate names inside the function. Specifically,
here's what happened:-
- Line 07 calls Swap, the first argument is 'b', the second 'a'.
- Line 14 expands 'b' and assigns it to 'a' which obviously overwrites
'a'.
- Line 15 expands 'a' which evaluates to the value of 'b', in other
words, both 'a' and 'b' are now the same
The usual workaround for this problem is to use variable names that are
unique to your function by preceeding all function variables with the
function name separated by a period. However, if using that approach,
it's actually only necessary to use unique names for vulnerable variables.
Rewriting lines 14-16 of the Area function as shown below would prevent
any arguments being overwritten before they had been saved.
14. call set Swap.a=%%%1%%
15. call set b=%%%2%%
16. endlocal&set %1=%b%&set %2=%Swap.a%&goto :EOF
This method has several drawbacks. Firstly, you must never use a period
in any variable name outside of your functions. Secondly, if the function
doesn't have a really short name and reads a number of variables by reference,
the number of lines of code in the function can double if you want to
avoid 'long' lines of code.
An alternative method is to read the variables with the FOR /F command
using a delimiter character not present in any of the argument values.
Using this method the Swap function could be rewritten as follows:-
12. :Swap
13. setlocal ENABLEEXTENSIONS
14. for /f "tokens=1-2 delims=¬" %%a in ('echo/%%%1%%¬%%%2%%')
do (
15. set a=%%a&set b=%%b
16. )
17. endlocal&set %1=%b%&set %2=%a%&goto :EOF
The delimiter character is a '¬' (ASCII 172, which looks like a back-to-front
'L' on its side). It was chosen because it's rarely used and it appears
on a UK keyboard (above the TAB key). If you don't have this key, you
can type it by holding down the Alt key and typing 172 on the numeric
keypad. The only drawback of this method being your variables must never
contain the delimiter character otherwise you may obtain incorrect results.
This issue was covered in alt.msdos.batch.nt in a thread titled "Function
parameters".
|