Appending file within foreach

I would like to save a file with the values discovered during the foreach loop below.

But, I’m having a hard time with appending values to the variable. I believe I need to use an array, but it’s beyond me at this time.

# Collect IP addresses

:foreach neighborID in=[/ip address find] do={
     :local nb        [/ip address get $neighborID]
     :local id        [:pick ("$nb"->".id") 1 99]
    
    :foreach key,value in=$nb do={
        :local newline     [:find $value "\n"]
 
        :if ([$newline]>0) do={
            :set value       [:pick $value 0 $newline]
        }
 
        :if ($key~"add") do={
            :log info message="script=IP-ADDRESSES nid=$id value=$key=\"$value\""
            :log info message="System IP Address $value"

            :local tempipaddressvalue $value 
            :local ipaddressvalue "$tempipaddressvalue  \n $value"
            :local interimipaddressvalue [get tempaddressvalue contents]
            :local newipaddressvalue "$interimipaddressvalue$value"
            :local finalipaddressvalue
            set $finalipaddressvalue contents=$newipaddresscontents
      }
    }
}

/file add name=finalipaddressvalues contents=$finalipaddressvalues

Help would be appreciated.

I’ve made some progress – it works, I think:

# Collect IP addresses
:global ipaddressvalues [:toarray ""]

:foreach neighborID in=[/ip address find] do={
    :local nb [/ip address get $neighborID]
    :local id [:pick ("$nb"->".id") 1 99]

    :foreach key,value in=$nb do={
        :local newline [:find $value "\n"]
      
           :if ([$newline]>0) do={
            :set value [:pick $value 0 $newline]
            }

            :if ($key~"add") do={
#          :log info message="script=IP-ADDRESSES nid=$id value=$key=\"$value\""
#          :log info message="System IP Address $value"

               :set ipaddressvalues ($ipaddressvalues,$value)
              }
       }
  }
 :log info $ipaddressvalues

Still not exactly what I’d like.

This code produces the output below:

/file 
remove systeminfo

/system
:local cdate [clock get date] 
:local yyyy  [:pick $cdate 0  4]
:local MM    [:pick $cdate 5  7]
:local dd    [:pick $cdate 8 10]
:local identitydate "$[identity get name]_$yyyy-$MM-$dd"

# Collect System Resources
/system resource
  :local cpuload [get cpu-load]
  :local freemem ([get free-memory]/1048576)
  :local totmem ([get total-memory]/1048576)
  :local freehddspace ([get free-hdd-space]/1048576)
  :local totalhddspace ([get total-hdd-space]/1048576)
  :local up [get uptime]
  :local sector [get write-sect-total]

#:log info message="free_memory=$freemem MB total_memory=$totmem MB free_hdd_space=$freehddspace MB total_hdd_space=$totalhddspace MB cpu_load=$cpuload uptime=$up write-sect-total=$sector"

:local resources
:set $resources "free_memory=$freemem MB total_memory=$totmem MB free_hdd_space=$freehddspace MB total_hdd_space=$totalhddspace MB cpu_load=$cpuload uptime=$up write-sect-total=$sector"
 
:log info $resources

# Collect system information
  :local model na
  :local ffirmware na
  :local cfirmware na
  :local ufirmware na
  :local version ([/system resource get version])
  :local board ([/system resource get board-name])
  :local identity ([/system identity get name])
    :do {
        :if ($board!="CHR" OR $board!="x86") do={
          /system routerboard
          :set model ([get model])
          :set ffirmware ([get factory-firmware])
          :set cfirmware ([get current-firmware])
          :set ufirmware ([get upgrade-firmware])
        }
    } on-error={}
#:log info message="version=\"$version\" board-name=\"$board\" model=\"$model\" identity=\"$identity\""

:local info
:set $local  "version=\"$version\" board-name=\"$board\" model=\"$model\" identity=\"$identity\""
:log info $local

# Collect IP addresses
:local  ipaddressvalues [:toarray ""]

:foreach neighborID in=[/ip address find] do={
    :local nb [/ip address get $neighborID]
    :local id [:pick ("$nb"->".id") 1 99]

    :foreach key,value in=$nb do={
        :local newline [:find $value "\n"]
      
           :if ([$newline]>0) do={
            :set value [:pick $value 0 $newline]
            }

            :if ($key~"add") do={
#          :log info message="script=IP-ADDRESSES nid=$id value=$key=\"$value\""
#          :log info message="System IP Address $value"
               :set ipaddressvalues ($ipaddressvalues,$value)
              }
       }
  }

 :log info $ipaddressvalues

