Would like new data appended to file

I didn’t give an explanation, I provided a solution,
which is very different from complaining without providing one.

I quote myself:

“monitor 0” must be replaced with something like
monitor ([find]->0)
or
monitor [find where name=“upsname”]
but I do not have any UPS to test it.

monitor ([find]->0)
is the same of “first you find”, the array is on base 0.

“0” in “monitor 0” is not any ID.
for see the correct id you must write something like :put ([find]->0)
The first item usually have ID *1 but configuration changes can change the ID.

the use of “monitor 0” & Co. are just helpers when is used nteractive terminal for manual commands,
but not are for scripts.

It works by itself, but then if there are multiple routes, how does it choose the right one if it’s not the first one?

In fact, it’s not that the “script fails”, rather it either does nothing, or does it the wrong route.

Yes, of course. If you have many routes, it won’t choose the right one, but they just wrote “script will fail”, and later they wrote it “will throw an error”, so it’s strange and confusing. It won’t fail and won’t throw an error (if only there will be no any routes at all). It just won’t give you any good and reliable result.

Trying to experiment and learn, and (as always), it’s not going smoothly.

The following do not work (error in log: “Script Error: missing value(s) of argument(s) in”):


:local voltage (([/system ups monitor ([find] -> 1) once as-value]->"line-voltage")/100)

:local voltage (([/system ups monitor ([find] -> 0) once as-value]->"line-voltage")/100)

:local voltage (([/system ups monitor ([find -> 1]) once as-value]->"line-voltage")/100)

:local voltage (([/system ups monitor ([find -> 0]) once as-value]->"line-voltage")/100)


When I issue the following from a terminal prompt it does not work:

:local voltage (([/system ups monitor ([find] -> 1) once as-value]->"line-voltage")/100)

:local voltage (([/system ups monitor ([find] -> 0) once as-value]->"line-voltage")/100)

:local voltage (([/system ups monitor ([find] -> 1) once as-value]->"line-voltage")/100)

When I issue the following from a terminal prompt, with or without the { } (which I learned here basically means execute the command as if it were in a script) it works:

:local voltage (([/system ups monitor ([find -> 1]) once as-value]->"line-voltage")/100)


line 2 of 3> monitor ([find -> 1])  
         on-line: yes     
    runtime-left: 1h52m55s
  battery-charge: 100%    
    line-voltage: 120V    
  output-voltage: 120V    
            load: 7%      
-- [Q quit|D dump|C-z pause]

In case it’s useful:

[admin@729hAPax3] /system/ups> print
Flags: X - disabled; I - invalid 
 0    name="ups1" port=usbhid1 offline-time=5m min-runtime=5m alarm-setting=immediate model="EC650LCD" serial="BF02206B5C2.x" load=7% on-line=yes 

Not really-really, from what I have understood, the curly brackets should mean more “put the contents inside a self-contained environment” which is what is needed to have the :local variable definitions be valid.
The local variables are local to either the script in which they are defined or to the environment they are in, on “normal” command line they are valid only until you end the line with [ENTER].

[find -> x] is pointless by itself. [find] -> 1 is also pointless, if you have only one ups. [find]->0 should be valid.

I don’t know, why
:local voltage (([/system ups monitor ([find] -> 0) once as-value]->"line-voltage")/100) doesn’t work. May be because of spaces, they cause problems in some cases. UPD: Though, no, it doesn’t matter.

I don’t have ups, but for lte interface the same approach works fine.

:put ([/interface/lte/monitor ([find] -> 0) once as-value]->"session-uptime")
11:12:20

RouterOS 7.16.2, no real UPS but…

[te@st] /system/ups> export         
# 2025-07-12 13:11:33 by RouterOS 7.16.2
# software id = 
#
/system ups
add alarm-setting=none check-capabilities=no disabled=yes name=dummy1
add alarm-setting=none check-capabilities=no disabled=yes name=dummy2
[te@st] /system/ups> :put [monitor ([find]->0) once as-value]                                                                                           
.id=*4
[te@st] /system/ups> 

@jaclaz

