Community discussions

MikroTik App
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

fetch seems to behave different when called in function

Thu Feb 29, 2024 3:07 pm

I'm trying to get my feet wet with MikroTik scripts. After having a basic script I'm trying to clean it up a bit and wrap certain parts into functions.

When doing this with the fetch function it seems to behave different:
# MikroTik script to update the DNS entry when Internet connection has been
# established via PPP.
#
# Put this into the PPP profile as script on-up and assign the profile to the 
# pppoe connection.
#
# Permissions required:
#   - read
#   - test
#

:global afraidSyncURL "http://sync.afraid.org/u/random_token/";

:local updateDDNS do={
    :local result [/tool fetch url=$afraidSyncURL as-value output=user]
    :if ($result->"status" = "finished") do={
        :log info ("FreeDNS: " . $result->"data");
    }
}

:log info "FreeDNS: IP address update started";
:local result [/tool fetch url=$afraidSyncURL as-value output=user]
:if ($result->"status" = "finished") do={
  :log info ("FreeDNS: " . $result->"data");
}

$updateDDNS
:log info "FreeDNS: Update script finished"
The part
:local result [/tool fetch url=$afraidSyncURL as-value output=user]
:if ($result->"status" = "finished") do={
  :log info ("FreeDNS: " . $result->"data");
}
runs without issues but when $updateDDNS is called there is an error "failure: Mode not specified", if the mode gets specified the next error appears ("failure: Please provide IP address or host") and so on...
When the variable $afraidSyncURL gets replaced by the actual value within $updateDDNS the script works, but its not clear exactly why.

Why do the two calls behave differently?
 
trkk
just joined
Posts: 17
Joined: Tue Mar 08, 2022 1:36 pm
Location: +09:00

Re: fetch seems to behave different when called in function  [SOLVED]

Thu Feb 29, 2024 3:58 pm

When you want to access global variables/functions from inside functions, you have to write ":global varName" explicitly.
:global afraidSyncURL "http://sync.afraid.org/u/random_token/";

:local updateDDNS do={
    :global afraidSyncURL
    :local result [/tool fetch url=$afraidSyncURL as-value output=user]
    ....
}
see
https://help.mikrotik.com/docs/display/ ... ing-Scopes
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: fetch seems to behave different when called in function

Thu Feb 29, 2024 5:48 pm

Or you don't need to use global variable at all and pass local variable as function argument...
:local afraidSyncURL "http://sync.afraid.org/u/random_token/"

:local updateDDNS do={
    :local result [/tool fetch url=$1 as-value output=user]
...
$updateDDNS $afraidSyncURL
:log info "FreeDNS: Update script finished"
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Thu Feb 29, 2024 8:05 pm

Many thanks for the replies. It really bugged me why it was not working.

Now I understood that without referring to the variable it would be undefined and instead of
:local result [/tool fetch url=$afraidSyncURL as-value output=user]
I was actually calling
:local result [/tool fetch url= as-value output=user]
within the function.

As a software engineer by profession it still boggles my mind why I have to refere to already globally defined variables... but at least I understand it now. ( And stiil I hit the trap again with custom functions which are also kind of variables because I had a typo in the refered name...)

BTW: Is there a way to see error messages within the UI? For now I used to SSH into the router to see what's going on. Or is there kind of pedantic switch so it would nag about usage of undefined variables?
 
pe1chl
Forum Guru
Forum Guru
Posts: 10248
Joined: Mon Jun 08, 2015 12:09 pm

Re: fetch seems to behave different when called in function

Thu Feb 29, 2024 8:08 pm

As a software engineer by profession it still boggles my mind why I have to refere to already globally defined variables...
It is done to prevent accidental access/modification of global variables.
Remember it is not required to declare local variables, so any set command could unintentionally use a global instead of an implicitly declared local.
This has caused a lot of havoc in languages like PHP, and it seems to be a good idea to guard against that.
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 11:17 am

As a software engineer by profession it still boggles my mind why I have to refere to already globally defined variables...
It is done to prevent accidental access/modification of global variables.
Remember it is not required to declare local variables, so any set command could unintentionally use a global instead of an implicitly declared local.
This has caused a lot of havoc in languages like PHP, and it seems to be a good idea to guard against that.
I don't buy your argument. There are many projects / languages were this is no problem at all. Especially on a router where access and control of scripts should be tight, there is much less danger of "someone" interferring with your code. I would prefer to have an optional "pedantic mode" which would point out all the potential problems in a given script.
I'd take constants any time as a more convenient way to store some of the crucial data. I'd see their use case as more crucial than global variables.

And yeah, the scripting language is not a full blown programming language, but that's not an argument either as there are ways to make it safe (eg. being forced to "export" changes if you really want to change a "global" variable eg. an enviroment variable).

If preventing "changing" global would be a goal, one wouldn't be forced to use the global modifier on functions (or I'm doing it utterly wrong...) and by that "risking" overwriting variables of the same name.

