Появления Роутер ОС 7 давно ждали. Новая система создавалась разумеется для более производительных устройств, имеющих новые процессоры и больше оперативной памяти. Новое ядро Linux, поддержка WireGuard и ZieroTier, новые возможности маршрутизации и BGP, новый users manager, обновление MPLS, запуск приложений в контейнерах далеко не полный список новинок.

Но в этой статье мы будем обсуждать нововведения только в скриптовом языке системы вплоть до актуальной beta версии 7.12.

Итак версия 7 (по состоянию на 7.12 beta 3) получила несколько новых команд:

:timestamp

Необходимость команды полностью очевидна. Вся работа со временем должна проводиться через вычисление универсального времени UNIX, равного числу секунд, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года; этот момент называют «эпохой Unix». Ранее, в том числе во всех ветках Роутер ОС 6, приходилось програмно вычислять UNIX time с помощью весьма объемного скрипта. Теперь для этого достаточно всего одной команды:

:put [:timestamp]

которая вычисляет Unix time с учетом часового пояса пользователя (GMT offset) не считая секунды високосных лет.

Правда формат вывода мне не нравится. Не знаю зачем, то в Рос 7 изменился и сам формат времени. Разработчики РоутерОС зачем то поддерживают вычисление количества недель.

Команда, приведенная выше выдает:

2801w06:05:12.725909345

Ну и как это понимать?

Для читабельности можно воспользоваться ещё одной новинкой – командой :tonsec. Обратите внимание, именно :tonsec, а не :tosec. Команда преобразует аргумент в количество наносекунд. Таким образом, для вычислений можно использовать такое преобразование:

:put [:tonsec [:timestamp]]

Получим такой результат:

1694066733047820422

По сути, без учета GMT

