Variable names and where expressions

I just noticed that if a variable and parameter of a where expression have the same name, there filter is ignored:


> /ip/arp/print proplist=address
Flags: H - DHCP, D - DYNAMIC; C - COMPLETE
Columns: ADDRESS
 #    ADDRESS      
 0  C 192.168.99.2 
 1 HC 192.168.3.6  
 2 HC 192.168.3.21 
 3 HC 192.168.3.3  
...

> /ip/arp/print proplist=address where address=1.1.1.1

> :global addr 1.1.1.1
> /ip/arp/print proplist=address where address=$addr

> :global address 1.1.1.1
> /ip/arp/print proplist=address where address=$address
Flags: H - DHCP, D - DYNAMIC; C - COMPLETE
Columns: ADDRESS
 #    ADDRESS      
 0  C 192.168.99.2 
 1 HC 192.168.3.6  
 2 HC 192.168.3.21 
 3 HC 192.168.3.3  
...

Is this a known thing?

Yes, work as expected.
A basic rule is to never use variable names that are the names of in the system.

[rex@test] > /ip/arp/print proplist=address
Flags: D - DYNAMIC; C - COMPLETE
Columns: ADDRESS

ADDRESS

0 DC 10.10.23.8
1 DC 10.10.23.11
2 DC 10.10.23.1
3 DC 10.10.23.10
4 DC 10.10.23.2

[rex@test] > /ip/arp/print proplist=address where address=10.10.23.8
Flags: D - DYNAMIC; C - COMPLETE
Columns: ADDRESS

ADDRESS

0 DC 10.10.23.8

[rex@test] > :global addr 10.10.23.8
[rex@test] > /ip/arp/print proplist=address where address=$addr
Flags: D - DYNAMIC; C - COMPLETE
Columns: ADDRESS

ADDRESS

0 DC 10.10.23.8

[rex@test] > /ip/arp/print proplist=address where address=$address ; # useless, print where address is = the address itself, so print all
Flags: D - DYNAMIC; C - COMPLETE
Columns: ADDRESS

ADDRESS

0 DC 10.10.23.8
1 DC 10.10.23.11
2 DC 10.10.23.1
3 DC 10.10.23.10
4 DC 10.10.23.2

[rex@test] > /ip/arp/find [:put $address]
10.10.23.8
10.10.23.11
10.10.23.1
10.10.23.10
10.10.23.2

[rex@test] > /ip/arp/find [:if ($address=10.10.23.8) do={:put $address}]
10.10.23.8
Other example of how to use self-reference in scripts

/interface ethernet find [:if ($name~"ether") do={/interface list member add interface=$name list="VLAN2"}]

$name is automatically filled in for each result found by find without needing to use foreach

Thanks, that makes sense. Do you know if that’s documented?

Also that’s news that find can take a function as an argument. Didn’t see it documented either.

I understand that it is not documented, but I have known about it for years.
If I had time I should write the manual for “ScriptOS” myself…
Then there are other users who by dint of digging also discover new things like >[], >“”, >{}, <%% etc.
even more difficult to discover, they have to be done at random…