Thank you so much for the clear explanation. Curly Brackets “{}” means create “a self-contained environment” which means, I assume among other things, that all locally defined variables are retained within this new environment.

Is it correct to say that this retention of the local variables is the reason why a script (which often has local variables) bounded the {} in a terminal works? Is there anything else this self-contained environment ‘brings to the table’?

@teslasystems

I thought the same thing: That [find]->1 would find nothing in an environment with a single UPS. But, I was wrong: [find]->0 finds nothing.

I should have included before my post today that I tried all variations with a space before and after, as well as no space before and after the “->” – it did not change the results.

@rextended

Am I reading the following correctly: UPS number 0 (not id number, just “number,” whatever that refers to) has .id=1

[admin@729hAPax3] /system/ups> :put [monitor ([find]->0) once as-value]
.id=*1;battery-charge=100;line-voltage=11900;load=7;on-line=true;output-voltage=121
00;runtime-left=01:52:55
[admin@729hAPax3] /system/ups> :put [monitor ([find]->1) once as-value] 
invalid value for argument numbers
[admin@729hAPax3] /system/ups> :put [monitor ([find]->2) once as-value] 
invalid value for argument numbers
[admin@729hAPax3] /system/ups>

Whaat??

That’s pretty much how I feel 86% of the time.

I mean first you say [find]->0 finds nothing and then you post an output, where it finds everything. One contradicts the other.

Not exactly.

It doesn’t work as used in the script.

The recently posted output was from the command issued in a terminal window.

If there is a contradiction it doesn’t originate with me (that is, I’m just the messenger, albeit perhaps not fluent in the language).

For me this works. For ethernet interfaces instead of UPS:

From the CLI:

[admin@xxx] > :put [ /interface/ethernet/monitor ([find]->0) as-value once ]
.id=*2;advertising=10M-baseT-half;10M-baseT-full;100M-baseT-half;100M-baseT-full;1G-baseT-full;2.5G-baseT;auto-negotiation=done;
comment=wan;full-duplex=true;link-partner-advertising=10M-baseT-half;10M-baseT-full;100M-baseT-half;100M-baseT-full;1G-baseT-ful
l;name=ether1;rate=1Gbps;rx-flow-control=true;status=link-ok;supported=10M-baseT-half;10M-baseT-full;100M-baseT-half;100M-baseT-
full;1G-baseT-half;1G-baseT-full;2.5G-baseT;tx-flow-control=true

The equivalent from script:

:log info [ /interface/ethernet/monitor ([find]->0) once as-value ]

Logs the same. What exactly is not working for you?

No, has .id=*1 and is *1. not only 1
What I wrote is exactly what I wrote, if I wrote ([find]->0) is ([find]->0) not ([find]->1) or ([find → 0]) etc.

If you still mix paste on termial and script launch you obtain nothing, just confusion.

For “paste” the script on terminal, you must use { at start and } at end:

{
:local rawvolt  ([/system ups monitor 0 once as-value]->"line-voltage")
:local theshold 11500
[...]
       /file add name=$filename contents=$logline
    }
}
}

@Josephny
Yep, It seemed to me you were mixing cause and effect.
Curly brackets make a script runnable on command line because they create a “local” environment in which local variables are valid, just like a script creates its own environment and local variables are valid in It.
Cannot say what other feature this environment has.

Okay, I’m working on it.

The following is from a terminal/CLI entry:

[admin@729hAPax3] /system/ups> :put [monitor ([find]->0) once as-value]            
.id=*1;battery-charge=100;line-voltage=12100;load=7;on-line=true;output-voltage=122
00;runtime-left=01:52:55

[admin@729hAPax3] /system/ups> :put [monitor ([find]->1) once as-value] 
invalid value for argument numbers

This shows that the first UPS (the one found using “([find]->0)”) has .id=*1 (not “1”) – is that correct?

And, the following script runs correctly (log entry: “[UPS]: UPS input voltage is 121”):

:local upsPrefix "[UPS]:"
:local voltage (([/system ups monitor ([find]->0) once as-value]->"line-voltage")/100)
:if ($voltage < 125) do={
  :log info "$upsPrefix UPS input voltage is $voltage"
}