For example this code does not work as expected (it prints only "func B"):
:local funcA do={
  :put "func A"
}

:local funcB do={
  :local funcA
  $funcA

  :put "func B"
}

$funcB
but one needs to change "funcA" to global to make it work, which then pollutes the global name space:
:global funcA do={
  :put "func A"
}

:local funcB do={
  :global funcA
  $funcA

  :put "func B"
}

$funcB
I'd love to know how to make deeper calls whithout using global if I'm doing wrong.
 
pe1chl
Forum Guru
Forum Guru
Posts: 10248
Joined: Mon Jun 08, 2015 12:09 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 1:01 pm

By "accidental" I only mean that you could use a variablename that someone else has already chosen to be global, and you use it as if is local.
When that cannot happen (because you work alone instead of as a team member) there is nothing to worry about.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3505
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 3:39 pm

Whole system has to fit in 16MB, so there are some limits on how many features scripting can have... so no linter to find the "use of undefined global in function". There is ":import verbose=yes <scriptfile.rsc>" that helps find what line something is failing.

On this one:

For example this code does not work as expected (it prints only "func B"):
:local funcA do={
  :put "func A"
}

:local funcB do={
  :local funcA
  $funcA

  :put "func B"
}

$funcB
but one needs to change "funcA" to global to make it work, which then pollutes the global name space:
:global funcA do={
  :put "func A"
}

:local funcB do={
  :global funcA
  $funcA

  :put "func B"
}

$funcB
I'd love to know how to make deeper calls whithout using global if I'm doing wrong.
It's annoying & I run into it... e.g. you want a few local helper functions, but the helpers function cannot use other helper function. But... local function cannot call other local function, even if same scope (e.g. do={} function or plain block {}). That's just how it works.

You can use recursion, with different args, to call your same global function to kinda proximate a local function calling a local function - just less procedural.

FWIW, a function can also be a member of an array. This is another approach to storing functions for re-use (e.g. a single global array, with function as named elements). But similar to locals, functions-in-array do not have access to the containing array (& limits use of array as "class"). And, confusingly, the stack gets shifted so $0 is first parameter to a function in an array (see viewtopic.php?t=181142)

There are a lot of oddities in RouterOS scripting.
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 4:51 pm

Local function can call other local function if passed as argument, see script in this post, all local functions and variables. Like from that script:
$escapeValue $v escapeStr=$escapeStr arrayToStr=$arrayToStr
, of course you need to pass as argument to root function all functions called from all child functions, so root function must have for all child function arguments even it is not called in it, it is just for passing to child as argument. It complicates a bit, but it works if you don't want to use global functions.
For scripts that have many functions like this
sms-read.rsc
I'm using global functions since it will be needed to pass a lot functions arguments it they are local, but in this case I'm unsettling them (:set SomeGlobalVar) after script is done just to avoid functions hanging in environment.
You do not have the required permissions to view the files attached to this post.
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 7:03 pm

Whole system has to fit in 16MB, so there are some limits on how many features scripting can have... so no linter to find the "use of undefined global in function". There is ":import verbose=yes <scriptfile.rsc>" that helps find what line something is failing.
16MB is really no argument at all. I've been running PCs from Floppies (5 1/4) and worked with Amiga which had only about 512kb of RAM and even managed to hava a full blown GUI. You can get a hell lot of stuff into 16MB.
FWIW, a function can also be a member of an array. This is another approach to storing functions for re-use (e.g. a single global array, with function as named elements). But similar to locals, functions-in-array do not have access to the containing array (& limits use of array as "class"). And, confusingly, the stack gets shifted so $0 is first parameter to a function in an array (see viewtopic.php?t=181142)