/file
add name=systeminfo contents="$resources\r\n$local\r\n$ipaddressvalues"

#/tool fetch upload=yes mode=ftp ascii=no src-path="/systeminfo.txt" dst-path="/mikrotik-backups/$[$identitydate]-systeminfo.txt" address=192.168.2.22 port=21 user=mikrotik password=mikrotik

The file looks like this:

free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
192.168.0.13/24;free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.10.100.15/24;free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.0.0.1/24;free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.0.0.1/24;free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
192.168.88.100/24

But what I was hoping for would be something like this:


free_memory=619 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=2 uptime=1w4d00:47:59 write-sect-total=7769253
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
192.168.0.13/24;10.10.100.15/24;10.0.0.1/24;10.0.0.1/24;192.168.88.100/24

All is useless.

Just this:

{
/ip address
:local allIPs [:toarray ""]
[pri as-value where [:set allIPs ($allIPs,$address)]]
:put [:tostr $allIPs]
}

That is fabulous!

I incorporated it into my script (removing the set of brackets so that the variable $allIPs remains), but the output is still repeating.

/file 
remove systeminfo

/system
:local cdate [clock get date] 
:local yyyy  [:pick $cdate 0  4]
:local MM    [:pick $cdate 5  7]
:local dd    [:pick $cdate 8 10]
:local identitydate "$[identity get name]_$yyyy-$MM-$dd"

# Collect System Resources
/system resource
  :local cpuload [get cpu-load]
  :local freemem ([get free-memory]/1048576)
  :local totmem ([get total-memory]/1048576)
  :local freehddspace ([get free-hdd-space]/1048576)
  :local totalhddspace ([get total-hdd-space]/1048576)
  :local up [get uptime]
  :local sector [get write-sect-total]

#:log info message="free_memory=$freemem MB total_memory=$totmem MB free_hdd_space=$freehddspace MB total_hdd_space=$totalhddspace MB cpu_load=$cpuload uptime=$up write-sect-total=$sector"

:local resources
:set $resources "free_memory=$freemem MB total_memory=$totmem MB free_hdd_space=$freehddspace MB total_hdd_space=$totalhddspace MB cpu_load=$cpuload uptime=$up write-sect-total=$sector"
 
:log info $resources

# Collect system information
  :local model na
  :local ffirmware na
  :local cfirmware na
  :local ufirmware na
  :local version ([/system resource get version])
  :local board ([/system resource get board-name])
  :local identity ([/system identity get name])
    :do {
        :if ($board!="CHR" OR $board!="x86") do={
          /system routerboard
          :set model ([get model])
          :set ffirmware ([get factory-firmware])
          :set cfirmware ([get current-firmware])
          :set ufirmware ([get upgrade-firmware])
        }
    } on-error={}
#:log info message="version=\"$version\" board-name=\"$board\" model=\"$model\" identity=\"$identity\""

:local info
:set $local  "version=\"$version\" board-name=\"$board\" model=\"$model\" identity=\"$identity\""
:log info $local

/ip address
:local allIPs [:toarray ""]
[pri as-value where [:set allIPs ($allIPs,$address)]]
#:log info [:tostr $allIPs]
:local IPaddresses [:tostr $allIPs]

/file
:log info $IPaddresses
add name=systeminfo contents="$resources\r\n$local\r\n$allIPs"

#/tool fetch upload=yes mode=ftp ascii=no src-path="/systeminfo.txt" dst-path="/mikrotik-backups/$[$identitydate]-systeminfo.txt" address=192.168.2.22 port=21 user=mikrotik password=mikrotik

Output:

free_memory=618 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=1 uptime=1w4d01:47:40 write-sect-total=7770230
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
192.168.0.13/24;free_memory=618 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=1 uptime=1w4d01:47:40 write-sect-total=7770230
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.10.100.15/24;free_memory=618 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=1 uptime=1w4d01:47:40 write-sect-total=7770230
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.0.0.1/24;free_memory=618 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=1 uptime=1w4d01:47:40 write-sect-total=7770230
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
10.0.0.1/24;free_memory=618 MB total_memory=1024 MB free_hdd_space=74 MB total_hdd_space=128 MB cpu_load=1 uptime=1w4d01:47:40 write-sect-total=7770230
version="7.16.1 (stable)" board-name="hAP ax^3" model="C53UiG+5HPaxD2HPaxD" identity="JRS-Router-SwitchAP-hAPax3"
192.168.88.100/24

If your willing to have the data as JSON, the newer [:serialize] makes quick work of this:

/file/add name=test.json contents=[:serialize to=json [/system/resource/print as-value] option=json.pretty]

For example, the output looks like:
:put [:serialize to=json [/system/resource/print as-value] option=json.pretty]

{
“architecture-name”: “arm”,
“bad-blocks”: 0,
“board-name”: “RB1100AHx4 Dude Edition”,
“build-time”: “2024-10-18 08:32:57”,
“cpu”: “ARM”,
“cpu-count”: 4,
“cpu-frequency”: 533,
“cpu-load”: 1,
“factory-software”: “6.41.3”,
“free-hdd-space”: 29204480,
“free-memory”: 933613568,
“platform”: “Amm0”,
“total-hdd-space”: 134217728,
“total-memory”: 1073741824,
“uptime”: “1970-01-01 16:39:17”,
“version”: “7.17beta4 (testing)”,
“write-sect-since-reboot”: 52724,
“write-sect-total”: 18742140
}

One useful property of this approach is the data can be read back, just as easily. So you can read the stored array from a script later as an array again, with one line:

:global sysresources [:deserialize from=json [/file/get test.json contents]]
:put ($sysresources->"uptime")



16:41:21

The astute observer might notice that the RouterOS types are preserved in the process above. “uptime” is a “time” type, that get persistent in file as ISO data with secs since epoch, and :deserialize correctly gets this back to the 16h uptime here.

Anyway, maybe you need your own format… but in modern RouterOS using JSON to store RouterOS arrays is likely a better approach generally IMO.

If I write something, it mean is it needed…

:put [:tostr $allIPs]


Do not forget to correct ALL:

add name=systeminfo contents=“$resources\r\n$local\r\n**$allIPs**”
==>>
add name=systeminfo contents=“$resources\r\n$local\r\n**$IPaddresses**”

Wow!

All that code cut down to the following and providing even more detail in a far more standardized and easy to work with format:

/file/add name=resour.json contents=[:serialize to=json [/system/resource/print as-value] option=json.pretty]

/file/add name=ips.json contents=[:serialize to=json [/ip/address/print as-value] option=json.pretty]

I don’t understand the notion of persistent in file.

Thank you!

Didn’t mean to be confusing, perhaps “saved to a file” be clear. I just meant that your variables you [:serialize to=json] to file, come back as the same str/num/time/array type when you [:deserialize from=json] from same JSON file.

And, getting variables BACK from a disk is often the trickier part, than writing something to disk…

I find that if I blindly copy then I am not using my brain, but if I use my brain then I am ignoring the experts -- ugh.

The reason I removed the

:put [:tostr $allIPs]

is because I did not want to use the :put command to send the output to the terminal Isn't that what :put does?

Instead, I modified it like this to create a new variable $IPaddresses as a string of $allIPs so that I can output to the log as well as saving it to the file.

But I did not catch the error using $allIPs insteaf of $IPaddresses on the /file add line -- thank you.

/ip address
:local allIPs [:toarray ""]
[pri as-value where [:set allIPs ($allIPs,$address)]]
#:log info [:tostr $allIPs]
:local IPaddresses [:tostr $allIPs]

:log info $IPaddresses

/file
add name=systeminfo contents="$resources\r\n$info\r\n$IPaddresses"

Fair enough. Again, it’s about the variable types… And specifically the array type, since those are a bit complex.

@rextended makes a point the unneeded parenthesis ( ) can get you in trouble. The () can do a few things, so you have to be careful. i.e. () create a list of things & how you append items to an another array/list & group math operations too. And the types of the variable inside (and whether you ; or , to seperate things) determine which of those it will do. So when you ([ /cmd ]), you may accidentally create a “a list of lists” with (). Exactly where/when depends here on what exactly the command returns.

Similarly, array types should never be used in strings – that is another way to get into trouble. Why again @rextendeded highlighted the [:tostr $allIPs]. So in something like “$resources\r\n$local\r\n$allIPs” - the variable all should be either strings or numbers, never an array. You can do the array type conversion to a string inside the string, and that be okay… like:
“$resources\r\n$local\r\n$[:tostr $allIPs]”.

So basically () might get you a wrong type of array, and without some [:tostr] you can end up without any data in the string.

You might want to look at @Dru’s YouTube videos on RouterOS types and arrays:
https://www.youtube.com/watch?v=9SeYC_s95rw&list=PLXr-HoBo2VtU2OgKCfuuIYnAS7ZxjR11j&index=3
https://www.youtube.com/watch?v=eWCJw0uZ-lE&list=PLXr-HoBo2VtU2OgKCfuuIYnAS7ZxjR11j&index=2