Writing Macros
You must write macros on your computer, not on the E-series router. The macros can contain loops, variables, string and numeric values, and conditional statements. Macros can invoke other macros (as long as they are contained within the same macro file), including themselves, but infinite recursion is not permitted. Macros are case-insensitive.
Macros consist of control expressions and noncontrol expressions. Control expressions are enclosed by control brackets, which are angle-bracket and number sign pairs, like this: <# controlExpression #>. Examples of control expressions include the macro name and macro end statements, and while loops. A control expression can include multiple operation statements if you separate the statements with semicolons (;). For example:
<# i:=0; while i++ < 3 #>All macros must have names consisting only of letters, numbers, and the underline character (_). The first character of a macro name cannot be a number. If you include more than one macro within a macro file, each macro must have a unique name. The first line of a macro defines the macro's name:
<# macroName #>Noncontrol expressions are not enclosed by control brackets and simply become part of the generated CLI command text.
You must end all macros with the following control expression:
<# endtmpl #>You can add comments to your control expressions to clarify the code by prefacing the comment with forward slashes (//) inside the control brackets:
<# endtmpl //A comment in the macro end expression #>Text after the // is ignored when the macro is run and is not displayed by the CLI.
You can also add comments outside the control expressions by prefacing the comment with an exclamation point (!). The CLI displays these comments if you use the test or verbose keywords with the macro command; the CLI never interprets these comments as commands.
!This is a comment outside any control expressionYou can improve the readability of a macro by using tabs to indent expressions. Leading and trailing tabs have no effect on the macro output, because they are removed when the macro is run.
Example
The following is a simple macro that you can use to configure the IP interface on the Fast Ethernet port of the SRP module after you have restored the factory defaults:
<# ipInit #><# ipAddress := env.getline ("IP Address of System?") #>enaconf tint f0/0ip addr <# ipAddress; '\n' #>ip route 10.0.0.0 255.0.0.0 192.168.1.1host pk 10.10.0.166 ftp<# endtmpl #>Environment Commands
Macros use environment commands to write data to the macro output, to determine a value, or to call other commands. Table 57 describes the environment commands that are currently supported.
Variables
A local variable enables you to store a value used by the macro while it executes. The macro can modify the value during execution. Local variables can be integers, real numbers, or strings. The initial value of local variables is zero.
Like macros, local variables must have a name consisting only of letters, numbers, or the underline character (_). The variable name must not begin with a number. You must not use a reserved keyword as a variable name. A line that ends with a variable needs a new line character at the end of the line.
Literals
A literal is an exact representation of numeric or string values. Every number is a literal. Place single or double quotation marks around a string to identify it as a string literal. You can specify special characters within a literal string by prefacing them with a backslash as follows:
Examples
4298.6'string literal'"count""\t this string starts with a tab and ends with a tab \t"Operators
You can use operators to perform specific actions on local variables or literals, resulting in some string or numeric value. Table 58 lists the available macro operators in order of precedence by operation type. Operators within a given row are equal in precedence.
Table 59 briefly describes the action performed by each operator.
Subtracts the element to the right of the operator from the element to the left of the operator
Evaluates the elements to the right of the operator, then assigns that value to the local variable to the left of the operator
Creates a new string by joining the values of the right and left sides; converts any numeric values to strings before joining
Evaluates as true (returns a 1) if the element to the left of the operator is less than the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the element to the left of the operator is greater than the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the element to the left of the operator is less than or equal to the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the element to the left of the operator is greater than or equal to the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the element to the left of the operator is equivalent to the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the element to the left of the operator is not equal to the expression to the right of the operator; otherwise the result is false (0)
Evaluates as true (returns a 1) if the values of either the left or right sides is nonzero; evaluation halts at the first true (1) expression
Evaluates as true (returns a 1) if the values of the left and right sides are both nonzero; evaluation halts at the first false (0) expression
See Invoking Other Macros for usage.
See While Constructs for usage.
Groups operands and operators to achieve results different from simple precedence; effectively has the highest precedence
Provides access to environment commands; see Table 57. Provides access to macros; see Invoking Other Macros
Multiplies the expression to the left of the operator by the expression to the right
Divides the expression to the left of the operator by the expression to the right
Divides the expression to the left of the operator by the expression to the right and returns the integer remainder. If the expression to the left of the operator is less than the expression to the right, then the result is the expression to the left of the operator.
Reverses the logical state of its operand. 0 is returned for nonzero operands. 1 is returned for operands that evaluate to zero.
Generates a random integer between the provided endpoints, inclusive
Truncates a noninteger value to the value left of the decimal point
Assignment
Use the assignment operator (:=) to set the value of a local variable. The expression to the right of the operator is evaluated, and then the result is assigned to the local variable to the left of the operator. The expression to the right of the operator can include the local variable if you want to modify its current value.
Example
<# i := i + 1 #><# count := count - 2 #>Increment and Decrement
You can use the increment operator (++) to increase the value of a local variable by one. You specify when the value is incremented by the placement of the operator. Incrementing occurs after the expression is evaluated if you place the operator to the right of the operand. Incrementing occurs before the expression is evaluated if you place the operator to the left of the operand.
Example 1
<# i := 0; j := 10 #><# j := j - i++ #>In Example 1, the result is that i equals 1 and j equals 10, because the expression is evaluated (10 0 = 10) before i is incremented.
Example 2
<# i := 0; j := 10 #><# j := j - ++i #>In Example 2, the result is still that i equals 1, but now j equals 9, because i is incremented to 1 before the expression is evaluated (10 1 = 9).
Similarly, you can use the decrement operator ( ) to decrement local variables. Placement of the operator has the same effect as for the increment operator.
When a local variable with a string value is used with the increment or decrement operators, the value is permanently converted to an integer equal to the length in characters of the string value.
String Operations
The combine operator ($) concatenates two strings into one longer string. Numeric expressions are converted to strings before the operation proceeds. The variable local evaluates to "want a big":
Example
\<# local := "want a " $ "big" #>Extraction Operations
The extraction operations are substring (substr), randomize (rand), round, and truncate. These operators are equal in precedence, and all take precedence over the string operator.
You can use the substring operator (substr) to extract a shorter string from a longer string. To use the substring operator, you must specify the source string, an offset value, and a count value. You can specify the string directly, or you can specify a local variable that contains the string. The offset value indicates the place of the first character of the substring to be extracted; "0" indicates the first character in the source string. The count value indicates the length of the substring. If the source string has fewer characters than the sum of the offset and count values, then the resulting substring has fewer characters than indicated by the count value.
Example
<# local := "want a " $ "big" $ " string" #><# substr(local, 5, 12) #> The result is "a big string"<# substr(local, 0, 10) #> The result is "want a big"<# substr("ready", 0, 4) #> The result is "read"The random operator produces a random integer value from the specified inclusive range; in the following example, the result is between 1 and 10:
<# number:= rand(1,10) #>The round operator rounds off the number to the nearest integer:
<# decimal:= 4.7 #><# round(decimal) #> The result is decimal is now 5The truncate operator truncates noninteger numbers to the value left of the decimal point:
<# decimal:= 4.7 #><# truncate(decimal) #> The result is decimal is now 4Arithmetic Operations
The arithmetic operations are multiply (*), divide (/), modulo (%), add (+), and subtract (-). Multiply, divide, and modulo are equal in precedence, but each has a higher precedence relative to add and subtract. Add and subtract are equal in precedence.
Example
<# 4 % 3 + 12 - 6 #> The result is 7When a local variable with a string value is used with arithmetic operators, the value is temporarily converted to an integer equal to the length in characters of the string value. You can use the env.atoi commands to avoid this situation.
Relational Operations
The relational operations compare the value of the expression to the left of the operator with the value of the expression to the right. The result of the comparison is 1 if the comparison is true and 0 if the comparison is false.
If the expressions on both sides of the operator are strings, they are compared alphabetically. If only one expression is a string, the numeric value is used for comparison. Arithmetic operators have a higher precedence.
Example
<# i := 9; i++ < 10 #> The result is 1<# i := 9; ++i < 10 #> The result is 0Logical Operations
You can use the logical operators AND (&&), OR (||), and NOT (!) to evaluate expressions. The result of the operation is a 1 if the operation is true and 0 if the operation is false.
For the logical AND, the result of the operation is true (1) if the values of the expressions to the left and right of the operator are both nonzero. The result of the operation is false (0) if either value is zero. The evaluation halts when an expression is evaluated as zero.
For the logical OR, the result of the operation is true (1) if the values of the expression on either the left or right of the operator is nonzero. The result of the operation is false (0) if both values are zero. The evaluation halts when an expression is evaluated as nonzero.
The NOT operator must precede the operand. The operation inverts the value of the operand; that is, a nonzero expression becomes 0, and a zero expression becomes 1. For the logical NOT, the result of the operation is true (1) if it evaluates to zero, or false if it evaluates to nonzero.
Example
<# i := 6; i >= 3 && i <= 10 #> The result is 1<# i := 1; i >= 3 && i <= 10 #> The result is 0<# i := 6; i >= 3 || i <= 10 #> The result is 1<# i := 1; i >= 3 && i <= 10 #> The result is 0<# i := 5; !i #> The result is 0<# i := 5; j := 0; !i && !j #> The result is 0<# i := 5; j := 0; !i || !j #> The result is 1Relational operators have a higher precedence than logical AND and OR. The NOT operator is equal in precedence to the increment and decrement operators.
Miscellaneous Operations
The positive (+) and negative (-) operations must precede the operand. The result of a positive operation is the absolute value of the operand. The result of a negative operation is the negative value of the operand; that is, a +(-5) becomes 5 and a -(-2) becomes 2. These operators have the same precedence as the increment and decrement operators. If there is an operand on both sides of these operators, they are interpreted as the add and subtract operators.
Example
<# local_abs := +local #><# local_neg := -local #>All operations are performed in the order implied by the precedence of the operators. However, you can modify this order by using parentheses (( )) to group operands and operators. Operations within parentheses are performed first. The result is that of the operations within the parentheses.
Example
<# 4 % (3 + 12) - 6 #> The result is -6<# 5 && 2 > 1 #> The result is 1<# (5 && 2) > 1 #> The result is 0Results of control expressions are written to the output stream when the expression consists of the following:
- A single local variable
- A single literal element
- An operation whose result is not used by one of the following operations:
Example
<# localvar #> value of localvar is written<# " any string" #> " any string" written<# 4 % 3 + 12 - 6 #> "7" is written<# 4 % (3 + 12) - 6 #> "-6" is written<# i := i + 1 #> nothing is written<# count := (count - 2) #> nothing is writtenConditional Execution
You can use if or while constructs in macros to enable conditional execution of commands.
If Constructs
If constructs provide a means to execute portions of the macro based on conditions that you specify. An if construct consists of the following components:
- An opening if expression
- A group of any number of additional expressions
- (Optional) Any number of elseif expressions and groups of associated expressions
- (Optional) An else expression and any associated group of expressions
- An endif expression to indicate the end of the if structure
The if expression and any optional elseif expressions must include a lone environment value command, a local variable, a literal, or some operation using one or more operators.
Only one of the groups of expressions within the if construct is executed, according to the following scheme:
- The if expression is evaluated. If the result is true (nonzero), the associated expression group is executed.
- If the result is false (zero), then the first elseif expression, if present, is evaluated. If the result is true (nonzero), the associated expression group is executed.
- If the result of evaluating the first elseif expression is false (zero), the next elseif expression is evaluated, if present. If the result is true (nonzero), the associated expression group is executed.
If all elseif expressions evaluate to false (zero) or if no elseif expressions are present, then the else expression groupif presentis executed.
- This evaluation process continues until an expression evaluates to nonzero. If there is no nonzero evaluation, then no expression group is executed.
You can write an empty expression group so that no action is performed if this group is selected for execution. You can nest if structures within other if structures or while structures.
The following sample macro demonstrates various if structures:
<# if_examples #><# //---------------------------------------- #><# if 1 #>! This is always output because any nonzero value is "true."<# endif #><# if 0 #>! This is never output because a value of zero is "false."<# endif #><# // Here's an example with elseif and else. #><# color := env.getline("What is your favorite color? ") #><# if color = "red" #>! Red is my favorite color, too.<# elseif color = "pink" #>! Pink is a lot like red.<# elseif color = "black" #>! Black is just a very, very, very dark shade of red.<# else #>! Oh. That's nice.<# endif #><# // Here's a nested if example. #><# sure := env.getline("Are you sure that " $ color $ " is your favorite color? ") #><# if substr(sure, 0, 1) = 'y' || substr(sure, 0, 1) = 'Y' #><# if color != "black" && color != "white";shade := env.getline("Do you prefer dark " $ color $" or light " $ color $ "? ") #><# if shade = "dark" #>! I like dark colors, too.<# elseif shade = "light" #>! I prefer dark colors myself.<# else #>! Hmmm, that's neither dark nor light.<# endif #><# else #>! Oh. That's nice.<# endif #><# else #>! I didn't think so!<# endif #><# endtmpl #>While Constructs
While constructs provide a means to repeatedly execute one or more portions of the macro based on a condition that changes during the execution. A while construct consists of the following components:
- An opening while expression
- A group of any number of additional expressions
- An endwhile expression to indicate the end of the while structure
The while expression must include a lone environment value command, a local variable, a literal, or some operation using one or more operators. Each time that this expression evaluates to nonzero, the associated expression group is executed.
You can place an iteration expression after the while expression. This optional expression is evaluated after each execution of the while expression group.
You can include if structures within a while structure. You can also include special control expressions indicated by the break or continue expressions. The break expression breaks out of the while structure by halting execution of the expression group and executing the first expression after the endwhile statement. The continue expression skips over the rest of the expression group, evaluates any iteration expression, then continues with the execution of the while structure. The while structure is limited to 100,000 repetitions by default. You can nest up to 10 while structures.
Example
The following sample macro demonstrates various while structures:
<# while_examples #><# //---------------------------------------- #><# // Remember that variables are automatically initialized to 0. #>! Table of squares of the first 10 integers:<# while ++i <= 10 #>!<#i;" ";i*i;"\n"#><# endwhile #><# // Remember that the value of a string used as an integer is the number. #><# // of characters in the string. #><# stars := "*" #><# while stars < 10, stars := stars $ "*"#>!<# stars;"\n" #><# endwhile #><# while stars > 0, stars := substr(stars, 0, stars-1)#>!<# stars;"\n" #><# endwhile #><# // An example of the continue and break statements. #><# // Also note that many statements can be grouped. #>! All the positive even numbers less than 11<# i:=0; while ++i < 100 #><#if i%2; continue; endif; if i > 10; break; endif; "!" $ i $ "\n"; #><# endwhile #><# // While constructs will NOT iterate forever. #><# while 100 > 0 // This is always true, but the macro will eventually stop #><# ++iterations; endwhile #>! The while loop iterated <#iterations#> times.<# endtmpl #>Passing Parameters in Macros
You can pass parameters to an entry macro. The system translates these parameters to the correct data type.
Example
The following macro (saved as m.mac) uses values specified in a CLI command to compute the final result:
<# m(left,right,third) #><# multi := left * right #><# multiFinal := multi * third #><# setoutput console #><# "The result is: multiFinal; "\n" #><# endsetoutput #><# endtmpl #>The following example provides the output from using this macro:
host1#macro m.mac m 5 6 7host1#The result is: 210Generating Macro Output
You may want a macro to provide output while it is operating. In simple cases, you can use the verbose keyword to echo commands to the display and display comments as the macro executes. For more information about the verbose keyword, see Example 2 in Invoking Other Macros.
When running more complex macros or macros that contain a lot of commands or comments, you may want to output only certain information (that is, not all commands and comments). In this case, you can use <# setoutput console #> to send the information directly to the console display when it executes.
Example 1
The following example shows how you can send output directly to the console:
<# setoutput console #>This message appears in the console window (whether or not you use verbose mode).<#endsetoutput #>Example 2
The following example shows how you can send a single argument to the console:
<# puts (msg) #>!==================================================================!==================================================================! output "msg" to console!==================================================================!==================================================================<# setoutput console #><# msg; "\n"#><#endsetoutput #><# endtmpl #>!==================================================================!==================================================================<# tmpl.puts("Hello World")Invoking Other Macros
Macros can invoke other macros within the same macro file; a macro can also invoke a macro from another macro file if the invocation takes place in literal text, that is, not within a control expression. A macro can invoke itself directly or indirectly (an invoked macro can invoke the macro that invoked it); the number of nested invocations is limited to 10 to prevent infinite recursion.
Within each macro, you can specify parameters that must be passed to the macro when it is invoked by another. You must specify named variables enclosed in parentheses after the macro name in the first line of the macro, as shown in this example:
<# macroName (count, total) #>Additional parameters can be passed as well. Parameters can be local variables, environmental variables, literals, or operations. The invoking macro passes local variables by reference to the invoked macro. Passing parameters has no effect on the invoking macro unless the parameter is a local variable that is changed by the invoked macro. When the invoked macro completes execution, the local variable assumes the new value for the invoking macro.
The invoked macro can use the param[n] expression to access parameters passed to it, where n is the number of the parameter passed. This is useful if optional parameters can be passed to a macro or if the same iterative algorithm needs to process the parameters.
Use the expression param[0] to return the total number of parameters passed to the macro. Use the return keyword to halt execution of the invoked macro and resume execution of the invoking macro. Use the exit keyword to halt execution of all macros.
Example 1
The following sample macro demonstrates macro invocation:
<# invoking_examples #><# //---------------------------------------- #><# name := env.getline("What is your first name? ") #>! First, <#name#>, we will invoke the if_examples and! the while_examples macros...<# tmpl.if_examples; tmpl.while_examples #>! Hey <#name#>, have you noticed that your name backwards is:!<# eman:= ""; tmpl.reversestring(name, eman); eman; "\n"#><# tmpl.argumentlist("a", "b", "c")#><# endtmpl #><# argumentlist #><# if param[0] = 0; return; endif #>! argumentList() was called with the following arguments:<# while ++i <= param[0]#>! <#param[i];"\n"#><# endwhile #><# endtmpl #><# reversestring (string, gnirts) #><# i := 0 + string; // i is now equal to the number of characters in string. #><# while --i >= 0; gnirts := gnirts $ substr(string, i, 1); endwhile #><# endtmpl #>Example 2
The following macro in file macro1.mac invokes a macro from within another file, macro2.mac:
<# callAnotherMacro #><# localVar := 5 #>macro macro2.mac macroName2 <# localVar #> string1<# endtmpl #>This macro passes the value of localVar to macroName2. The value of localVar remains at 5 for callAnotherMacro, regardless of any operations upon that variable in the second macro. In other words, an invoked macro in another file cannot return any values to the invoking macro.
The output of callAnotherMacro looks like this:
host1#macro verbose macro1.mac callAnotherMacrohost1#!Macro 'callAnotherMacro' in the file 'macro1.mac' starting execution (Id: 55)macro macro2.mac macroName2 5 string1!Macro 'macroName2' in the file 'macro2.mac' starting execution!Macro 'macroName2' in the file 'macro2.mac' ending executionhost1#!Macro 'callAnotherMacro' in the file 'macro1.mac' ending execution (Id: 55)The invoked macro cannot invoke a third macro from another file. Only a single level of invocation is supported.