Работа с массивами или когда массив распадается на указатели

Здесь выкладываем скрипты
Правила форума
Уважаемые Пользователи форума, обратите внимание!
Ни при каких обстоятельствах, Администрация форума, не несёт ответственности за какой-либо, прямой или косвенный, ущерб причиненный в результате использования материалов, взятых на этом Сайте или на любом другом сайте, на который имеется гиперссылка с данного Сайта. Возникновение неисправностей, потерю программ или данных в Ваших устройствах, даже если Администрация будет явно поставлена в известность о возможности такого ущерба.
Просим Вас быть предельно осторожными и внимательными, в использовании материалов раздела. Учитывать не только Ваши пожелания, но и границы возможностей вашего оборудования.
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Читая примеры и документацию понимаешь, что объявить массив можно по разному:

Код: Выделить всё

:global ar [:toarray "1,2,3"]
:global ar {1;2;3}
:global ar {"n1";"n2";"n3"}
:global ar ({})
:global ar [:toarray ""]
Но почему-то все упускают из внимания, что выше можно выделить два разных типа массива.
Первый тип:

Код: Выделить всё

:global ar ({})
:global ar {"n1";"n2";"n3"}
Второй тип:

Код: Выделить всё

:global ar [:toarray "1,2,3"]
:global ar [:toarray ""]
При простых операциях вы не заметите в них разницу, но когда пойдут циклы и ветвления можно наступить на грабли и потерять много времени.

И так простой пример который покажет разницу между ними:

Код: Выделить всё

