Mikrotik RouterOS scripting: for loops are a bit of getting used to
Posted by jpluimers on 2017/07/18
Earlier, I wrote “:for loops are a strange beast so I will elaborate on those in a separate post.” so now is the time to do that.
The :for loop documentation is very dense:
Command Syntax Description for :for <var> from=<int> to=<int> step=<int> do={ <commands> }
execute commands over a given number of iterations
So a for loop has these elements:
- from=
- to=
- step=
- do=
Luckily, the old RouterOS 2.7 documentation on loops (which they’ve revamped after Router OS 2.7 removing many useful examples) has this:
:for – It has one unnamed argument, the name of the loop variable. from argument is the starting value for the loop counter, tovalue is the final value. This command counts loop variable up or down starting at from and ending with to, inclusive, and for each value it executes the do statement. It is possible to change the increment from the default 1 (or -1), by specifying the stepargument.
[admin@MikroTik] > :for i from=1 to=100 step=37 do={:put ($i . " - " . 1000/$i)} 1 - 1000 38 - 26 75 - 13 [admin@MikroTik] >
You might think that from= the start value, to= the finish value and the loop won’t execute when step= a positive value and from= larger than to=. Or that without a step= the loop will always iterate in ascending order.
Wrong! And wrong!
So it’s time for some…
:for loop examples
I’ve split the examples in groups to show a few points.
The first point is that :for loops can count both forward and backward. You don’t even need the step= element for it: proper values for from= and to= will do just fine:
> :for i from=3 to=0 do={ :put $i }
3
2
1
0
> :for i from=0 to=0 do={ :put $i }
0
> :for i from=0 to=3 do={ :put $i }
0
1
2
3
The middle loop shows :for will execute at least once.
Now lets add some positive and negative step= magic to see what happens:
Positive step Negative step > :for i from=0 to=3 step=1 do={ :put $i } 0 1 2 3 > :for i from=0 to=3 step=2 do={ :put $i } 0 2 > :for i from=0 to=3 step=3 do={ :put $i } 0 3 > :for i from=0 to=3 step=4 do={ :put $i } 0 > :for i from=0 to=3 step=5 do={ :put $i } 0 > :for i from=0 to=3 step=-1 do={ :put $i } 3 2 1 0 > :for i from=0 to=3 step=-2 do={ :put $i } 3 1 > :for i from=0 to=3 step=-3 do={ :put $i } 3 0 > :for i from=0 to=3 step=-4 do={ :put $i } 3 > :for i from=0 to=3 step=-5 do={ :put $i } 3
You see the loops will:
- with step= larger than the final bound will only perform the first loop iteration
- with negative step= automagically reverse the iteration order thereby overruling the meaning of from= and to=
Lets see what happens when we reverse from= and to= to illustrate the second point better:
Positive step Negative step > :for i from=3 to=0 step=1 do={ :put $i } 0 1 2 3 > :for i from=3 to=0 step=2 do={ :put $i } 0 2 > :for i from=3 to=0 step=3 do={ :put $i } 0 3 > :for i from=3 to=0 step=4 do={ :put $i } 0 > :for i from=3 to=0 step=5 do={ :put $i } 0 > :for i from=3 to=0 step=-1 do={ :put $i } 3 2 1 0 > :for i from=3 to=0 step=-2 do={ :put $i } 3 1 > :for i from=3 to=0 step=-3 do={ :put $i } 3 0 > :for i from=3 to=0 step=-4 do={ :put $i } 3 > :for i from=3 to=0 step=-5 do={ :put $i } 3
:for loop recommendations
There are two recommendations I took away from the above:
- Only use step= when absolutely needed
- Do not rely on :for only counting forward – like it does in many other languages – be aware that it will automatically cound downward when to= has a smaller value than from=
In the second case, use an extra :if statement or convert it to a :while loop like these examples:
:if and :for loop :local and :while loop { :local start 3 :local finish 0 :if ($start <= $finish) do={ :for i from=$start to=$finish do={ :put $i } } }with this output:
> { {... :local start 3 {... :local finish 0 {... :if ($start <= $finish) do={ {{... :for i from=$start to=$finish do={ :put $i } {{... } {... } { :local start 3 :local finish 0 :local i $start :while ($i <= $finish) do={ :put $i :set $i ($i + 1) } }with this output:
> { {... :local start 3 {... :local finish 0 {... :local i $start {... :while ($i <= $finish) do={ {{... :put $i {{... :set $i ($i + 1) {{... } {... }
:do while / :while do loops also lack proper documentation
Similarly, the old documentation is better at describing the while.
The new documentation does mention the :while loop, but only explains the :do loop:
Command Syntax Description do..while :do { <commands> } while=( <conditions> ); :while ( <conditions> ) do={ <commands> };
execute commands until given condition is met.
The old documentation again is better (though it lacks examples):
- :while – this command has one unnamed argument, a condition. It is evaluated every time before executing do statement. If result is not a boolean value, error is reported. If the result of condition is true, commands are executed once, and the condition is evaluated again, and this repeats until false.
- :do – It has one unnamed argument, which holds the console commands that must be executed. It is similar to the do statement of other commands. If no other arguments are given, :do just executes these commands once. There is not much use in that. If you specify a condition as a value for while argument, it is evaluated after executing commands, and if it returns true, do statement is executed again, and this is repeated until the condition returns false. If you specify a condition for if argument, it is computed only once, before doing anything else, and if it is false, nothing is done. If it is true, everything is executed as usual. Note that:do A while=B is different from :while B do=A, because :do evaluates condition after executing command, not before, like:while does. However, :do A if=B and :if B do=A do exactly the same thing.
Maybe more on that in a later post.
–jeroen
Leave a Reply