Fi MikroTik RouterOS V2.6 Scripting Manual

Scripting Manual

Document revision 29-Nov-2002
This document applies to the MikroTik RouterOS V2.6

Overview

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.

Contents of the Manual

Scripts

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>

Network Watching Tool

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> 

Writing Scripts

Console scripting introduction

Although 2.6 console syntax has many changes from previous versions, most users will not notice any differences. However, if you are using scripting capabilities of RouterOS, it is recommended to read this section, even if you have some experience with previous console versions.

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.

Command

Console commands in 2.6 are made from the following parts:
PREFIX PATH PATH_ARGUMENT COMMAND NAMELESS_ARGUMENTS ARGUMENTS
first, 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") 3
is not.

Grouping level commands

It is possible to execute several commands from the same command level, by grouping them with '{}'. For example:
[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.

Variables

Console allows to create and use global (system wide) and local (only usable within one script) variables. Variables can be accessed by writing '$' followed by name of variable. Variable names can contain letters, digits and '-' character.
[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:
  • With :global. It introduces name of global variable, which is created if it doesn't exist already.
    [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.
  • With :local. It introduces new local variable, which is not shared with any other script, other instance of the same script, other console logins. It's value is lost when script finishes or when variable name is freed by :unset.
    [admin@MikroTik] > :local l1
    [admin@MikroTik] > :set l1 "this is local variable"
    [admin@MikroTik] > :put $l1
    this is local variable
    [admin@MikroTik] >
    
  • With :for and :foreach commands, which introduce loop index variable. It's valid only in the do= block of commands and is removed after command completes.
    [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.
  • monitor commands, that have do= argument. See details below.

    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.

    Changing variable values

    You can assign new value to variable using :set command. It has two unnamed arguments. First is name of variable. Second is the new value of variable.
    [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.

    Command substitution, return values

    Some console commands are most useful if their output can be used as an argument value in other commands. In console, this is done by "returning" value from commands. Return value is not displayed on the screen. When you type such command between square brackets '[' ']', this command is executed and it's return value is used as the value of these brackets. This is called command substitution. Consider find command.
    [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.

    Expressions

    Console can do a simple math with numbers, time values, ip addresses, and strings and lists. It is done by writing expressions, putting them in parentheses '(' and ')'.
    [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

    Value types

    Console can work with several types of values. Currently it distinguishes between strings, truth values (also known as booleans), numbers, time intervals, ip addresses, internal numbers and lists. Currently console tries to convert any value to the most specific type first, backing up if it fails. This is the order in which console attempts to convert value:
  • list
  • internal number
  • number
  • ip address
  • time value
  • truth value
  • string value

    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)

    Colon commands

    Console has many built-in commands that start with ':' prefix. They don't change configuration directly, but are most useful for writing scripts. You can see list of all such commands by pressing '?' after typing just the ':' prefix:
    [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.

    Monitor commands

    It is possible to access values that are shown by most monitor commands from scripts. If monitor command has do argument, it can be supplied either script name (see /system scripts), or console commands. If do argument is present, monitor command will execute given script after each time it prints stats on the screen, and it will assign all printed values to local variables with the same name:
    [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.

    Get commands

    It is also possible to access from scripts values that are shown by most print commands. Most command levels that have print command, also have get command. It has one or two unnamed arguments. If this command level deals with list of items, first argument is name or internal number of item. Second argument is a name of item's property which should be returned.
    [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.

    More on syntax

    It is possible to include comments in console scripts. If script line starts with '#', all characters until newline are ignored

    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 32
    
    Also, '\' 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.
    Copyright 1999-2001, MikroTik