On a different device:

[admin@125hAPax3] /system/ups> :put [monitor ([find]->0) once as-value]
.id=*2;battery-charge=100;line-voltage=11700;load=3;on-line=true;output-voltage=117
00;runtime-left=07:30:00

This shows that the first UPS has .id=*2

Do I understand that “([find]->0)” finds the first UPS? And that the first UPS tells us nothing about the .id number? And that *1 or *2 is the id number – not 1 or 2?

Normally that [find] (you can also include additional filter conditions of course; and also this is not the :find command that is used for substring) returns an array of internal IDs. It’s a list of items, and you can access the value of any items in the list by specifying the index (the position) of the item. Arrays in RouterOS scripts use zero-based indexing, which means the first item of the list has the index 0.

And the syntax for accessing item in arrays is $array->index.

Which means [find]->0 gets the list of internal IDs of all available UPS (because find has no additional filter options) as array and then picks the first item of that array.

Please note that I wrote above about arrays as returned by [find] which are similar to traditional indexed arrays in other programming languages. But arrays in RouterOS are actually similar to those in PHP and can also have elements with named keys (associative arrays).

The output above:

.id=*1;battery-charge=100;line-voltage=12100;load=7;on-line=true;output-voltage=122

Is an associative array, with .id, battery-charge, etc… being “keys”. And you use the same -> operator to access an element with the named key.

Let’s go back to the problem. Cut-and-paste CLI fragments while talking about a /system/script blocks any meaning help.

Basic questions:

  1. Do you have 1 or 2 UPS on one single router and logging? Need help figuring out logging in this case.

  2. Or, you need a generic script that deals with logging one, or more, UPS generically on with any UPS, on any router.

If it’s closer to 1, then you don’t need find. You just need to use the .id from /system/ups/print show-ids and then use that .id in your script. No [find] thing needed. This is covered by #1 in MikroTik’s “Scripting Tips and Tricks”

If it’s closer to 2, then you need a :foreach someplace, something structured like this (just for example, so that works in CLI or script)

/system/ups {
   :foreach ups in=[find] do={
       :log info [:put [monitor $ups once as-value]]
    }
}

If you want to learn about how lists/array’s work – which is what [find] gives you (specifically always a array/list of .id string values — which is why you need the ->0 even if there is only one, [find] returns a list of 1 item).

MirkoTik has YouTub videos on data types and arrays here:
Scripting: variable data types
Scripting: working with Arrays

@Amm0
Thanks for the… hint…

But you wrote two things that shouldn’t be done unnecessarily:
unnecessarily opening { } after a path,
you used ups as the variable name, which is already the path name.

This remove all problems logging all UPS in the system:

/system ups
:foreach item in=[find] do={
    :local iupsname [get $item name]
    :local rawvolt  ([monitor $item once as-value]->"line-voltage")
    :local theshold 11500
    :local voltage  ($rawvolt / 100)
    :local cdate    [/system clock get date]
    :local ctime    [/system clock get time]
    :local filename "upsvoltagehistory.txt"

# NAND flash or XOR flash ?
    :if ([:len [/file find where name="flash" and type="disk"]] = 1) do={:set filename "/flash/$filename"}

    :if ($rawvolt < $theshold) do={
        :log info "UPS $iupsname input voltage is $voltage"
        :local logline "$cdate $ctime $iupsname $voltage"
        :local fileid  [/file find where name=$filename]
        :local howmany [:len $fileid]
        :if ($howmany > 0) do={
# if file exist
           :local previouscontent [/file get $fileid contents]
           /file set $fileid contents="$previouscontent\r\n$logline"
        } else={
# if file do not exist
           /file add name=$filename contents=$logline
        }
    }
}

That is such a great explanation! Thank you.

I had no idea what the “->” meant, so I couldn’t have possibly understood that the [find] command (with or without any additional filters) returns the Nth item in an array as specified by the number after “->”.

And, that the returned item will be entire (is that correct) associative array, and that elements in that array element can also be accessed indvidually.

So much to learn!