There are a lot of oddities in RouterOS scripting.
You really want to kill me :lol: My head still tries to get accustomed to "the easy things" in RouterOS scripting.

(BTW I'm just using it for my home setup, nothing fancy. Just my nerdy way of approaching the home network. Actually the main driver was that the router I had before didn't manage QoS well so when someone was downloading a bigger file at max speed it broke IPTV. At least that works now :D )
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 7:10 pm

For scripts that have many functions like this sms-read.rsc I'm using global functions since it will be needed to pass a lot functions arguments it they are local, but in this case I'm unsettling them (:set SomeGlobalVar) after script is done just to avoid functions hanging in environment.
Using (:set SomeGlobalVar) sounds like a real neat trick for the clean up. Cool, thanks! :-D I'll store it in my mental toolbox.
(Then again my brain thinks of side effects, what happens when two scripts with the same function names run concurrently, can they break each other on clean up... :-? )
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 7:37 pm

BTW my current solution was to "unroll" the script and get rid of the functions, but that feels very strange to me as I tried to train myself to seprate stuff and have small working parts and now I have to do the opposite...

Here is the result for DDNs updates used for PPP on-up events. It works nice for FreeDNS which only require you to GET a URL with a randomized token. The nice thing is that FreeDNS does all the work and puts all the information in its response so this script works also nicely when run from the shell, no need for $local-address or anything:
#
# MikroTik script to update the DNS entry when Internet connection has been
# established via PPP.
#
# Assign this script in the PPP profile as "on-up" script and assign the
# profile to the PPPoE connection.
#
# Permissions required:
#   - read
#   - test (to execute ping)
#

:global serviceName;    # eg. "FreeDNS"
:global serviceURL;     # eg. "http://sync.afraid.org/u/your_token/"

:local onlineCheck false;
{
    :local maxDelay 5;

    :local runLoop true;
    :local counter 0;
    :while ($runLoop) do={
        :set counter ($counter + 1);

        # check if Internet is up, pinging the nameserver of Quad9 in this case
        if ([:typeof ([:ping address=9.9.9.9 count=1 as-value]->"time")] = "nothing") do={
            # if max delay has been exceeded: abort
            :if ($counter > $maxDelay) do={
                :set runLoop false;
            } else={
                # bit of delay between attempts
                :delay 1;
            }
        } else={
            :set onlineCheck true;
            :set runLoop false;
        }
    }
}

:if ($onlineCheck) do={
    :do {
        :local result [/tool fetch url="$serviceURL" as-value output=user]
        :if ($result->"status" = "finished") do={
            :local response ($result->"data");

            # retrieve only the first line of $response
            :local endOfFirstLine ([:find $response "\n"]);
            if ($endOfFirstLine >= 0) do={
                :set response ([:pick $response 0 $endOfFirstLine ]);
            }

            :put "$response";
            :log info ("$serviceName: $response");
        }
    } on-error={
        :log warning "$serviceName: Failure while calling update endpoint";
    }
} else={
    :log warning "$serviceName: Failed to detect Internet => no update";
}
The globals can be set up by another script (as far as I understood the scripting system) so that this script could be easily shared, e.g. here or in my documentation about my ride to set up the router...
 
MakroTok
newbie
Topic Author
Posts: 28
Joined: Wed Dec 04, 2019 4:36 pm

Re: fetch seems to behave different when called in function

Fri Mar 01, 2024 7:47 pm

The globals can be set up by another script (as far as I understood the scripting system) so that this script could be easily shared, e.g. here or in my documentation about my ride to set up the router...
Seems omitting the value of the globals works only when called via shell or by the PPP profile. Pushing the "Run Script" button doesn't seem to infer the proper values from the enviroment. Nice, NOT! (Sorry for ranting but there are so many annoying inconsistencies...)

Who is online

Users browsing this forum: No registered users and 9 guests