[:tonsec [:timestamp] = [:tonsec ([:totime [/system clock get date]]+[/system clock get time])]

Следующее нововведение – команда :rndnum служит для генерации случайного числа. Без параметров использоваться не может и требует указания диапазона, из которого нужно выбрать случайное число.

:rndnum from=[num] to=[num]

Например:

:put [:rndnum from=1 to=99]

Вывод:

17

Целесообразна для генерации ключей, паролей, выборки случайных значений из диапазона для чего угодно.

:rndstr from=[str] length=[num]

Команда генерации случайной строки. Может использоваться без параметра length, тогда длина строки устанавливается по умолчанию 16 символов.

:put [:rndstr from="abcdef%^&" length=33]

e^&b^accaefcbf&b%^%e&ac%feda%&%ac

Символы для генерации берутся только из указанных в «from=». Команда также очень полезна для генерации ключей и паролей.

Например, генерация случайного МАК-адреса теперь может быть проще, чем в Роутер ОС 6 (примеры взяты из этого поста официального форума Микротик:

{
:local array { "c8:ea:f8"; "70:9f:2d"; "38:3b:26"; "5c:fa:fb"; "9c:7b:ef" }   
:local part1 ($array->[:rndnum from=0 to=([:len $array]-1)])

:local hex1 [:rndstr length=6 from="0123456789abcdef"]
:local part2 ([:pick $hex1 0 2].":".[:pick $hex1 2 4].":".[:pick $hex1 4 6])

:local Mac ($part1.":".$part2)
:put $Mac
}

Или вообще скриптом в одну строку:

:put "$[:rndstr length=1 from="0123456789ABCDEF"]$[:rndstr length=1 from="048C"]$[:rndstr length=10 from="0123456789ABCDEF"]"

:retry command= delay=[num] max=[num] on-error=

Новая команда :retry призвана обеспечить выполнения инструкций в блоке «command» с максимальным количеством попыток, указанном в параметре max= с заданной задержкой между попытками delay=. При ошибке исполнения блока command выполняется блок on-error={}

Например:

:retry delay=1s max=3 on-error={:put "got an error"} command={:put [/system/identity/get name]}

# Mikrotik

Или:

:retry delay=1s max=3 on-error={:put "I give up!"} command={:put "trying..."; :error "cause error"}   

# trying…
# trying…
# trying…
# I give up!

:jobname

Наконец-то появилась команда позволяющая без «танцев с бубнами» узнать имя исполняемого скрипта.

Ранее, в Роутер ОС версии 6, для этого можно было использовать такой трюк (автор Rextended, https://forum.mikrotik.com/viewtopic.php?t=197314#p1009493):

:local UniqueScriptID "QnJhdm8h"
:local ThisScriptName [/system script get ([find where source~"$UniqueScriptID"]->0) name]
:local AlreadyRunning "Script $ThisScriptName already running"
:if ([:len [/system script job find where script=$ThisScriptName]] > 1) do={:log error $AlreadyRunning; :error $AlreadyRunning}

Скрипт искал своё имя по уникальной метке в собственном «теле».

Теперь, в Роутер ОС 7 можно так:

/system script add name=log-jobname source=\"/log info \"\$[:jobname]\""

Или просто из кода скрипта

 :log info [:jobname]

Подошли к самой сложной новой команде :convert from=[arg] to=[arg] transform=
Команда преобразует указанное значение из одного формата в другой. По умолчанию используется автоматически проанализированное значение, если формат «from» не указан (например, «001» становится «1», «10.1» становится «10.0.0.1» и т.д.). Опция transforme по умолчанию не используется, что эквивалентно transforme=none. При указании transforme=reverse преобразуемые данные переворачиваются.

from задает формат значения — base32, base64, hex, raw, rot13, uri.

to указывает формат выходного значения — base32, base64, hex, raw, rot13, uri.

Примеры:

:put [:convert 001 to=hex ]

31

:put [:convert [/ip dhcp-client/option/get hostname raw-value] from=hex to=raw ]

MikroTik

:put [:convert "abcd" to=base64]   

# YWJjZA==

:put [:convert from=base64 "YWJjZA==" ]   

# abcd

:put [:convert from=base64 "YWJjZA==" transform=reverse]

# dcba

:put [:convert from=base64 "YWJjZA==" transform=reverse to=hex] 

# 64636261

Часть новых команд коснулась только консоли.

Для упрощения ввода значений появилась новая инструкция
:terminal/ask preinput= prompt= , позволяющая вводить значения после выдаваемого в терминал preinput с приглашением из параметра prompt.

Например:

:put [:terminal/ask "Do you agree to use Router OS 7?"]

В данном случае preinput даётся без самого кодового слова, а prompt опущен.

Чтобы ввести строку из Терминала, в том числе в обоих ветках системы (6 и 7) конечно, можно написать небольшой скрипт с использованием :terminal inkey типа такого:

:local EnterString do={
     :local cont; :local string
        :while ($cont!=13) do={
          :if ([:len $string]<254) do={
             :local key ([:terminal inkey])
                :if ($key!=13) do={
                     :local char [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($key >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($key & 0xF)]\")"]]
             :set string ("$string"."$char")}
             :set cont $key
         }
      }
:return $string}

Эта небольшая локальная функция позволяет ввести строку длиной до 254 символов посимвольно. В этом примере ввод прекращается, если пользователь нажимает клавишу «Enter» (код клавиши Enter=$0D (13). Разумеется посимвольный ввод позволяет использовать для конца ввода любые другие ограничения (например другой символ окончания или количество вводимых символов), что позволяет наиболее гибко настроить ввод.

Её можно дополнить разными плюшками с моментальной проверкой вводимых символов, ограничивать длину формируемой строки и.т.д…

Но, если нужно проще и быстрее можно использовать такую фичу:

:local input do={:put $1; :return;}
:local login [$input "Enter login:"]
:local password [$input "Enter password:"]
:put "Login is [$login] and password is [$password]"

Работает это так:

Enter login
value: admin
Enter password
value: test
Login is [admin] and password is [test]

Фишка в том, что пустой :return из Терминала всегда предлагает ввести значение. Окончанием ввода обязательно служит клавиша «Enter».Единственным неудобством будет вывод слова «value:» в качестве приглашения перед вводом, но если это не раздражает, то всё нормально работает.

Удобно использовать из интерактивных скриптов настройки конфигураций и т.д…

В Роутер ОС 7, начиная с версии 7.11 появилась ещё одна возможность — использование :terminal ask. Эта новая команда лишена неудобства, связанного с выводом приглашения ко вводу значения :value. Окончанием ввода также обязательно служит клавиша «Enter».

:global Answer [:terminal/ask "Do you agree to use Router OS 7?"]
:put $Answer

Ну или как-то так (для примера):

{
:global Answer []
:while ($Answer!="yes") do={
:set Answer [:terminal/ask "Do you agree to use Router OS 7?"]
}
}

Можно также задавать любое приглашение ко вводу, если использовать параметр «preinput»:

:global userinput [/terminal/ask preinput="preinput>" prompt="Some text that in prompt"]

Очень полезная новая команда:

:console/inspect as-value request= path=

Инструкция позволяет запросить список команд и параметров дочернего меню.

Гуру официального форума Микротик Amm0 написал скрипт, выполняющий обход всего дерева команд Роутер ОС и получающий список всех команд, параметров и их типов:

:global ast [:toarray ""]

:global mkast do={
    :global mkast
    :global ast
    :local path "" 
    :if ([:typeof $1] ~ "str|array") do={ :set path $1 }
    :local pchild [/console/inspect as-value request=child path=$path]
    :foreach k,v in=$pchild do={
        :if (($v->"type") = "child") do={
            :local astkey ""
            :local arrpath [:toarray $path]
            :foreach part in=$arrpath do={
                :set astkey "$astkey/$part"
            }
            :set ($ast->$astkey->($v->"name")) $v
            :put "Processing: $astkey $($v->"name") $($v->"node-type")"
            :local newpath "$($path),$($v->"name")"
    		# TODO use [/console/inspect as-value request=syntax path=$path]
            [$mkast $newpath]
        }
    }
    return $ast
}

# & this call start the recursion 
:put [$mkast]


 :put ($ast->"/ip/address")

add=.id=*2;name=add;node-type=cmd;type=child;comment=.id=*3;name=comment;node-type
=cmd;type=child;disable=.id=*4;name=disable;node-type=cmd;type=child;edit=.id=*5;n
ame=edit;node-type=cmd;type=child;enable=.id=*6;name=enable;node-type=cmd;type=chi
ld;export=.id=*7;name=export;node-type=cmd;type=child;find=.id=*8;name=find;node-t
ype=cmd;type=child;get=.id=*9;name=get;node-type=cmd;type=child;print=.id=*a;name=
print;node-type=cmd;type=child;remove=.id=*b;name=remove;node-type=cmd;type=child;
reset=.id=*c;name=reset;node-type=cmd;type=child;set=.id=*d;name=set;node-type=cmd
;type=child

Ещё одна новая инструкция консоли :task по слухам аналогична инструкциям Linux jobs, fg и bg, позволяющими узнать какой процесс выполняется, переместить процесс в фон или наоборот сделать его первым активным и т д… Но пока точных данных как работает команда и какие параметры имеет у меня нет.

Некоторые изменения коснулись команды :execute. Она получила новый атрибут «as-string», которая делает его синхронным (например, ожидает завершения скрипта, прежде чем вернуться с новым «as-string»).

Также в версии 7 введена опция "as-value" для "/tool /snmp-get", которая может получать значения из SNMP OID в скрипт без синтаксического анализа, требуемого в версии 6:

{
   :local interfaceOneName [/tool/snmp-get oid=.1.3.6.1.2.1.2.2.1.2.1 address=127.0.0.1 community=public as-value] 
   :put $interfaceOneName
   :put ($interfaceOneName->"value")
}

# oid=1.3.6.1.2.1.2.2.1.2.1;type=octet-string;value=ether1
# ether1

Наконец улучшена работа консоли при создании сложных массивов:

*) console — improved multi-argument property parsing into array;

Не знаю, что конкретно разработчики Микротик имели ввиду, но гуру форума Микиротик уверяют, что сложные массивы в РОС 7 перестали «рассыпаться на указатели», теперь при копировании массива происходит именно его копирование в памяти (мы ведь помним, что у новых устройств Микротик оперативной памяти теперь хватает !), а не создания указателя. То есть массивы — это копии в версии 7, а не «указатели». Можно говорить о («передаче по значению» в версии 7, вместо «передаче по ссылке» в версии 6).

Наглядно проблему иллюстрирует следующий скрипт:

# the mother array
:global aGlobalArray ({});    

# function to add a sub-array, defined locally, to the global array
:global popData do={
    :global aGlobalArray;        
    :local subObj ({soname="soname-orig"; sodata="sodata-orig"});
    :set ($aGlobalArray->"sub-obj-1") $subObj;
    :return "OK";
}

:log info "Populate and log";
:local pop [$popData];
:foreach key,value in=$aGlobalArray do={  
    :log info ("[key:".$key."] soname:".($value->"soname")." | sodata:".($value->"sodata"));
    # result [key:sub-obj-1] soname:soname-orig | sodata:sodata-orig
}

:log info "Modify subjobj and log";
:foreach key,value in=$aGlobalArray do={  
    :set ($value->"sodata") "sodata-modified";
    # logging the data here shows it is modified    
    # Adding this line fixes the issue for v7 but is not needed in v6
    # :set ($aGlobalArray->"$key") $value;
}
:foreach key,value in=$aGlobalArray do={  
    :log info ("[key:".$key."] soname:".($value->"soname")." | sodata:".($value->"sodata"));
    # v6 result [key:sub-obj-1] soname:soname-orig | sodata:sodata-modified
    # v7 result [key:sub-obj-1] soname:soname-orig | sodata:sodata-orig
}

На более простом примере, которым любезно поделился Amm0: если мы возьмем простой массив с дочерним массивом внутри. Наибольшее внутреннее значение, ca, начинается с «2», и мы создаем новый массив, используя часть первого, затем устанавливаем внутреннее значение подмассива равным 1:

:global myarray {a=1;child={ca=2}}
:global mysubarray ($myarray->"child")
:set ($mysubarray->"ca") 1

В версии Роутер ОС 6 «mysubarray» фактически ссылается на родительский массив, поэтому при установке значения «mysubarray» родительское значение «myarray» также обновляется:

:put ( ($myarray->"child"->"ca") = 1)

# true

В версии 7 это изменилось, поэтому mysubarray является копией исходного массива, поэтому обновления не влияют на myarray. Таким образом, ":set" устанавливает только тот, который находится в подмассиве. Таким образом, «родительский» массив остается с использованием его исходного значения 2, а не обновленного 1.

:put ( ($myarray->"child"->"ca") = 2)

# true

Дополнительно хочу сообщить новость, касающуюся обоих веток системы и 6 и 7. Недавно пользователь fludorkin обнаружил новый (не документированный) тип данных Роутер ОС – op:

:put [:typeof (>[])]

op

В последующем этот тип данных был проанализирован и высказано предположение, что «op» возможно означает «only pointer» (только указатель), а конструкция служит ярлыком для обращения к элементам сложных ассоциативных массивов, в том числе если их элементами являются функции. Подробнее об этом можно прочитать тут и тут.

На этом пока всё. Мы рады, что наконец-то разработчики Микротик снизошли до улучшений скриптового языка. Видимо раньше руки им сильно связывало недостаточное количество оперативной памяти у старых RouterBoard, что было преодолено с выходом новых устройств, таких как RB5009, HAP AX2 и HAP AX3, L009 и т д… Хотелось бы пожелать им успехов, пожелать вносить изменения и дополнения системно и аккуратно, чтобы не нарушить работу системы и, конечно, подробно документировать свои разработки. Излишне говорить, что описания изменений бета-версий обкатываются и не помещаются в официальные руководства до выхода stable-версий. Все желающие, прочитавшие данную статью, могут в коментариях оставить свои предложения по улучшению скриптового языка Микротик. Мы обязательно обсудим достойные внимания из них на официальном форуме.

Комментарии (18)


  1. NikaLapka
    07.09.2023 13:53
    +1

    RoS 7 прекрасна, но хочется процитировать с 4pda

    если у тебя стоит 7 ролтер ос - ты тестер.


    1. Sertik13 Автор
      07.09.2023 13:53
      +1

      У меня стоит много разных версий Рос. Конечно, все мы тестеры в какой-то степени.


    1. Alexsey
      07.09.2023 13:53
      +2

      Не знаю в чем это заключается. Я конечно не ах какой power user, но на 7 версии с косяками не сталкивался, а вот на 6 прекрасно помню как при включеном DoH текла память.


      1. aborouhin
        07.09.2023 13:53
        +1

        В июне 2022 года обновление на 7.3 отправило один из моих роутеров в бесконечный бутлуп, на следующий день Микротик признали баг, через 2 дня выкатили 7.3.1 с исправлением. Хорошо, что я был в тот момент рядом с той проблемной железкой (а со всеми своими роутерами я рядом одновременно быть физически не могу)...

        Но в целом уже пару лет на ROS 7 в моём маленьком микротик-зоопарке всё, тьфу-тьфу-тьфу, на удивление стабильно.


        1. DaemonGloom
          07.09.2023 13:53
          +1

          Из личного опыта — при активном vlan-filtering обновление 7.11 практически убивало интернет на hex s и ряде других устройств. Пофиксили, конечно. Но осадочек остался.


        1. czz
          07.09.2023 13:53

          После апгрейда CCR2004-1G-12S+2XS на 7.11.2 роутер стал почти виснуть (так сильно лагать, что команды выполнялись по несколько минут) при наличии SFP-модуля Ubiquiti UF-RJ45-1G.

          При отсутствии физического доступа тоже бы не решилось.

          Раньше просто не парясь обновлял роутеры удаленно, теперь не буду :)


  1. XOR2048
    07.09.2023 13:53

    Отличная статья, спасибо!


    1. Sertik13 Автор
      07.09.2023 13:53
      +1

      Спасибо, приятно.


  1. adron_s
    07.09.2023 13:53
    +1

    Python бы туда притащили, вместо корявого недоязыка на коротом в RouterOS скрипты пишутся.


    1. Sertik13 Автор
      07.09.2023 13:53
      +4

      Товарищ, Вы полегче с подобными комментариями. Во-первых сравнение не корректно, так как, позволю напомнить, что скриптовый язык Роутер ОС - это язык сценариев, а не язык программирования. Да, синтаксис не дружелюбный, но кто сказал, что синтаксис Питона лучше ? До сих пор не понимаю зачем ставить два знака == ? К синтаксису любого языка нужно привыкать, а для скриптового языка возможности Lua Микротик весьма впечатляют. Вы хотя бы примерно представляете какие вещи писали на скриптах Микротик ? Познакомьтесь, может Вам будет полезно. Да и у каждого свой хлеб - Вы зарабатываете тем, что пишите на Питоне, а другие - тем, что пишут скрипты для Микротик. Я даже рад, что язык скриптов Микротик такой какой он есть - не у многих хватит терпения его освоить, поэтому нас мало, но мы в тельняжках. Если же Вам хочется писать на Дельфине, Питоне, Анаконде или тому подобных рыбах, змеях или животных - пожалуйста, мы не возражаем. А нам оставьте наш любимый луноподобный и Lua-подобный язык скриптов Микротик - редкий и блестящий как алмаз лунный камень Коллинза.


      1. aborouhin
        07.09.2023 13:53

        Проблема же не в том, какой язык лучше, а какой хуже. А в том, что бóльшая часть пользователей на написание этих скриптов к Микротику тратит несколько часов за всю свою жизнь. И едва ли не больше - на то, чтобы разобраться в языке. А потом, когда через год-два надо подправить пару строчек, - ещё раз тратишь кучу времени, чтобы вспомнить, что там и как.

        Python я сам не люблю, но, так уж сложилось, он стандарт де-факто в качестве языка, который по той или иной причине все знают хотя бы в основных чертах. И, к вопросу о комментарии ниже, в виде MicroPython пробрался даже на микроконтроллеры, так что уж на Микротиках точно реализовать не проблема. Ну или не Python, да хоть bash, хотя bash и бесконечно ужасен, но его тоже знает и регулярно применяет существенная часть ЦА, - и не надо ради пары скриптов на полсотни строк учить что-то совершенно своеобычное, редкое и блестящее :)


        1. XOR2048
          07.09.2023 13:53

          Писать скрипты не так уж и сложно, если подумать. Ну и к тому же, кто сказал, что внедрив Python их станет писать проще настолько, что ради этого нужно будет вводить поддержку данного языка?

          Ну и полностью соглашусь с автором статьи:

          Если же Вам хочется писать на Дельфине, Питоне, Анаконде или тому подобных рыбах, змеях или животных - пожалуйста, мы не возражаем. А нам оставьте наш любимый луноподобный и Lua-подобный язык скриптов Микротик - редкий и блестящий как алмаз лунный камень Коллинза.


          1. aborouhin
            07.09.2023 13:53
            +1

            Конечно, несложно. Но если можно сэкономить несколько часов жизни каждому пользователю, внедрив поддержку общеизвестного языка вместо своеобычного, - почему бы этого не сделать? Вряд ли в природе существуют люди, у которых скрипты для Микротика - это больше 0,1% всего кода, который они пишут по жизни, и остальное-то в любом случае на других языках, так зачем для такой редкой и экзотической задачи создавать лишние сложности?


    1. Kitsok
      07.09.2023 13:53
      +2

      Питон с его "предположим, у нас бесконечное количество памяти" плохо лезет в маленькие устройства, не для того он.


      1. Sertik13 Автор
        07.09.2023 13:53
        +2

        Думаю не за горами установка в контейнеры ROS7 какой-либо оболочки а в ней Питона, например, но нам это не нужно и абсолютно избыточно.


    1. ABATAPA
      07.09.2023 13:53
      +2

      Да уж лучше просто posix sh...


    1. czz
      07.09.2023 13:53

      Докер уже есть, до Питона недалеко осталось :)


  1. Sertik13 Автор
    07.09.2023 13:53
    +2

    Прошу оставляющих комментарии не обсуждать Роутер ОС 7 в целом, а комментировать либо материал статьи либо оставлять пожелания по развитию скриптового языка Микротик.