Fi
Scripting gives the administrator a way to execute console commands by writing a script for the router which is executed on the basis of time or events that can be monitored on the router. Some examples of uses of scripting could be: setting bandwidth settings according to time. In RouterOS v2.6, a script may be started in three ways:
To write a script, the writer must learn all of the console commands described in the relevant documentation. Scripts may be written for the System Scheduler (see relevant manual), the Traffic Monitoring Tool ( see relevant manual), and for the Netwatch Tool.
The scripts are stored under /system script. Use the add command to add a new script. The following example is a script for writing message "kuku" to the system log:
[admin@MikroTik] system script> add name=log-test source={:log message=kuku} [admin@MikroTik] system script> print 0 name="log-test" source=":log message=kuku" owner=admin run-count=0 [admin@MikroTik] system script>
Argument description:
name - name of the script to be referenced when invoking it. If not specified, the name is generated automatically as "scriptX", X=1,2,...
source - the script itself
owner - user's name who created the script
run-count - usage counter. This counter is incremented each time the script is executed, it can be reset to zero by setting 'run-counter=0'
last-started - date and time when the script has been last invoked. The argument is shown only if the 'run-count=0'.
Note that the counters will reset after reboot.
You can execute a script by using the run command.
To manage the active or scheduled tasks, use the /system script job menu. You can see the status of all currently active tasks using the print command. For example, we have a script that delays some process for 10 minutes:
[admin@MikroTik] system script> add name=DelayeD source={:delay 10m} [admin@MikroTik] system script> print 0 name="log-test" source=":log message=kuku" owner=admin last-started=may/09/2001 03:22:19 run-count=1 1 name="DelayeD" source=":delay 10m" owner=admin run-count=0 [admin@MikroTik] system script> run DelayeD [admin@MikroTik] system script> job print # SCRIPT STARTED 0 DelayeD may/09/2001 03:32:18 [admin@MikroTik] system script>
You can cancel execution of a script by removing it from the jobs list:
[admin@MikroTik] system script> job remove 0 [admin@MikroTik] system script> job print [admin@MikroTik] system script> print 0 name="log-test" source=":log message=kuku" owner=admin last-started=may/09/2001 03:36:44 run-count=3 1 name="DelayeD" source=":delay 10m" owner=admin last-started=may/09/2001 03:32:18 run-count=1 [admin@MikroTik] system script>
Netwatch monitors state of hosts on the network. It does so by sending ICMP pings to list of specified IP addresses. For each entry in netwatch table you can specify IP address, ping interval and console scripts.
The main advantage of netwatch is ability to issue arbitrary console commands on host state changes. Here's an example configuration of netwatch. It will run the scripts gw_1 or gw_2 which change the default gateway depending on the status of one of the gateways:
[MikroTik] system script> add name=gw_1 source={/ip route set [/ip route find dst 0.0.0.0] gateway 10.0.0.1} add name=gw_2 source={/ip route set [/ip route find dst 0.0.0.0] gateway 10.0.0.217} [MikroTik] system script> /tool netwatch add host=10.0.0.217 interval=10s timeout=998ms up-script=gw_2 down-script=gw_1 [MikroTik] tool netwatch> print Flags: X - disabled # HOST TIMEOUT INTERVAL STATUS 0 10.0.0.217 997ms 10s up [MikroTik] tool netwatch> print detail Flags: X - disabled 0 host=10.0.0.217 timeout=997ms interval=10s since=mar/22/2002 11:21:03 status=up up-script=gw_2 down-script=gw_1 [MikroTik] tool netwatch>
Argument description:
host - IP address of host that should be monitored
interval - Time between pings. Lowering this will make state changes more responsive, but can create unnecessary traffic and consume system resources.
timeout - Timeout for each ping. If no reply from host is received in this time, host is considered unreachable (down).
up-script - Console script that is executed once when state of host changes from unknown or down to up.
down-script - Console script that is executed once when state of host changes from unknown or up to down.
since - Time when state of host changed last time.
status - tells the current status of the host (up / down / unknown). State of host changes to unknown when any properties of this list entry are changed, or it is enabled or disabled. Also, any entry that is added has state unknown initially.
Hint: Scripts are not printed by default, to see them, type print detail.
Without scripts, netwatch can be used just as an information tool to see which links are up, or which specific hosts are running at the moment.
Let's look at the example above - it changes default route if gateway becomes unreachable. How it's done? There are two scripts. The script "gw_2" is executed once when status of host changes to up. In our case, it's equivalent to entering this console command:
[MikroTik] > /ip route set [/ip route find dst 0.0.0.0] gateway 10.0.0.217
The /ip route find dst 0.0.0.0 command returns list of all routes whose dst-address value is zero. Usually that's the default route. It is substituted as first argument to /ip route set command, which changes gateway of this route to 10.0.0.217
The script "gw_1" is executed once when status of host becomes down. It does the following:
[MikroTik] > /ip route set [/ip route find dst 0.0.0.0] gateway 10.0.0.1
It changes the default gateway if 10.0.0.217 address has become unreachable.
Here's another example, that sends email notification whenever the 10.0.0.215 host goes down:
[MikroTik] system script> add name=e-down source={/tool e-mail send from="rieks@mt.lv" server=\ "159.148.147.198" body="Router down" subject="Router at \ second floor is down" to="rieks@latnet.lv"} add name=e-up source={/tool e-mail send from="rieks@mt.lv" server=\ "159.148.147.198" body="Router up" subject="Router at \ second floor is up" to="rieks@latnet.lv"} [MikroTik] system script> [MikroTik] system script> /tool netwatch [MikroTik] system script> add host=10.0.0.215 timeout=999ms interval=20s \ up-script=e-up down-script=e-up [MikroTik] tool netwatch> print detail Flags: X - disabled 0 host=10.0.0.215 timeout=998ms interval=20s since=mar/22/2002 14:07:36 status=up up-script=e-up down-script=e-up [MikroTik] tool netwatch>
This is more an introductory text, less a reference. It freely uses commands and concepts before explaining them, to make it as short, simple and comprehensive as possible. It might be necessary to read it several times. Many examples are given, because it is the best way to explain most things.
PREFIX PATH PATH_ARGUMENT COMMAND NAMELESS_ARGUMENTS ARGUMENTSfirst, few examples:
/ping 10.0.0.13 count=5 PREFIX - "/" COMMAND - "ping" NAMELESS_ARGUMENTS - "10.0.0.13" ARGUMENTS - "count=5"
... ip firewall rule input PATH - ".. ip firewall rule" PATH_ARGUMENT - "input"
:for i from=1 to=10 do={:put $i} PREFIX - ":" COMMAND - "for" NAMELESS_ARGUMENTS - "i" ARGUMENTS - "from=1 to=10 do={:put $i}"
/interface monitor-traffic ether1,ether2,ipip1 PREFIX - "/" PATH - "interface" COMMAND - "monitor-traffic" NAMELESS_ARGUMENTS - "ether1,ether2,ipip1"Here are explanations for each part of command:
PREFIX is either '/' or ':'. It is optional
PATH is a sequence of command level names and '..'. It is also optional, but the processing of commands without given path may change in future versions; so, in your scripts, use path that starts with prefix ('/' or ':') whenever possible
PATH_ARGUMENT is required by some command levels (like /ip firewall rule), and is not allowed anywhere else
COMMAND is command name from the command level specified by path
NAMELESS_ARGUMENTS are specific to each command. Values of these arguments are written in fixed order after name of command, and only after all nameless argument values any named arguments can be given
ARGUMENTS are sequence of argument names (like /user print brief without-paging). For arguments that take values, argument name is followed by '=', followed by value of argument
Variable substitution, command substitution and expressions are allowed only for PATH_ARGUMENT and command argument values. Prefix, path, command name and argument names can only be given directly, as a word. So
:put (1 + 2)is valid and
(":pu" . "t") 3is not.
[admin@MikroTik] ip address> /user { {... add name=x password=y group=write {... add name=y password=z group=read {... print {... } Flags: X - disabled 0 ;;; system default user name="admin" group=full address=0.0.0.0/0 1 name="x" group=write address=0.0.0.0/0 2 name="y" group=read address=0.0.0.0/0 [admin@MikroTik] ip address>You should not change current command level in scripts by typing just it's path, without any command, like you when working with console interactively. Such changes have no effect in scripts. Consider:
[admin@MikroTik] ip address> /user { {... /ip route {... print {... } Flags: X - disabled 0 ;;; system default user name="admin" group=full address=0.0.0.0/0 1 name="x" group=write address=0.0.0.0/0 2 name="y" group=read address=0.0.0.0/0 [admin@MikroTik] ip route>Although the current command level is changed to /ip route, it has effect only on next command entered from prompt, print command is still considered to be /user print.
[admin@MikroTik] ip route> :put $a ERROR: unknown variable a [admin@MikroTik] ip route>Before using variable in script, it's name must be introduced. There are several ways to do that:
[admin@MikroTik] ip route> / [admin@MikroTik] > :global g1 [admin@MikroTik] > :set g1 "this is global variable" [admin@MikroTik] > :put $g1 this is global variable [admin@MikroTik] >Global variables can be accessed by all scripts and console logins on the same router. There is no way currently to remove global variable, except rebooting router. Variables are not kept across reboots.
[admin@MikroTik] > :local l1 [admin@MikroTik] > :set l1 "this is local variable" [admin@MikroTik] > :put $l1 this is local variable [admin@MikroTik] >
[admin@MikroTik] > :for l1 from=1 to=3 do={:put $l1} 1 2 3 [admin@MikroTik] > :put $l1 this is local variable [admin@MikroTik] >See how loop variable "shadows" already introduced local variable l1. It's value is not overwritten by :for loop.
Introducing variable has no effect on other scripts that may be running. It just tells the current script what variable names can be used, and where to get their values. After variable is no longer needed, it's name can be freed by :unset command. If you free local variable, it's value is lost. If you free global variable, it's value is still kept in router, it just becomes inaccessible from current script.
[admin@MikroTik] > :local counter [admin@MikroTik] > :set counter 0 [admin@MikroTik] > :put $counter 0 [admin@MikroTik] > :set counter ($counter + 1) [admin@MikroTik] > :put $counter 1 [admin@MikroTik] >Because increasing or decreasing variable's value by one is such a common case, there are two commands that do just that. :incr increases value of variable by 1, and :decr decreases it by 1.
[admin@MikroTik] > :incr counter [admin@MikroTik] > :put $counter 2 [admin@MikroTik] >Variable must contain integer number value, otherwise these commands will fail.
[admin@MikroTik] > /interface [admin@MikroTik] interface> find type=ether [admin@MikroTik] interface>It displays nothing on screen, and returns internal numbers of items with matching property values. This is how return value looks:
[admin@MikroTik] interface> :put [find type=ether] *A,*B [admin@MikroTik] interface>and this is how it can be used in other commands
[admin@MikroTik] interface> enable [find type=ether] [admin@MikroTik] interface>Besides find, some other commands also return useful values. /ping returns number of successful pings:
[admin@MikroTik] interface> :put [/ping 10.0.0.1 count=3] 10.0.0.1 64 byte pong: ttl=64 time<1 ms 10.0.0.1 64 byte pong: ttl=64 time<1 ms 10.0.0.1 64 byte pong: ttl=64 time<1 ms 3 packets transmitted, 3 packets received, 0 packet loss round-trip min/avg/max = 0/0.0/0 ms 3 [admin@MikroTik] interface>:set returns value of it's second argument. :time returns the measured time value. :incr and :decr return new value of variable. Another important case is add commands, which return internal number of newly created item.
[admin@MikroTik] interface> /user [admin@MikroTik] user> :put [add name=z password=x group=full] *7 [admin@MikroTik] user>This way you can store it in variable for later use.
[admin@MikroTik] user> :put (1 + 2) 3 [admin@MikroTik] user> /interface [admin@MikroTik] interface> :put ([find type=ipip ] . [find type=ether ]) *6,*A,*B [admin@MikroTik] interface>Supported operations are
Unary operation. Argument is a truth value. Result is an opposite truth value.
[admin@MikroTik] interface> :put (!true) false [admin@MikroTik] interface> :put (!(2>3)) true [admin@MikroTik] interface>
Unary operation. Argument and result is a number.
[admin@MikroTik] interface> :put (-1<0) true [admin@MikroTik] > :put (--1) 1
Unary operations. Inverts bits in IP address.
[admin@MikroTik] interface> :put (~255.255.0.0) 0.0.255.255 [admin@MikroTik] interface>
Add together two numbers, two time values, or add number to an IP address.
[admin@MikroTik] interface> :put (3s + 5s) 8s [admin@MikroTik] interface> :put (10.0.0.15 + 0.0.10.0) ERROR: cannot add ip address to ip address [admin@MikroTik] interface> :put (10.0.0.15 + 10) 10.0.0.25 [admin@MikroTik] interface>
Subtract one number from another, one time value from another. Subtracting a number from IP address gives IP address. Subtracting one IP address from another gives number.
[admin@MikroTik] interface> :put (10.0.0.15 + 10) 10.0.0.25 [admin@MikroTik] interface> :put (10.0.0.15 - 10.0.0.3) 12 [admin@MikroTik] interface> :put (10.0.0.15 - 12) 10.0.0.3 [admin@MikroTik] interface> :put (15h - 2s) 14h59m58s [admin@MikroTik] interface>
Multiply two numbers, or multiply a time value by a number.
[admin@MikroTik] interface> :put (12s * 4) 48s [admin@MikroTik] interface> :put (-5 * -2) 10 [admin@MikroTik] interface>
Divide one number by another (gives an integer), or a time value by a number (gives time value).
[admin@MikroTik] interface> :put (10s / 3) 3s333.333ms [admin@MikroTik] interface> :put (5 / 2) 2 [admin@MikroTik] interface>
Compare two numbers, two time values, or two IP addresses. Gives truth value.
[admin@MikroTik] interface> :put (10.0.2.3<=2.0.3.10) false [admin@MikroTik] interface> :put (100000s>27h) true [admin@MikroTik] interface>
Compare two values of the same type. Arrays are equal if their respective elements are equal.
[admin@MikroTik] interface> :put (60s,1d!=1m,3600s) false [admin@MikroTik] interface> :put (bridge=routing) false [admin@MikroTik] interface> :put (yes=false) false [admin@MikroTik] interface> :put (true=aye) ERROR: cannot compare if truth value is equal to string [admin@MikroTik] interface>
Logical operation on two truth values. Result of && is true, if both operands are true. Result of || is true if either operand is true.
[admin@MikroTik] interface> :put ((yes && yes) || (yes && no)) true [admin@MikroTik] interface> :put ((no || no) && (no || yes)) false [admin@MikroTik] interface>
Bitwise operations on two IP addresses. Result is also an IP address.
[admin@MikroTik] interface> :put (10.16.0.134 & ~255.255.255.0) 0.0.0.134 [admin@MikroTik] interface>
Shift IP value left or right by given amount of bits. First argument is IP address, second argument is integer. Result is IP address.
[admin@MikroTik] interface> :put (~((0.0.0.1 << 7) - 1)) 255.255.255.128 [admin@MikroTik] interface>
Paste together two strings, or append one list to another, or append an element to a list.
[admin@MikroTik] interface> :put (1 . 3) 13 [admin@MikroTik] interface> :put (1,2 . 3) 1,2,3 [admin@MikroTik] interface> :put (1 . 3,4) 13,4 [admin@MikroTik] interface> :put (1,2 . 3,4) 1,2,3,4 [admin@MikroTik] interface> :put ((1 . 3) + 1) ERROR: cannot add string to integer number [admin@MikroTik] interface>
There is no way to explicitly control this type conversion, but it will most likely change in future versions. Meanwhile, this can help to explain why console sometimes "corrupts" values, that are meant to be strings, but look like one of the above types:
[admin@MikroTik] interface> :put sd90039 2d1h40s [admin@MikroTik] interface>In console integers are internally represented as 64 bit signed numbers, so the range of variable values can be from -9223372036854775808 to 9223372036854775807. It is possible to input them as hexadecimal numbers, by prefixing with "0x":
[admin@MikroTik] interface> :put 0x123ABCDEF4567890 1313569907099990160 [admin@MikroTik] interface> / [admin@MikroTik] >Lists are written as comma separated sequence of values. Putting whitespaces around commas are not recommended, because it might confuse console about word boundaries.
[admin@MikroTik] > :foreach i in 1,2,3 do {:put $i} 1 2 3 [admin@MikroTik] > :foreach i in 1, 2, 3 do {:put $i} ERROR: no such argument (2,) [admin@MikroTik] >Truth values are written as either true or false. Console also accepts yes for true, and no for false.
Internal numbers begin with '*'.
Time intervals are written as sequence of numbers, that can be followed by letters specifying the units of time measure. The default is second. Numbers may have decimal point. It is also possible to use the HH:MM:SS notation. Here are some examples:
[admin@MikroTik] > :put "1000s" 16m40s [admin@MikroTik] > :put "day day day" 3d [admin@MikroTik] > :put "1.5hours" 1h30m [admin@MikroTik] > :put "1:15" 1h15m [admin@MikroTik] > :put "0:3:2.05" 3m2s50ms [admin@MikroTik] >Accepted time units:
d, day, days - unit is 24 hours
h, hour, hours - unit is 1 hour
m - unit is 1 minute
s - unit is 1 second
ms - unit is 1 millisecond (0.001 second)
[admin@MikroTik] > : local introduces local variable global introduces global variable unset forgets variable set creates or changes variable value put prints argument on the screen while executes command while condition is true if executes command if condition is true do executes command time times command incr increments variable decr decrements variable for executes command for a range of integer values foreach executes command for every element in a list delay does nothing for a while (default 1 second) environment log [admin@MikroTik] > ::local, :global, :unset, :set, :incr and :decr commands are explained in the section about variables. Here all the other commands will be explained.
[admin@MikroTik] > :if (yes) do={:put yes} else={:put no} true [admin@MikroTik] > :if ([/ping 10.0.0.1 count=1] = 0) do {:put "gateway unreachable"} 10.0.0.1 pong timeout 1 packets transmitted, 0 packets received, 100% packet loss gateway unreachable [admin@MikroTik] >There are four loop control commands in console. They all have do argument, which is the console commands that have to be executed repeatedly.
[admin@MikroTik] > :for i from=100 to=1 step=-37 do={:put ($i . " - " . 1000 / $i)} 100 - 10 63 - 15 26 - 38 [admin@MikroTik] >
[admin@MikroTik] > :foreach i in=[/interface find type=ether ] do={ {... :put [/interface get $i name] {... :foreach j in=[/ip address find interface=$i] do={ {{... :put [/ip address get $j address] {{... } {... } ether1 ether2 10.0.0.65/24 [admin@MikroTik] >
[admin@MikroTik] > :time {:delay 1756ms} 1.755333s [admin@MikroTik] > :put [:time {:delay}] 1.007464s 1s7.464ms [admin@MikroTik] >
[admin@MikroTik] > :log facility=System-Warning message="Very Bad Thing happened" [admin@MikroTik] >
[admin@MikroTik] > :environment print Global Variables g1=this is global variable Local Variables g1=this is global variable l1=this is local variable counter=2 [admin@MikroTik] >This can be useful in debugging scripts, or just for figuring out how variables work in console. Suppose we don't want to use variable "g1" anymore:
[admin@MikroTik] > :unset g1 [admin@MikroTik] > :environment print Global Variables g1=this is global variable Local Variables l1=this is local variable counter=2 [admin@MikroTik] > :put $g1 ERROR: unknown variable g1 [admin@MikroTik] >Here, although such global variable still exists (and we can get it back with :global g1 command), it is unknown because we have told current script to forget about it.
[admin@MikroTik] > :global g1 [admin@MikroTik] > :put $g1 this is global variable [admin@MikroTik] >
[admin2@kzd] > /interface [admin2@kzd] interface> monitor-traffic ether2 once do={:environment print} received-packets-per-second: 2 received-bits-per-second: 960.00bps sent-packets-per-second: 0 sent-bits-per-second: 0.00bps Global Variables Local Variables sent-bits-per-second=0 received-packets-per-second=2 received-bits-per-second=960 sent-packets-per-second=0 [admin2@kzd] interface>Monitor command with do argument can also be called directly from scripts. It will not print anything then, but just execute the given script.
[admin2@kzd] interface> :put [/interface get ether1 disabled ] true [admin2@kzd] interface>If command level has general settings, get command only takes the name of property:
[admin2@kzd] interface> :put [/system clock get time ] oct/23/2002 01:44:39 [admin2@kzd] interface>Names of properties that can be accessed by get are the same as shown by print command, plus names of item flags (like the disabled in the example above). You can use tab key completions to see what properties any particular get command can return.
It is possible to put multiple commands on single line, separating them by ';'. Console treats ';' as end of line when separating script text into commands.
If you want to use any of {}[]"'\$ characters in string, you have to prefix them with '\' character. Console takes any character following '\' literally, without assigning any special meaning to it, except for such cases:
\a bell (alarm), character code 7 \b backspace, character code 8 \f form feed, character code 12 \n newline, character code 10 \r carriage return, character code 13 \t tabulation, character code 9 \v vertical tabulation, character code 11 \_ space, character code 32Also, '\' followed by any amount of whitespace characters (spaces, newlines, carriage returns, tabulations), followed by newline is treated as a single whitespace, except inside quotes, where it is treated as nothing. This is used by console to break up long lines in scripts generated by export commands.