:local ar do={
  :local x [:toarray ""];
  :set ($x->$arg) $arg;
  :return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
прежде чем посмотреть ответ подумайте что должно получится.
 ответ
1=1
2=2
3=3
ожидаемо.
теперь изменим тип массива:

Код: Выделить всё

:local ar do={
  :local x ({});
  :set ($x->$arg) $arg;
  :return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
и получаем посхалку:
 ответ
1=1
1=1;2=2
1=1;2=2;3=3
можно предположит, что в данном случаи массив содержит директиву static, но это не так - если запустит скрипт сразу второй раз, то вы увидете другой результат. Скорее всего, это происходит по причине того, что когда вы высвобождаете массив, RouterOS не восвобождает память сразу, а только по требованию и если запросить массив с таким же именем, то он отдаст всю начинку этого массива от предыдущего вызова скрипта. Это более наглядно видно при работе с большими структурами.

Второй нежданчик более критичен.
попробуйте понять что тут должно произойти зная первую фичу и запуская скрипт несколько раз:

Код: Выделить всё

:log info "run";
:local ar1 ({});
:local ar2 {"ar2-st0";"ar2-st1";"ar2-st2"};
:log info $ar1;
:for i from=0 to=2 do={
:if ($i = 2) do={:set ($ar1->i) ($ar2->i);}}
:log info $ar1;
:set ($ar2->2) "test";
:log info $ar1;
:log info $ar2;
:log info "end\r\n";
Если вы немного усложните скрипт, то увидете, что ключи в разных массивах будут указывать на одни и те же значения.
В принципе этим можно пользоваться в некоторых сценариях, или нет, но знать это надо. А то можете потерять несколько дней на поиск блохи, как произошло это у меня.
Старайтесь при инициализации массива использовать конструкцию [:toarray ""]


Аватара пользователя
podarok66
Модератор
Сообщения: 4355
Зарегистрирован: 11 фев 2012, 18:49
Откуда: МО

Очень интересно. Признаться, я никогда не пользовался :local x ({}); Я только из ваших постов узнал о таком варианте. Писать не стал, подумал, что отстал от жизни :-)
Переношу тему с скрипты, там ей самое место.


Мануалы изучил и нигде не ошибся? Фаервол отключил? Очереди погасил? Витая пара проверена? ... Тогда Netinstal'ом железку прошей и настрой ее заново. Что, все равно не фурычит? Тогда к нам. Если не подскажем, хоть посочувствуем...
Sertik
Сообщения: 1598
Зарегистрирован: 15 сен 2017, 09:03

С переменными в РОС вообще одни косяки, а с массивами еще хуже. Видимо, когда lua адаптировали для Рос урезая много накосячили. Теперь наверняка даже в Микротик нет человека, который четко бы знал что и как работает.

Вот это, наверное читали все https://habr.com/ru/post/270719/

Вот Вам пример Росовских косяков по Вашему же "методу":

:local ar do={
:local x ({:nothing});
:set ($x->$arg) $arg;
:return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];

Посмотрите результат

и вот для размышлений:

:local ar do={
:global x ({:});
:set ($x->$arg) $arg;
:return $x;
}
:local x [$ar arg=1];
:log info $x
:log info [$ar arg=2];
:log info [$ar arg=3];
:log info [:len $x]

Как видим, после :global x ({:}) массив приобретает первый пустой элемент, второй элемент тогда всегда "правильный"
Вообще косячно всё это. Лучше действительно объявлять переменные и массивы правильными методами. В частности [:toarray]


фрагменты скриптов, готовые работы, статьи, полезные приемы, ссылки
viewtopic.php?f=14&t=13947
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

вы не поняли, ваши примеры не совсем корректны. Массивы нумеруются с нуля. поэтому когда вы указываете:

Код: Выделить всё

:set ($x->$arg) $arg;

и даёте аргумент 1, то естественно на выходе вы получаете массив из двух элементов, если вы дадите аргумент 50 , то получите размер массива в 51 элемент, 50 будет равен 50, а остальные пустые.

В первом вашем примере вы не удаляете массив(он глобальный) по этому он заполняется правильно с инициализацией.

Надо понимать, что практически в любом языке есть такие понятия как объявление переменной и её инициализация.
Когда вы пишете в скрипте массив через {} вы его объявляете и инициализируете ТОЛЬКО первый раз. Когда вы запускаете скрипт второй раз , то ROS найдя у себя в памяти этот высвобожденный массив - просто объявляет его без инициализации. Это сделано для ускорения работы языков.
Команда :toarray - а это именно команда - производит инициализацию массива в любом случаи.

ПРАВИЛО - используйте {} когда данные массива не будут изменятся в процессе работы скрипта. Если данными массива будет заполнятся другой массив и последний будет изменятся, а так же во всех остальных случаях используйте :toarray


Sertik
Сообщения: 1598
Зарегистрирован: 15 сен 2017, 09:03

Спасибо большое, стало яснее.

Хотя как
В первом вашем примере вы не удаляете массив(он глобальный) по этому он заполняется правильно с инициализацией.
когда в тексте моего первого примера:
:local ar do={
:local x ({:nothing});
:set ($x->$arg) $arg;
:return $x;
}
:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
где тут объявлен глобальный массив, когда написано :local x ({:nothing}); ?


Интересно как работает Winbox и РОС с Вашим примером ...

Берем Ваш пример:

Код: Выделить всё

:local ar do={
:global x ({});
:set ($x->$arg) $arg;
:return $x;
}

:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
Запускаем его. Получаем в логе

1=1
1=1;2=2
1=1;2=2;3=3

Первый раз нормально.
Если дальше будем зупускать получим сами знаете что:

1=1;2=2;3=3
1=1;2=2;3=3
1=1;2=2;3=3

Теперь такой эксперимент:

Добавим в скрипт что-нибудь ну хоть знак комментария в первую строку:

Код: Выделить всё

#
:local ar do={
:global x ({});
:set ($x->$arg) $arg;
:return $x;
}

:log info [$ar arg=1];
:log info [$ar arg=2];
:log info [$ar arg=3];
Запускаем:

Результат:

1=1
1=1;2=2
1=1;2=2;3=3

(То есть корректный, ожидаемый)

Если дальше опять будем запускать - получим опять некорректные

1=1;2=2;3=3
1=1;2=2;3=3
1=1;2=2;3=3

А вот если будем что-то менять в тексте скрипта (ну хоть добавлять по # к той же первой строке) и жать кнопки "apply" и потом "Run script"
То после каждой правки скрипта и запуска - только корректные результаты.


Если Микротиковские разработчики сделали что при команде :toarray массив и объявляется и инициализируется, (что видимо происходит и при моём эксперименте в случае правки текста скрипта), а при ({}) только объявляется, для, как Вы пишите, ускорения работы скриптового языка, так они должны были где то об этом написать.
Но мало того, что ни у кого (ну может только у Господа ! или Джона с Янисом) нет руководства по скриптовому языку ROS, так там ещё сплошные недокументированные вещи (можно вспомнить хоть нашу переписку с Podarok66 по поводу нигде не описанного ключевого слова :any, при желании можно найти на этом форуме). Вот мы тут с Вами и тратим время на их изыскания и объяснения.

Раз такое происходит и с экспериментом по правке скрипта, можно предположить, что у них вся оболочка, а не только скриптовый язык работают по принципу - если изменений данных не было (жали только кнопку "apply", но ничего не правили в тексте скрипта) - объект не инициализируется. Если правки скрипта были - после apply и run - происходит и инициализация и объявление, потом запуск. Тоже для ускорения ...


фрагменты скриптов, готовые работы, статьи, полезные приемы, ссылки
viewtopic.php?f=14&t=13947
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

давай будем разбираться
Хотя как
Да это моя невнимательность.
Берем Ваш пример:
Всё-таки это не мой пример. У меня в примере local у вас global
давай по порядку
1- функции объявляются синтаксисом

Код: Выделить всё

:local name do={}
естественно вместо local может быть global
поскольку функция имеет локальную область видимости, то все объявленные локальные переменные уничтожаются при выходе из функции, но это сточки зрения писателя функции. А на самом деле, люди которые писали *nix говорят нам, что переменная просто восвобождается и уничтожится когда когда системе потребуется место для других целей, (память просто перезапишется). Как яркий пример - приложения в android - их нельзя закрыть, так как для этого потребуется дополнительные ресурсы процессора. Приложения сами выпадут, как только не будет хватать ресурсов.
2- первым действием функции мы объявляем переменную. Вот тут есть большая разница объявите вы её локально или глобально. Если мы объявляем её локально, то подразумеваем, что переменная исчезнет при выходе из функции, но мой пост как раз о том, что не всегда это происходит. Вы же в своём втором примере объявляете функцию глобально и при выходе из функции переменная ни куда не девается. Но вы забыли одну важную вещь - при повторном заходе в функцию - функция ни чего не знает о существовании одноимённой глобальной переменной. Чтобы функция знала, надо в теле функции сначала объявить переменную, делается это с помощью

Код: Выделить всё

:global x
То что данному поведению поддаются одинаково и локальные и глобальные переменные видно на изменённом примере

Код: Выделить всё

:global x;
:local ar do={
  :global x;
  :global x ({});
  :set ($x->$arg) $arg;
  :return $x;
}
:log info [$ar arg=1];
:set $x;
:log info [$ar arg=2];
:set $x;
:log info [$ar arg=3];
:set $x;
Про :any дайте ссылку - почитаю.
Про то что при изменении скрипта сбрасываются из памяти высвобожденные переменные предполагает то, что разрабы так и задумали.
Сама тема называется про указатели в массивах, на на самом деле здесь не приведено ни одного примера с указателями, просто я не могу сообразить как написать доходчивый пример.


Sertik
Сообщения: 1598
Зарегистрирован: 15 сен 2017, 09:03

Дежа Вю просто:

Вот мой первый пример см. выше:

Код: Выделить всё

    :local ar do={
    :local x ({:nothing});
    :set ($x->$arg) $arg;
    :return $x;
    }
    :log info [$ar arg=1];
    :log info [$ar arg=2];
    :log info [$ar arg=3];
Вот Ваш комментарий по нему:
В первом вашем примере вы не удаляете массив(он глобальный) по этому он заполняется правильно с инициализацией.
Ещё раз где в моем примере объявлен глобальный массив, когда написано :local x ({:nothing}); ?

Дальше, как работают :local и global я знаю, не один десяток скриптов написал. За Ваши разъяснения спасибо, но тут ведь не все чайники ... Поведение Ваших "примеров массивов распадающихся на указатели" не зависит от того локальные они или глобальные это ясно, я же с этим не спорил.

Насчет :any смотрите хотя бы скрипты Чупакабры:

https://habr.com/ru/post/337978/

Когда наткнулся на :any мы с Podarok66 обсуждали как это работает вот тут:

viewtopic.php?f=14&t=10100

Если Вы знаете какие-то недокументированные возможности скриптов Микротик и не жалко поделиться, милости просим, и будем признательны Вам, так как опыт у Вас в скриптописании большой судя по Вашему скрипту с разбором SMS. Спасибо Вам за него.


фрагменты скриптов, готовые работы, статьи, полезные приемы, ссылки
viewtopic.php?f=14&t=13947
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

Ещё раз где в моем примере объявлен глобальный массив, когда написано :local x ({:nothing}); ?
говорю же, ошибся я, не первый а второй пример в вашем посте, Ваш пример:

Код: Выделить всё

:local ar do={
:global x ({:});
:set ($x->$arg) $arg;
:return $x;
}
:local x [$ar arg=1];
:log info $x
:log info [$ar arg=2];
:log info [$ar arg=3];
:log info [:len $x]
и далее :
Берем Ваш пример:
Мы, видимо, друг друга не до поняли.

А вот это льстит
так как опыт у Вас в скриптописании большой судя по Вашему скрипту с разбором SMS.
Но по факту это и есть весь мой опыт, да и основные моменты разложил чуть ранее в первой теме другой человек на этом ресурсе.
Многое в моём коде можно оптимизировать для ускорения работы.
А эта тема была создана из-за нежданчиков, на которых я потерял много времени, может кому-то и пригодится, в поисках проблем.


Sertik
Сообщения: 1598
Зарегистрирован: 15 сен 2017, 09:03

Если это Ваш единственный опыт, то он очень хороший в любом случае, значит у Вас есть опыт программирования на других языках и он не маленький.
Такие люди как Вы нужны нам всем. Ибо их очень мало.
Будут возможности писать скрипты для Микротика мы все только приветствуем Вас и Ваши труды.

Относительно Вашего скрипта по SMS просьба-пожелание. Сделайте ему установщик. То есть чтобы пользователь не копировал текст каждого скрипта с форума сам вставлял его в репоззиторий, а возьмите чистый роутер, создайте там юзера pepelxl, создайте там Все Ваши скрипты с нужными именами. Потом сделайте /system script export в один файл. Всё ! этот файл выложите на форум сюда. Пользователь сможет брать его и делать /import. Все Ваши функции/скрипты добавятся к скриптам пользователя на его роутере, но под именем создателя - так сразу будет о Вас память в веках ! Ему останется только запускать главный Ваш скрипт.


фрагменты скриптов, готовые работы, статьи, полезные приемы, ссылки
viewtopic.php?f=14&t=13947
pepelxl
Сообщения: 161
Зарегистрирован: 23 июл 2013, 18:47

значит у Вас есть опыт программирования на других языках
Вот что-что, а советовать начинать знакомится с миром программирования с помощью скриптов роутера точно не стоит. Может привиться стойкая неприязнь и отторжение к любому коду.
просьба-пожелание. Сделайте ему установщик
Там бетка и заворачивать её в обёртку ещё очень рано.
Тому есть очень большая причина- комментарии. Они все на русском, хотя свой русский я не назову великим и могучим. Как думаете, что произойдёт если скопировать код экспортом из роутера? Правильно - вся кириллица превратится в HEX коды. И это только пол беды - вы сможете прочитать такие комментарии только на клиенте у которого кодировка по умолчанию установлена в win-1251. Весь остальной мир с кодировкой отличной от кириллицы увидит крякозябры.
Именно по этому мой код требует отправки разобранных sms в систему которая сможет гарантированно отобразить многобайтовую кодировку. Ни обычный терминал, ни winbox этого сделать не смогут. Хотя возможно есть исключения - терминал под никсы с возможностью выбора многобайтовой кодировки; да и вроде в tikapp для смартфонов в настройках есть выбор utf8/16. Но это не масс сегмент, и не универсально.
Выделить\исключить коментарии из кода можно в любой момент регуляркой.
А если уж дойдёт до релизной версии, то тогда уж и покрашить русский и завернуть в обёртку.

Вот чего мне действительно не хватает при написании такого скрипта, так это функции автоматического обновления на новую версию. Не то что бы это нельзя было реализовать, но у Ros очень лажовая система политик доступа. Например, что бы создать простую функцию 2+2, скрипту надо дать четыре!!! разрешения: write, read, policy, test. А это товарищи уже полный ПИ..ЕЦ. То- есть не возможно обновить независимому разработчику скрипт так что бы гарантировать, что новый код не вылез за пределы своей песочницы.


Ответить