Но в этой статье мы будем обсуждать нововведения только в скриптовом языке системы вплоть до актуальной 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)
adron_s
07.09.2023 13:53+1Python бы туда притащили, вместо корявого недоязыка на коротом в RouterOS скрипты пишутся.
Sertik13 Автор
07.09.2023 13:53+4Товарищ, Вы полегче с подобными комментариями. Во-первых сравнение не корректно, так как, позволю напомнить, что скриптовый язык Роутер ОС - это язык сценариев, а не язык программирования. Да, синтаксис не дружелюбный, но кто сказал, что синтаксис Питона лучше ? До сих пор не понимаю зачем ставить два знака == ? К синтаксису любого языка нужно привыкать, а для скриптового языка возможности Lua Микротик весьма впечатляют. Вы хотя бы примерно представляете какие вещи писали на скриптах Микротик ? Познакомьтесь, может Вам будет полезно. Да и у каждого свой хлеб - Вы зарабатываете тем, что пишите на Питоне, а другие - тем, что пишут скрипты для Микротик. Я даже рад, что язык скриптов Микротик такой какой он есть - не у многих хватит терпения его освоить, поэтому нас мало, но мы в тельняжках. Если же Вам хочется писать на Дельфине, Питоне, Анаконде или тому подобных рыбах, змеях или животных - пожалуйста, мы не возражаем. А нам оставьте наш любимый луноподобный и Lua-подобный язык скриптов Микротик - редкий и блестящий как алмаз лунный камень Коллинза.
aborouhin
07.09.2023 13:53Проблема же не в том, какой язык лучше, а какой хуже. А в том, что бóльшая часть пользователей на написание этих скриптов к Микротику тратит несколько часов за всю свою жизнь. И едва ли не больше - на то, чтобы разобраться в языке. А потом, когда через год-два надо подправить пару строчек, - ещё раз тратишь кучу времени, чтобы вспомнить, что там и как.
Python я сам не люблю, но, так уж сложилось, он стандарт де-факто в качестве языка, который по той или иной причине все знают хотя бы в основных чертах. И, к вопросу о комментарии ниже, в виде MicroPython пробрался даже на микроконтроллеры, так что уж на Микротиках точно реализовать не проблема. Ну или не Python, да хоть bash, хотя bash и бесконечно ужасен, но его тоже знает и регулярно применяет существенная часть ЦА, - и не надо ради пары скриптов на полсотни строк учить что-то совершенно своеобычное, редкое и блестящее :)
XOR2048
07.09.2023 13:53Писать скрипты не так уж и сложно, если подумать. Ну и к тому же, кто сказал, что внедрив Python их станет писать проще настолько, что ради этого нужно будет вводить поддержку данного языка?
Ну и полностью соглашусь с автором статьи:Если же Вам хочется писать на Дельфине, Питоне, Анаконде или тому подобных рыбах, змеях или животных - пожалуйста, мы не возражаем. А нам оставьте наш любимый луноподобный и Lua-подобный язык скриптов Микротик - редкий и блестящий как алмаз лунный камень Коллинза.
aborouhin
07.09.2023 13:53+1Конечно, несложно. Но если можно сэкономить несколько часов жизни каждому пользователю, внедрив поддержку общеизвестного языка вместо своеобычного, - почему бы этого не сделать? Вряд ли в природе существуют люди, у которых скрипты для Микротика - это больше 0,1% всего кода, который они пишут по жизни, и остальное-то в любом случае на других языках, так зачем для такой редкой и экзотической задачи создавать лишние сложности?
Kitsok
07.09.2023 13:53+2Питон с его "предположим, у нас бесконечное количество памяти" плохо лезет в маленькие устройства, не для того он.
Sertik13 Автор
07.09.2023 13:53+2Думаю не за горами установка в контейнеры ROS7 какой-либо оболочки а в ней Питона, например, но нам это не нужно и абсолютно избыточно.
Sertik13 Автор
07.09.2023 13:53+2Прошу оставляющих комментарии не обсуждать Роутер ОС 7 в целом, а комментировать либо материал статьи либо оставлять пожелания по развитию скриптового языка Микротик.
NikaLapka
RoS 7 прекрасна, но хочется процитировать с 4pda
Sertik13 Автор
У меня стоит много разных версий Рос. Конечно, все мы тестеры в какой-то степени.
Alexsey
Не знаю в чем это заключается. Я конечно не ах какой power user, но на 7 версии с косяками не сталкивался, а вот на 6 прекрасно помню как при включеном DoH текла память.
aborouhin
В июне 2022 года обновление на 7.3 отправило один из моих роутеров в бесконечный бутлуп, на следующий день Микротик признали баг, через 2 дня выкатили 7.3.1 с исправлением. Хорошо, что я был в тот момент рядом с той проблемной железкой (а со всеми своими роутерами я рядом одновременно быть физически не могу)...
Но в целом уже пару лет на ROS 7 в моём маленьком микротик-зоопарке всё, тьфу-тьфу-тьфу, на удивление стабильно.
DaemonGloom
Из личного опыта — при активном vlan-filtering обновление 7.11 практически убивало интернет на hex s и ряде других устройств. Пофиксили, конечно. Но осадочек остался.
czz
После апгрейда CCR2004-1G-12S+2XS на 7.11.2 роутер стал почти виснуть (так сильно лагать, что команды выполнялись по несколько минут) при наличии SFP-модуля Ubiquiti UF-RJ45-1G.
При отсутствии физического доступа тоже бы не решилось.
Раньше просто не парясь обновлял роутеры удаленно, теперь не буду :)