(when I register on 2014 to this form I always know it v6.7 syntax.. is my 2nd post on this board… :laughing: http://forum.mikrotik.com/t/script-find-where-routing-mark-stops-work-routeros6-7/74610/4 )

Ah, so the function argument of find acts as one of filters itself:

/ip/address/find [($address~“192.168.3.1/”)]

is equivalent to

/ip/address/find address~“192.168.3.1/”

Initially I thought the function argument is called only for the elements that matched the where expression.


, >“”, >{}, <%%

What devil are you summing here?

Documentation, LOL…

But yeah both “print where” and “find” acts as iterators… In the case of “print where”, you can use an as-value to suppress print’s output to “override” it with custom output per line. I think this is a good example of some real world case for the “functions in iterators”:
http://forum.mikrotik.com/t/container-traefik-on-rb5009/165849/14
(which essentially colorizes log output from Traefik container using a function in a "print).

Another example, is @optio using the both “find” and “:find” to deal with /interface/bridge/vlan vlan-ids array:
http://forum.mikrotik.com/t/append-bridge-vlan-values/147015/1

Also [:parse “/what/ever/wierd/thing/you/want to=see”] will get you an s-expression which is closer to how the script is executed. This for sure isn’t documented, but you can see it’s a stack… so function in find gets added to stack:

:put [:parse "/ip/arp/find [:if (\$address=10.10.23.8) do={:put \$address}]"]

(
evl /ip/arp/findwhere=
$address;
$mac-address;
$interface;
$published;
$status;
$invalid;
$dhcp;
$dynamic;
$published;
$complete;
$disabled;
$comment;
$.id;
$.nextid;
$.dead;
$.about;
(evl
(evl /ifcondition=
(= $address 10.10.23.9);
do=;
(evl
(evl /putmessage=$address)
)
)
)
;5
)
And there you can see all the attributes from [find] are provided on stack.

Additionally, you can also get more “hints” on how the S-expr / IL thing works from looking at the API docs on “query” - which implies some stack-based internal processing of “RouterOS list things”:
https://help.mikrotik.com/docs/spaces/ROS/pages/47579160/API#API-Queries (see also http://forum.mikrotik.com/t/how-using-query-stack-in-rest-api/173298/1 )

LOL. Latvian LISP?

But essentially those allow you “inject” some code into the S-expr / IL generated. But generally those can be “shorthand” for similar “built-in” syntax & generally operate on lists.

I got a little bit excited because the script I’m working on is full of code like:

:local varItems [/ip/address/print as-value where ...]
:foreach varI in=$varItems do={
	# process records
}

When I saw @rextended’s snippet I thought to rewrite it as

/ip/address/find where ... [
	# process records
]

But alas.

Maybe if it multiple lines you need a {} code block, since the is not a codeblock

/ip/arp/find [{
  :put $address
  :put $interface
}]

But it does act like an iterator, so the code is executed per list item.

More as APL :slight_smile:

life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}

https://en.wikipedia.org/wiki/APL_(programming_language)

Coding style may vary...
[rex@test-v7] > /ip arp find [:put "$address\r\n$interface"]
10.11.12.8
ether1
10.11.12.11
ether2
10.11.12.1
ether3
10.11.12.10
ether4
10.11.12.2
ether5

[rex@test-v7] > /ip arp find [:put $address; :put $interface]
10.11.12.8
ether1
10.11.12.11
ether2
10.11.12.1
ether3
10.11.12.10
ether4
10.11.12.2
ether5

example for print nicely

[rex@test-v7] > /ip arp find [:put "$address\t$interface"]
10.11.12.8 ether1
10.11.12.11 ether2
10.11.12.1 ether3
10.11.12.10 ether4
10.11.12.2 ether5

example of creating one array ip/interface

[rex@test-v7] > :local ip2if ; /ip arp find [:set ($ip2if->[:tostr $address]) $interface] ; :put ($ip2if->10.11.12.10)
ether4

example of creating one array ip -> interface and mac

[rex@test-v7] > :local ip2ifmac ; /ip arp find [:set ($ip2ifmac->[:tostr $address]) ({"mac"=$"mac-address"},{"if"=$interface}) ] ; :put ($ip2ifmac->10.11.12.10->"if")
ether4
[rex@test-v7] > :local ip2ifmac ; /ip arp find [:set ($ip2ifmac->[:tostr $address]) ({"mac"=$"mac-address"},{"if"=$interface}) ] ; :put ($ip2ifmac->10.11.12.10->"mac")
64:D1:54:DE:AD:BF

Regarding OP: this behavior is indeed documented https://help.mikrotik.com/docs/spaces/ROS/pages/47579229/Scripting#Scripting-Reservedvariablenames

Regarding my “alas”: the syntax works but it does not do what I initially expected. It’s executed as part of the where expression and acts as an independent condition. It’s executed for every item regardless of whether prior conditions matched*. Moreover it’s a condition itself:


> /ip/address/find where [false]
> /ip/address/find where [true]
> /ip/address/find where [($address~"^192.168.1.1/")]
  • which IMO is wrong since the conditions of the where expression are ANDed. Thus if the prior condition was false the scripting engine should stop further evaluation right there as an optimization. So technically supplying the condition should have worked, but alas.

As per my previous examples, is not a condition, it is executed for every single value returned by find/print.


Wrong syntax, do not work, because… is wrong…

Correct syntax (find->print to… print something):

> /ip address print where [(false)]
> /ip address print where [(true)]
> /ip address print where [($address~"^192.168.1.1/")]

/ip address print where [(false)]
print nothing because… really?

/ip address print where [(true)]
print obviously all

/ip address print where [true] or /ip address print where [false]
print obviously all because [false] return nothing, without missing ( ), and with nothing to check, print all…

I beg a pardon but the code snippets are valid syntax. That they don’t print anything is the intended behavior.

The expression inside is a condition:


> :put [:len [/ip/address/find]]                 
8
> :put [:len [/ip/address/find where [(true)]]]     
8
> :put [:len [/ip/address/find where [($interface~"bridge")]]]   
1
> :put [:len [/ip/address/find where [(false)]]]                 
0

As you can see “find” found only those items for which the expression inside evaluated to true.

Why this “clarification”?
In the previous post the invalid syntax was in the lack of parentheses around true and false
Obviously it works if the parentheses are used correctly.

I think what @Kentzo is trying to say is that a bool (true/false) return value from the should be used as part of the filter.
i.e. if [expression] evaluated to false, then the outer find should NOT include that element in outer [find]. And, logically, that would make sense.

But string “false” or “true” is actually NOT an expression itself in scripting, so yes the parentheses are what make it an expression. And expression is what you need for either /…/find or a […].