Whitespace considerations with find command and comparison operators (was: Maximizing "Find" filter syntax)

Hey there,

Brand new to the Mikrotik world, and this may have been answered elsewhere, but my search fu may just not be up to the task. Also, I have years of scripting experience across a variety of CLIs (Windows, Linux, and BSDs), script languages, and some programming. I definitely have a bit of a learning curve to understand the nuances of how RouterOS scripting works.

I’m trying to enumerate all interfaces that meet specific criteria. Example code (particular concern in bold):

:local vlanOverhead 8; #max=l2mtu minus 8 bytes (802.1Q and 802.1AD in use)
:foreach interface in=[/interface find mtu != (l2mtu - $vlanOverhead)] do={
     :put (\[/interface get $interface name\] . ";" . \[/interface get $interface mtu\] . ";" . \[/interface get $interface l2mtu\])
}

Relevant logic: /interface find mtu != (l2mtu - $vlanOverhead)

Error: “expected mtu value”

Additional questions beyond, “How do I make this work?” I’d like to understand how the commands and scripting language behave:

  • Is it possible to use variables with the “find” search query?
  • If I hard code an integer in the “find” search query ?
  • I know I can get all interfaces and then use conditionals within the loop to further filter. I would prefer to use the find command as the only place I filter. And am looking to understand the capabilities and limitations of the find command.

Thank you

Solution: White space between operands and comparison operator are not allows.

Here is script with example of advanced find command: :Deserialize on DSV is throwing invalid JSON Error. Bug Or User Error? - #8 by optio .

To use programmatic approach for find argument validation put code between square brackets, argument value will be contained in variable with same name as argument name and when needed criteria matches return back find argument value.

Thanks.

The query syntax in my question is comparing 2 interfaces values. I could get it to work with “find mtu != l2mtu”. I got an error when I changed it to “find mtu != l2mtu - $vlanOverhead”.

In the example you shared there is no math as a comparison (though it does answer the variable in find query).

Not currently at computer (writing this on mobile phone) to give you exact checked example, use :if where math result condition can ce checked and return mtu value when condition is true - not checked example: find mtu=[:if ($mtu != ($l2mtu - $vlanOverhead)) do={ :return $mtu }]

Doesn’t seem to work. Results:

