Isolate a value from a console output

I wanted to capture the WAN address of my HAP AX3 router.
I can see the address by issuing the print command.

[@MikroTik] > ip address/print proplist=address where interface=ether1_WAN
Flags: D - DYNAMIC
Columns: ADDRESS
#   ADDRESS
2 D 1.2.3.4/21

Now, I wanted to isolate the address, something like “grep -o”.
Is there a way to print a single value from a console output?
Is there a regex function?

If you want to store it a variable it looks like:

:global wanIP [/ip/address get [find interface=ether1_WAN] address]

You can then use that variable in future command. To output it, it’s just:
:put $wanIP
Or, in a string like
:put “my WAN IP is $wanIP”

Alternatively, if you using DDNS in IP > Cloud, you can also get the detected WAN address via
:put [/ip cloud get public-address]

In the “find”, “print where”, or in an :if like statement you can use the tilde to match a variable or attribute against a regex:
attribute~“.*”
Only boolean matches and no grouping or replacement however.

So in the find shown above, you could search for anything that starts with “ether1”

:global wanIP [/ip/address get [find interface~"^ether1"] address]

(although there is also default-name= on /interface to match on too)

I appreciate your help @Amm0.

If I use

:do {:set currentIPv4 ([/ip address/get [find interface=$wanInt] address])}
put $currentIPv4
1.2.3.4/21

The resulting address contains the network mask.
Now, I need to remove this mask number, but how?

where you get the useless do {} and the other frills???

{
/ip address
:local wfaddress [get [find where interface=$wanInt] address]
:put $wfaddress
:local waddress  [:pick $wfaddress 0 [:find $wfaddress "/" -1]]
:put $waddress
}

Nevermind…
I think this works

:do {:set currentIPv4 ([/ip address/get [find interface=$wanInt] address]); :set currentIPv4 [:pick $currentIPv4 0 {[:len $currentIPv4] - 3}]}
put $currentIPv4
1.2.3.4

Until is /xx, if is, for example, /9 do not work.

Use everityme proper method.

why do you use brackets { } randomly and without any logical reason?

In a script, do you think I could drop the "do"?

ignoring the way, the syntax is this:

:set currentIPv4 [/ip address get [find where interface=$wanInt] address] ; :set currentIPv4 [:pick $currentIPv4 0 ([:len $currentIPv4] - 3)]
:put $currentIPv4

1.2.3.4

obviously this do not consider the other script part you do not have pasted, like where $currentIPv4 and $wanInt is defined, etc.

I see the limitations.
How do you propose to handle the single-digit mask lengths you mentioned above?

Read post #5
http://forum.mikrotik.com/t/isolate-a-value-from-a-console-output/174618/1

[:find $wfaddress "/" -1]
vs
([:len $currentIPv4] - 3)

Awsome…
Now it works great.

:do {:set currentIPv4 ([/ip address/get [find interface=$wanInt] address]); :set currentIPv4 [:pick $currentIPv4 0 [:find $currentIPv4 "/" -1]]};

Of course, this is just a snippet of a script. I appreciate all the valuable help.

Yes. Harmless, but unnecessary. Since :do { ... } is do'ing nothing. e.g. this is identical in function:

{:set currentIPv4 ([/ip address/get [find interface=$wanInt] address]); :set currentIPv4 [:pick $currentIPv4 0 [:find $currentIPv4 "/" -1]]}

if you want to keep it on one line.

And, yes, the ; at end is unneeded too. Newline ends a command (unless the backtick \ is used at end which mean the next line is part of this one). Semicolons only need if you want multiple commands on one line. So in your :do {} they are needed (since you don't have newlines). But there is no need to END a command with a semi-colon AND newline.

FWIW we're picky around here... since RouterOS script is also picky sometimes. :slight_smile:

You can also make it a function, if you need to do for a multiple items:

:global getInterfaceIP do={
   :local currentIPv4 [/ip address get [find interface~$1] address]
   :return [:pick $currentIPv4 0 [:find $currentIPv4 "/" -1]]
}

:put [$getInterfaceIP ether1]

Dorky side note: RouterOS has types too. So as a technical point “:return [:toip [:pick $currentIPv4 0 [:find $currentIPv4 “/” -1]]]” in above be more accurate but in nearly all contexts RouterOS will “cast” a string to IP if needed internally… so keep IP as a string likely easier in practice.

I was mostly testing on the terminal, hence the one-liners.
For a clearer understanding, I guess I should use more indentations.

Thanks for the tips. Turning the commands into a function is very helpful, indeed.

Now, without a function, what if I were to use “on-error{}”? Would that require a “do{}” statement?

:do {
    :set currentIPv4 ([/ip address/get [find interface=$wanInt] address]);
    :set currentIPv4 [:pick $currentIPv4 0 [:find $currentIPv4 "/" -1]];
  } on-error={
    :log error "IPv4 was not detected from $wanInt interface"
  };

Sure, that kinda more what’s the :do is used for.

And, above code could fail, if an interface= had multiple IP address associated with it (e.g. “multihoming”).

But… it’s often’s better if a script fails immediately, than “catching” the on-error=. If something else further needs that IP address, which is likely, you’ve just moved what is going to fail & it’s may hard to know that the issue is the IP is empty.

So, instead of

on-error={:log error ... };

I could try

on-error={
    :log error "IPv4 was not detected from $wanInt interface"
    :error "..."
};

Yup. :error will stop the script, and print message to the CLI/console. While the /log handles the case if same script run in background. So that combo covers both CLI and /system/script.

But often it better to just add some more :if (error-condition) do={:error …} checks along the way. And if something actually goes to the on-error={}, your still left wondering what may have cause the error…

For example, in your code above (or my function too), using “[find interface=$wanInt]”… if you have one interface that has 2 /ip/address records – that cause an error on the “get”. But… so would if there 0 /ip/address found for an interface. And… your on-error={} have no clue as which of those two case that was. Now a lot of times, the “why” doesn’t matter too. But PREVENTING anything from going to the on-error= is a good idea.

How to manage errors without using shitty on-error.
{
:global ipfrominterface do={
:local iface [:tostr $1]
:local ifadd 0.0.0.0
/interface
:if ([:len [find where name=$1]] != 1) do={:return “interface >$iface< do not exist”}
/ip address
:local temp [find where interface=$iface]
:if ([:len $temp] = 0) do={:return “interface >$iface< do not have one address”}
:if ([:len $temp] > 1) do={:return “interface >$iface< have more than one address”}
:local ifadd [get $temp address]
:return [:toip [:pick $ifadd 0 [:find $ifadd “/” -1]]]
}

:local iface    "ether1"
:local ifaceadd [$ipfrominterface $iface]
:if ([:type $ifaceadd] = "ip") do={
    :put "The IP address of $iface is $ifaceadd"
    :log info "The IP address of $iface is $ifaceadd"
} else={
    :put "ERROR: $ifaceadd"
    :error $ifaceadd
}

}