The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,184 other subscribers

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:

  1. with step= larger than the final bound will only perform the first loop iteration
  2. 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:

  1. Only use step= when absolutely needed
  2. 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

Source: Mikrotik scripting language: a list of questions I had linking to the forum messages having answers « The Wiert Corner – irregular stream of stuff

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: