原文:
http://commandline.co.uk/lib/treeview/index.php
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Batch Function Overview (批处理函数概述)
What are they?
A batch function is a block of batch code that can be called by a batch file, optionally with a number of parameters. The function performs its task without unintentionally modifying variables outside of its own scope, and then returns control to the statement following the one that called the function.
Why use them?
The primary benefit of functions is reusable code. Once a function has been written, it can be used over and over again. This reduces the time spent developing and debugging scripts
Scripts that use functions naturally have a modular structure and use fewer 'global' variables, therefore they are much easier to understand, debug and maintain.
What do they look like?
All the functions in the library are based on the following template(下面是提供的模板):
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:<Function Name> <Parameter 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
<Parameter 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> This is where the function performs its main purpose
<Set return values> This is where the function 'returns' any local values back to the calling routine
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Batch Functions Explained (批处理函数解释)
Passing Arguments
Most high-level programming languages allow arguments to be passed to functions 'by value' and 'by reference'. Passing by value means that the function receives a copy of the arguments value. Any changes made to this copy by the function do no affect the original variable.
When a variable is passed by reference, the function receives a pointer to the original variable, and loosely speaking, changes made to this pointer by the function do affect the original variable.
In the batch programming language variables are passed by reference. Unfortunately the pointers to the original variables are read only and consequently cannot be assigned values and therefore are unable to alter the variable which they point to.
The good news is that with a little ingenuity, the batch language can be coerced into passing variables by value and by reference in a similar way to high-level languages such as C and Visual Basic.
By Value
Below is a very simple example showing how to pass an argument by value and how that value is then read by the function.
1. @echo off & setlocal ENABLEEXTENSIONS
2. set str=Hello
3. call :demoA %str%
4. goto :EOF
5.
6. :demoA
7. echo/%1
8. goto :EOF
In line 3, %str% is expanded to Hello, and then the memory location of where Hello is actually stored is assigned to %1. Line 7 then references %1 which is then expanded to Hello.
If two or more arguments were being passed, they could be referenced using %2, %3 and so on, all the way up to %9. If more than nine variables were being passed, then to reference the tenth, the shift command would have to be used.
shift effectively increments all the pointers. Before shift is used, %0 points to the name of the function or routine and %1 points to the first argument. After shift is used, %0 points to what %1 used to point to, %1 points to what %2 used to point to and so on. The tenth argument can now be referenced using %9.
By Reference
Here is a slighty different version of the example above. Instead of passing the value Hello to the function, the name of a variable that contains Hello is passed instead.
1. @echo off & setlocal ENABLEEXTENSIONS
2. set str=Hello
3. call :demoB str
4. goto :EOF
5.
6. :demoB
7. call echo/%%%1%%
8. goto :EOF
Inside the function demoB, %1 expands to str. To read the value contained in str, it is necessary to enclose %1 with double percents and instruct the command interpreter to perform another expansion. This is most easily accomplished by using the CALL command as shown in line 7. For a detailed explanation of variable expansion, see the thread in alt.msdos.batch.nt titled "Variable expansion (W2KSP2)".
Why would we want to pass arguments like this? Well now that the function knows the name of a variable, it can assign it a value and thereby the function can now return values.
Returning Values
So far the contrived examples have only served to illustrate the points being made. This next example demonstrates how a function can return a value and is typical of the functions in this library. The methods used guarantee this function can be inserted, as it stands, into any batch file without causing any side effects whatsoever.
The function below is called Area and it simply calculates the product of width and height and then returns the result. Three arguments are required, the first two should be passed by value, the third by reference.
01. @echo off & setlocal ENABLEEXTENSIONS
02. set x=2
03. set y=3
04. call :Area %x% %y% answer
05. echo/The area is: %answer%
06. goto :EOF
07.
08. :Area %width% %height% result
09. setlocal
10. set /a res=%1*%2
11. endlocal & set "%3=%res%"
12. goto :EOF
Line 04 calls the Area function passing three arguments (it could equally have read call :Area 2 3 answer). The first command of the function (line 09) is setlocal. This command creates a copy of the current environment and this copy then becomes the current environment. Line 10 calculates the product of arguments %1 and %2 and saves the result in the variable called res.
The next line consists of two commands seperated by an ampersand (&). This causes the command interpreter to expand both commands and then execute them one after the other. So line 11, after it has been expanded equates to endlocal & set "answer=6". Now the endlocal command is executed, which deletes the copy of the environment created by the most recent setlocal command, and restores the previous environent. Then set "answer=6" is executed, and finally on line 12 goto :EOF exits the function and execution continues at line 05. The net result is that the Area function has returned a value.
If on line 11, the command set "%3=%res%", had been expanded after endlocal had been executed, then instead of expanding to set "answer=6" it probably would have been set "answer=" as the res variable was deleted by the endlocal command.
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) are passed by value, but in order to assign the result of the calculation to the answer 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 unquoted 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 so-called 'poison' characters (such as " ! | < > ^ " & %).
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.
Unfortunately things get a little more complicated when two or more arguments are passed by reference and those arguments need to be read by your function (as opposed to just being assigned values). This is best illustrated by the Swap function shown below.
Protecting Arguments
When passing two or more arguments by reference where those arguments need to be read, care must be taken not to destroy any of the arguments before they have all been read. Consider this example below.
01. @echo off & setlocal ENABLEEXTENSIONS
02. set a=one
03. set b=two
04. echo/Before call :swap a b
05. call :Swap a b
06. echo/After call 1 :swap a b
07. call :Swap b a
08. echo/After call 2 :swap b a
09.
10. goto :EOF
11.
12. :Swap
13. setlocal
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
After call 1 :swap a b
After call 2 :swap b a
This function is supposed to swap the contents of two variables. Notice it succeed the first time, but failed on the second. This was caused by variable names outside of the function clashing with duplicate names inside the function. Specifically, this is what went wrong:-
Line 07 called Swap, the first argument is b, the second a.
Line 14 is expanded twice because of the call command, after the second expansion, the line equates to set a=one, and once this command is executed, a is overwritten.
Line 15 is also expanded twice and equates to set b=one, in other words both a and b are now the one
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".
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Using the Functions(函数的使用)
Copy the Function
To use any of the functions, just copy and paste them to the end of your batch file. To prevent the function(s) from being executed after your main batch file code has run, add goto :EOF after your main batch file code but before the function(s). For example:-
Example 1a
<-- Your main batch file code goes here
goto :EOF
<-- 1st function here
<-- 2nd function here
<-- and so on...
Calling the Function
The documentation for each function describes the number of required and optional parameters, the type of data each parameter should contain and whether the parameter must be passed 'by value' or 'by reference'. Both terms are used very loosely here and the reason is covered in great detail in the next section. Suffice to say that to pass a variable by value, call the function with a constant or expanded variable. And to pass by reference, call the function with the name of a variable.
By Value
To pass a parameter 'by value', either call the function with a constant or an expanded variable that contains the value. As an example, the Sleep function expects one parameter (a number of seconds) to be passed by value. Here are three ways to call the Sleep function with the value 9:-
Example 2a
call :Sleep 9
Example 2b
set AnyVarName=9
call :Sleep %AnyVarName%
Example 2c
call :Sleep %1
Notes: In example 2b, practically any variable name could have been used. Example 2c assumes the value of 9 was passed to the batch file as a commandline parameter.
By Reference
To pass a value 'by reference' simply use the name of a variable. As an example, the GetDate function expects three parameters, all passed by reference. Here's how to call the GetDate function and use the 'returned' values:-
Example 3a
call :GetDate yy mm dd
echo/Todays date is: %yy%-%mm%-%dd%
Note that although example 3a used the variable names yy, mm and dd, practically any variable names could have been used. For instance, example 3b is functionally equivalent to example 3a (but not so easy to understand).
Example 3b
call :GetDate x y z
echo/Todays date is: %x%-%y%-%z%
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
应用举例:
DateToDOW
The DateToDOW function returns the day of week number for a given calendar date..
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:DateToDOW %yy% %mm% %dd% dow
::
:: By: Ritchie Lawrence, 2003-04-29. Version 1.1
::
:: Func: Creates a day of week number from a calendar date, where 1 = Mon
:: and 7 = Sun. For NT4/2000/XP/2003.
::
:: Args: %1 year component to be converted, 2 or 4 digits (by val)
:: %2 month component to be converted, leading zero ok (by val)
:: %3 day of month to be converted, leading zero ok (by val)
:: %4 var to receive day of week number, 1 to 7 (by ref)
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
setlocal ENABLEEXTENSIONS
set yy=%1&set mm=%2&set dd=%3
if 1%yy% LSS 200 if 1%yy% LSS 170 (set yy=20%yy%) else (set yy=19%yy%)
set /a dd=100%dd%%%100,mm=100%mm%%%100
set /a z=14-mm,z/=12,y=yy+4800-z,m=mm+12*z-3,dow=153*m+2
set /a dow=dow/5+dd+y*365+y/4-y/100+y/400-2472630,dow%%=7,dow+=1
endlocal&set %4=%dow%&goto :EOF
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Parameters
%1 year component to be converted, 2 or 4 digits (by val)
%2 month component to be converted, leading zero ok (by val)
%3 day of month to be converted, leading zero ok (by val)
%4 var to receive day of week number, 1 to 7 (by ref)
Return Values
See parameters above.
Example
@echo off & setlocal ENABLEEXTENSIONS
call :GetDate y m d
call :DateToDOW %y% %m% %d% dow
call :DayName %dow% day
echo/Today is %day%
goto :EOF
Remarks
The DateToWeek function also returns day of week (in addition to year and month).
See Also
DateToWeek, DayName, DayNumber