[admin@MikroTik] /interface> :local vlanOverhead 8;
[admin@MikroTik] /interface> :foreach interface in=[/interface find mtu=[:if ($mtu != ($max-l2mtu - $vlanOverhead))]] do={
{...
6to4 dot1x gre6 l2tp-server mesh pppoe-client sstp-server vxlan disable monitor-traffic
amt eoip ipip list ovpn-client pppoe-server veth wifi edit print
bonding eoipv6 ipipv6 lte ovpn-server pptp-client vlan wireguard enable reset
bridge ethernet l2tp-client macsec ppp-client pptp-server vpls blink export reset-counters
detect-internet gre l2tp-ether macvlan ppp-server sstp-client vrrp comment find set
{... :put $interface
{... }
do:

Appreciate your help.

change it to $l2mtu

load it like that

{
script

}

And that too if multiple commands (like variable definitions) are executed from CLI.

...$&$@##...


/interface {
    :local QinQoh 8
    :foreach if in=[find where mtu!=(l2mtu - $QinQoh)] do={
        :put "$[get $if name];$[get $if mtu];$[get $if l2mtu]"
    }
}

First, read the basic guide, which you seem to have never read at all, or don't even understand.

If you want examples of how to do scripts:

Thank you.

This worked! When I compared this to my original post, it seems what it did not like was the white space between the operands and the comparison operator. As soon as I deleted that whitespace from my original code, it ran as well.

Yes and no.

It depends if you were running your original code in a script or on command line.

On the latter the use of curly brackets to include the "local" variable declaration into the same "scope" of the code is needed (before and besides possible white space issues).

Well, when I tried to make a quite advanced script on RouterOS some time ago (so things might have changed) I learned that you cannot put an arbitrary expression just anywhere. In many programming languages you can write whatever expression in place of any parameter value, with arbitrary number of parentheses and sub-expressions, but not always in RouterOS. You often/sometimes have to simplify things and assign intermediate results to variables which you can then use in the next statement.

And indeed, whitespace which is arbitrary in many languages (not all…) can be relevant as well.

It was definitely the whitespace. I was using braces when testing at the CLI, I just didn’t put them in the code snippet. After the edit, I was able to confirm it worked at the command line when using braces. And able to confirm it worked as a script. It worked in neither scenario before, even when properly using braces at the CLI.

Thankfully, the awesome community provided working code that I could compare piece by piece to mine that helped me figure out where my syntax errors were.

The documentation calls out that whitespaces are not allowed for parameters and certain verbs, which I found after the fact. It does take one a few go arounds to consume 30 pages of documentation. That said parameters and operands are technically different, which might also be why it I didn’t extrapolate that might also be relevant to the relational operators/operand.

It is interesting to note that the whitespaces only seem to matter (as seen in the working example) to the relational operators/operands. Where as white spaces can/(must?) be used with arithmetic and logical operator/operands. I haven’t worked with bitwise yet to determine.

Thanks! This is exactly what I thought I was running into. Varying scripting/programming definitely have WIDELY varying degrees of flexibility here. As well, as WILDY varying tricks and techniques to get the expression where you want.

100% agree. Let me add: Decisions about how whitespace rules are implementd across different languages feels very arbitrary. :man_facepalming:

I do appreciate the heads up that RouterOS is not as flexible on expressions as some other languages. With that warning I can look for alternatives so I don’t spend to much time trying to put a square peg in a round hole!

Edit: That is actually why I spent so much time on this… To try and figure out the nuances of the scripting language so that later down the road I can try and write more elegant scripts.

You might want to check out Scripting Tips and Tricks on their help page, which has a lot of examples and tried to clarify some of the more funky things. And all example of find in docs show the lack of whitespace between operator/operands. But agree the docs don't explictly state any rule that operators must be directly next operands.

But overall, the scripting language is pretty consistent... If you do /cmd/set attribute=value, it also does not use spaces. And things with spaces often needs "'s So find's filters uses same form as set/add config is not entirely unexpected.

I wouldn't say "cannot". You can add scripting to any config operation. Now while most "scripting mysteries" are explainable... but often not obvious at first, so annoying.

I'd say the large source of troubles is not expressions, but that RouterOS variables/attributes all have types. RouterOS attributes often have varying rules about automatic type casting, so using the in expressions requires knowing the variable type. So the need form of an expression for particular cmd attribute may vary. e.g. if you look at @rextended's various examples, you note the judicious use of tostr/tonum and typeof which how you prevent these these things.

So were I agree more is:

Even if just for "debugging"... since you can add a put//log/info if you get an error.

Now you want to "optimize" things, that's were you do need to know a fair amount of the "scripting rules". For example, @optio alludes you can actually "play tricks" on find... e.g since an expression in find can return nil, so you can do operations directly in find expression with [ ... ], so a dorky example could avoid the foreach in OP's example to "shorten" the whole script.

I think because [find] allow listing "flag"-properties as single word, for example

/ipv6 address find global !dynamic !deprecated interface=$ifname

So putting spaces around the operators and it probably recognizes the left- and right-hand side operands as standalone flags which don't exist. In the original post mtu would be treated as a boolean property named mtu.

1 Like

Interesting that find <arg> =<value> is valid, but find <arg> = <value> not. From CLI it can be easly detected where is error position in line where red block cursor is present.

Regarding my example, it was unnecessary for specific OP topic post case where find argument is mapped as literal word and can be used as value for calculation, but in cases of some “advanced“ filtering, and as I understand OP wants to learn advanced RSC possibilities it can be useful, for eg. when it is needed to find items where criteria array value is contained in argument value array (find array items in array), then it can be done by putting script between square brackets which process argument value array and returns argument value when needed condition matches.

Interesting that you state the use case of using find based on a list. That’s exactly what I just sat down to work on. :grinning_face: Very handy to have that linked script as an example to start with.

Maybe I didn't foresee RSC possibilities well when I was writing that script, it can be simplified with tilde operator (~) when finding single value in array (find <array_arg>~<val>), but for matching array items in argument value array then it can be done like that because it can be different cases when matching collections, like when it is needed to find all values or to find single any value… (edited my above post to be clear about criteria array value find).

:global QinQoh 8
/interface/find [:if ($mtu!=($l2mtu-$QinQoh)) do={:put "$name;$mtu;$l2mtu"}]