Scripting
RouterOS 脚本可通过用户定义的脚本实现自动化,这些脚本由系统调度或流量监控等事件触发。脚本使用命令行语法,支持可选前缀、命名参数等。
脚本
RouterOS 包含强大的脚本语言,可用于自动化维护任务。你可以创建用户定义的脚本并将其绑定到事件。
你可以将脚本存储在脚本仓库中,也可以直接在 CLI 中编写。常见触发器包括系统调度器、流量监控工具和 Netwatch 工具事件。
如果你已经熟悉 RouterOS 脚本,可以直接查看我们的提示与技巧。
行结构
RouterOS 脚本由若干命令行组成。命令行会逐条执行,直到脚本结束或发生运行时错误。
命令行
RouterOS CLI 使用以下命令语法:
[prefix] [path] command [uparam] [param=[value]] .. [param=[value]]
- [prefix] -
:或/字符,指示后面的词是全局命令还是路径。从根菜单或相对于所调用路径/命令的菜单调用时,不需要前缀。 - [path] - 指向目标菜单级别的相对路径。可能不需要。
- command - 指定菜单级别中可用的命令之一。
- [uparam] - 未命名参数,如果命令需要则必须指定。
- [params] - 一系列命名参数及其值(如果参数需要值)。
命令行的结尾由标记 ; 或换行符表示。
括在 ()、[] 或 {} 中的命令不需要任何命令结束字符。命令的结束由整个脚本的内容决定
:if ( true ) do={ :put "lala" }
另一命令行内部的每个命令行都以方括号 [](命令连接运算符)开始和结束。
:put [/ip route get [find gateway=1.1.1.1]];
注意,上面的代码包含三个命令行:
:put/ip route getfind gateway=1.1.1.1
注意,菜单特定的 find 命令不需要完整路径,因为路径是从父命令的路径推导出来的。
命令行可以按照行连接规则由多个物理行构成。
物理行
物理行是由行结束(EOL)序列终止的字符序列。可以使用任何标准平台的行终止序列:
- Unix - ASCII LF;
- Windows - ASCII CR LF;
- mac - ASCII CR;
可以使用标准 C 的换行字符约定(\n、\r\n 字符)。
注释
注释适用以下规则:
- 注释以井号
#开始,到物理行末尾结束。 - RouterOS 不支持多行注释。
- 如果
#出现在字符串内部,则不会被视为注释。
# this is a comment
# continued comment in the next line
:global a; # comment describing variable
:global myStr "part of the string # is not a comment"
行连接
可以使用反斜杠字符(\)将两个或多个物理行连接为逻辑行。
使用反斜杠作为行连接工具时适用以下规则:
- 以反斜杠结尾的行不能带注释。
- 反斜杠不会延续注释。
- 除字符串字面量外,反斜杠不会延续标记。
- 在字符串字面量之外的任何位置使用反斜杠都是非法的。
:if ($a = true \
and $b=false) do={ :put "$a $b"; }
:if ($a = true \ # bad comment (syntax error)
and $b=false) do={ :put "$a $b"; }
# comment \
continued is invalid (syntax error)
标记之间的空白
空白可用于分隔标记。只有当两个标记的连接可能被解释为不同的标记时,才需要在它们之间使用空白。示例:
{
:local a true; :local b false;
# whitespace is not required
:put (a&&b);
# whitespace is required
:put (a and b);
}
不允许在以下位置使用空白字符:
<parameter>=之间。from=、to=、step=、in=、do=、else=之间。
示例:
#incorrect:
:for i from = 1 to = 2 do = { :put $i }
#correct syntax:
:for i from=1 to=2 do={ :put $i }
:for i from= 1 to= 2 do={ :put $i }
#incorrect
/ip route add gateway = 3.3.3.3
#correct
/ip route add gateway=3.3.3.3
作用域
变量只能在脚本的特定区域中使用,这些区域称为作用域。这些区域决定变量的可见性。在块内声明的变量只能在该块及其嵌套块内、且在声明点之后访问。
作用域有两种类型:全局和局部。
全局作用域
全局作用域或根作用域是脚本的默认作用域。它会自动创建,且无法关闭。
由另一个脚本设置的全局变量可以通过声明而不赋值来访问。
例如,第一个脚本将 myVar 的值设置为 3:
:global myVar 3
要从另一个脚本访问此变量:
:global myVar
:put "myVar=$myVar"
输出:
myVar=3
局部作用域
你可以定义组来限制变量访问;这些组称为局部作用域。每个局部作用域都括在花括号({})中。
{
:local a 3;
{
:local b 4;
:put ($a+$b);
} #line below will show variable b in light red color since it is not defined in this scope
:put ($a+$b);
}
变量 b 在局部作用域中声明,在右花括号之后无法访问。
CLI 中编写的每一行都被视为一个局部作用域。 例如,定义的局部变量在下一命令行中不可见,并会产生语法错误
[admin@MikroTik] > :local myVar a;
[admin@MikroTik] > :put $myVar
syntax error (line 1 column 7)
危险
不要在局部作用域内定义全局变量。 即使变量可以定义为全局变量,也只有从其作用域中才能访问,除非它被引用为在作用域外可见。
{
:local a 3;
{
:global b 4;
}
:put ($a+$b);
}
代码输出 3,因为 b 在作用域外不可见。
以下代码修复了该问题,并按预期输出 7:
{
:local a 3;
{
:global b 4;
}
:global b;
:put ($a+$b);
}
关键字
以下词语是关键字,不能用作变量名和函数名:
and or in
分隔符
以下标记在语法中用作分隔符:
() [] {} : ; $ /
数据类型
RouterOS 脚本语言具有以下数据类型:
| 类型 | 说明 |
|---|---|
| num(数字) | 64 位有符号整数,可输入十六进制; |
| bool(布尔) | 值可以是 true 或 false。 |
| str(字符串) | 字符序列。 |
| ip | IP 地址。 |
| ip-prefix | IP 前缀。 |
| ip6 | IPv6 地址。 |
| ip6-prefix | IPv6 前缀。 |
| id(内部 ID) | 以 * 字符为前缀的十六进制值。每个菜单项都有一个分配的唯一编号 - 内部 ID。 |
| time | 日期和时间值。 |
| array | 组织在数组中的值序列。 |
| nil | 未赋值时的默认变量类型。 |
常量转义序列
以下转义序列可用于在字符串中定义特定特殊字符:
| " | 插入双引号。 |
| \ | 插入反斜杠。 |
| \n | 插入换行符。 |
| \r | 插入回车符。 |
| \t | 插入水平制表符。 |
| $ | 输出 $ 字符。否则,$ 用于链接变量。 |
| \_ | 空格。 |
| \a | BEL (0x07)。 |
| \b | 退格 (0x08)。 |
| \f | 换页 (0x0C)。 |
| \v | 插入垂直制表符。 |
| \xx | 由十六进制值表示的打印字符。十六进制数字应使用大写字母。 |
例如:
:put "\48\45\4C\4C\4F\r\nThis\r\nis\r\na\r\ntest";
将输出:
HELLO
This
is
a
test
运算符
算术运算符
RouterOS 脚本语言支持常见算术运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
+ | 二元加法 | :put (3+4); |
- | 二元减法 | :put (1-6); |
* | 二元乘法 | :put (4*5); |
/ | 二元除法 | :put (10 / 2); :put ((10)/2) |
% | 取模运算 | :put (5 % 3); |
- | 一元取负 | { :local a 1; :put (-a); } |
注意,要使除法正常工作,必须在被除数周围使用花括号或空格,以免被误认为 IP 地址。
关系运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
< | 小于 | :put (3<4); |
> | 大于 | :put (3>4); |
= | 等于 | :put (2=2); |
<= | 小于或等于 | |
>= | 大于或等于 | |
!= | 不等于 |
要对表达式取反,可以使用 <expression>=false。例如,要打印所有不是 "ethernet" 的接口,可以这样使用表达式取反:
/interface/print where (name~"ether")=false
或者执行相反操作,使用 <expression>=true:
/interface/print where (name~"ether")=true
逻辑运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
! | 逻辑非 | :put (!true); |
&&, and | 逻辑与 | :put (true&&true) |
||, or | 逻辑或 | :put (true||false); |
in | :put (1.1.1.1/32 in 1.0.0.0/8); |
位运算符
位运算符仅适用于 IP 和 IPv6 地址数据类型。
| 运算符 | 说明 | 示例 |
|---|---|---|
~ | 位反转。 | :put (~0.0.0.0) :put (~::ffff) |
| | 按位或,对每一对对应位执行逻辑或运算。在每一对中,如果其中一个位或两个位为 "1",则结果为 "1",否则结果为 "0"。 | :put (192.168.88.0|0.0.0.255) :put (2001::1|::ffff) |
^ | 按位异或与或相同,但每个位置的结果在两位不相等时为 "1",相等时为 "0"。 | :put (1.1.1.1^255.255.0.0) :put (2001::ffff:1^::ffff:0) |
& | 按位与,如果第一个位和第二个位都为 "1",则结果为 "1"。否则结果为 "0"。 | :put (192.168.88.77&255.255.255.0) :put (2001::1111&ffff::) |
<< | 左移给定位数,IPv6 地址数据类型不支持。 | :put (192.168.88.77<<8) |
>> | 右移给定位数,IPv6 地址数据类型不支持。 | :put (192.168.88.77>>24) |
例如,使用 & 运算符根据给定 IP 和 CIDR 网络掩码计算子网地址:
{
:local IP 192.168.88.77;
:local CIDRnetmask 255.255.255.0;
:put ($IP&$CIDRnetmask);
}
获取给定 IP 地址的最后 8 位:
:put (192.168.88.77&0.0.0.255);
使用 | 运算符和反转的 CIDR 掩码计算广播地址:
{
:local IP 192.168.88.77;
:local Network 192.168.88.0;
:local CIDRnetmask 255.255.255.0;
:local InvertedCIDR (~$CIDRnetmask);
:put ($Network|$InvertedCIDR)
}
连接运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
. | 连接两个字符串。 | :put ("concatenate" . " " . "string"); |
, | 连接两个数组或向数组添加元素。 | :put ({1;2;3} , 5 ); |
可以直接将变量值添加到字符串中,而无需连接运算符:
:global myVar "world";
# value can be added with concatenation operator
:put ("Hello " . $myVar);
# or without operator
:put "Hello $myVar";
使用 $[] 和 $() 可以执行表达式并将结果值插入字符串:
:local a 5;
:local b 6;
:put " 5x6 = $($a * $b)";
:put " We have $[ :len [/ip route find] ] routes";
其他运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
[] | 命令替换,只能包含一个命令行 | :put [ :len "my test string"; ]; |
() | 子表达式或分组运算符。 | :put ( "value is " . (4+5)); |
$ | 替换运算符。 | :global a 5; :put $a; |
~ | 二元运算符,将值与 POSIX 扩展正则表达式匹配。 | 打印网关以 202 结尾的所有路由:/ip/route/print where gateway~"^[0-9 \\.]*202\$" |
-> | 按键获取数组元素。 | [admin@x86] >:global aaa {a=1;b=2}``[admin@x86] > :put ($aaa->"a")``1``[admin@x86] > :put ($aaa->"b")``2 |
变量
脚本语言有两种类型的变量:
变量可能未定义。此时解析器会查找应用程序提供的内置变量。例如,DHCP 的 lease-script 提供了几个内置变量:
/system script
add name=myLeaseScript policy=\
ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive,api \
source=":log info \$leaseActIP\r\
\n:log info \$leaseActMAC\r\
\n:log info \$leaseServerName\r\
\n:log info \$leaseBound"
/ip dhcp-server set myServer lease-script=myLeaseScript
除内置 RouterOS 变量外,每个变量在使用前都必须用 local 或 global 关键字声明。使用未声明的变量会导致编译错误。例如:
# following code will result in compilation error, because myVar is used without declaration
:set myVar "my value";
:put $myVar
正确代码:
:local myVar;
:set myVar "my value";
:put $myVar;
变量名中的有效字符是字母和数字。如果变量名包含字母或数字以外的任何字符(包括运算符),请用双引号("")将其括起来。示例:
#valid variable name
:local myVar;
#invalid variable name
:local my-var;
#valid because double quoted
:global "my-var";
如果变量最初定义时没有值,变量数据类型会设置为 nil;否则脚本引擎会自动确定数据类型。有时需要从一种数据类型转换为另一种。可以使用数据转换命令实现。示例:
#convert string to array
:local myStr "1,2,3,4,5";
:put [:typeof $myStr];
:local myArr [:toarray $myStr];
:put [:typeof $myArr]
变量名区分大小写。
:local myVar "hello"
# following line will generate error, because variable myVAr is not defined
:put $myVAr
# correct code
:put $myVar
不带值的 set 命令会取消变量定义:
#remove variable from environment
:global myVar "myValue"
:set myVar;
保留变量名
所有内置 RouterOS 属性都是保留变量。使用与 RouterOS 内置属性相同的名称定义变量可能会导致错误。为避免此问题,请选择不同的名称。
例如,以下脚本将无法工作:
{
:local type "ether1";
/interface print where name=$type;
}
但使用不同定义的变量就可以工作:
{
:local customname "ether1";
/interface print where name=$customname;
}
命令
全局命令
每个全局命令都应以 : 标记开头;否则它会被视为变量。
| 命令 | 语法 | 说明 | 示例 |
|---|---|---|---|
| / | 转到根菜单。 | ||
| .. | 返回上一级菜单。 | ||
| ? | 列出所有可用菜单命令及简要说明。 | ||
| global | :global <var> [<value>] | 定义全局变量。 | :global myVar "something"; :put $myVar; |
| local | :local <var> [<value>] | 定义局部变量。 | { :local myLocalVar "I am local"; :put $myLocalVar; } |
| beep | :beep frequency=[num] length=[num] | 让内置扬声器发出蜂鸣声。 | |
| convert | :convert from=[arg] to=[arg] transform=[arg] | 将指定值从一种格式转换为另一种格式。如果未指定 from 格式,默认使用自动解析的值(例如 "001" 变为 "1","10.1" 变为 "10.0.0.1" 等)。from - 指定值的格式:base32、base64、bit-array-lsb、bit-array-msb、byte-array、hex、num、raw、url。to - 指定输出值的格式:base32、base64、bit-array-lsb、bit-array-msb、byte-array、hex、num、raw、url。transform - 指定如何转换值:lc(转换为小写)、uc(大写)、lcfirst(首字母小写)、ucfirst(首字母大写)、crlf、ed25519-private-to-x25519-private、none、rot 13、x25519-private-to-x25519-public、ed25519-private-to-ed25519-public、ed25519-public-to-x25519-public、md5、reverse(反转文本)、sha512。 | :put [:convert 001 to=hex ] 31 :put [:convert [/ip dhcp-client/option/get hostname raw-value] from=hex to=raw ] MikroTik :put [:convert transform=lc "AAA"] aaa |
| delay | :delay <time> | 在给定时间段内不执行任何操作。 | |
| environment | :environment print <start> | 打印已初始化变量的信息。 | :global myVar true; :environment print; |
| error | :error <output> | 生成控制台错误并停止执行脚本。 | |
| execute | :execute <expression> | 在后台执行脚本。可以通过设置 file 参数将结果写入文件,或通过设置 as-string 打印到 CLI。使用 as-string 参数时,执行的脚本会被阻塞(不在后台执行)。执行的脚本不能大于 64 kB。 | { :local j [:execute {/interface print follow where [:log info ~Sname~]}]; :delay 10s; :onerror e {/system script job remove $j}} |
| find | :find <arg> <arg> <start> | 返回子字符串或数组元素的位置。 | :put [:find "abc" "a" -1]; |
| grep | :grep script=[str] pattern=[expression] after=[num] before=[num] filename=[str] | 在终端中执行提供的 script,并打印与给定 pattern 匹配的行。参数 after 和 before 设置匹配行前后额外打印的行数。可以使用 filename 参数将结果直接保存到文件中。 | :grep script="/interface print" pattern="ether" after=1 before=1 filename=results.txt |
| jobname | :jobname | 返回当前脚本名称。 | 将脚本执行限制为单实例 :if ([/system script job print count-only as-value where script=[:jobname] ] > 1) do={ :error "script instance already running" } |
| len | :len <expression> | 返回字符串长度或数组元素数量。 | :put [:len "length=8"]; |
| log | :log <topic> <message> | 向系统日志写入消息。可用主题有 debug、error、info 和 warning。 | :log info "Hello from script"; |
| onerror | :onerror <var_name> in={<command>} do={<expression>} | 捕获错误并获取错误详情。当 in={...} 块发生错误时执行 do={...} 块,错误详情写入 <var_name> 变量。参数顺序很重要。error 参数必须在 do 块之前设置,否则 do 块将看不到局部变量。:onerror 会返回 false(如果没有错误)和 true(如果有错误),除非另有指定(使用 :return 或 :error 等命令),因此可以在 :if 条件语句脚本中使用。 | :onerror errorName in={ :error "failure" } do={ :put "Critical $errorName" } |
| parse | :parse <expression> | 解析字符串并返回解析后的控制台命令。可用作函数。 | :global myFunc [:parse ":put hello!"]; $myFunc; |
| pick | :pick <var> <start> [<end>] | 返回元素或子字符串的范围。如果未指定数量,则只返回数组中的一个元素。var - 要从中选取元素的值。start - 开始选取的元素(第一个元素索引为 0)。end - 终止索引(不包含该索引处的元素)。 | [admin@MikroTik] > :put [:pick "abcde" 1 3]``bc |
| put | :put <expression> | 将提供的参数打印到终端。 | :put "Hello world" |
| range | :range <var> <var> | 从指定范围创建数组。 | :put [:range 2 8] 2;3;4;5;6;7;8 |
| resolve | :resolve <arg> [<domain-name>][<server>][<server-port>][<type>] | 返回给定 DNS 名称的 IP 地址。domain-name - 需要解析的 DNS 名称。server - 用于解析 DNS 名称的特定服务器(返回的结果不会被缓存)。server-port - 要连接的服务器端口。type - any/any6/ipv4/ipv6:any - 尝试解析 IPv4,失败后尝试 IPv6。any6 - 尝试解析 IPv6,失败后尝试 IPv4。ipv4 - 仅解析 IPv4。ipv6 - 仅解析 IPv6。 | :put [:resolve "www.mikrotik.com"]; :put [:resolve domain-name="www.mikrotik.com"]; :put [:resolve domain-name="www.mikrotik.com" server=192.168.88.1 server-port=53]; :put [:resolve domain-name="www.mikrotik.com" type=ipv6]; |
| retry | :onerror e {:retry command=<expr> delay=[num] max=[num]} do={<expr>} | 尝试执行给定命令 max 次,每次尝试之间使用给定的 delay(秒)。失败时,执行 do={} 块中的命令。 | :onerror e {:retry command={abc} delay=1 max=2} do={:put "got error"} |
| typeof | :typeof <var> | 返回给定变量的数据类型。 | :put [:typeof 4]; |
| rndnum | :rndnum from=[num] to=[num] | 随机数生成器。 | :put [:rndnum from=1 to=99]; |
| rndstr | :rndstr from=[str] length=[num] | 随机字符串生成器。from 指定用于构造字符串的字符,默认为所有 ASCII 字母和数字。length 指定要创建的字符串长度,默认为 16。 | :put [:rndstr from="abcdef%^&``" length=33]; |
| set | :set <var> [<value>] | 为已声明的变量赋值。 | :global a; :set a true; |
| serialize | :serialize [<value>] to=[arg] | 将指定值/数组序列化为 JSON 或 dsv(分隔符分隔值)格式。value 指定要处理的值。to 指定格式 - json、dsv。delimiter 设置“分隔符”。order 指定变量的顺序。options 指定附加选项:json.pretty - 使 JSON 输出更美观;json.no-string-conversion - 防止从控制台字符串类型到 JSON 数字类型的隐式转换;dsv.wrap-strings - 将字符串值括在引号中;dsv.ignore-size - 如果数组值大小不同,例如 <code>a=(1,2);b=(3,4);c=(5,6,7)</code>,此选项将绕过 <code>array size mismatch</code> 错误并在这些槽中设置“空”值;dsv.remap - 将字典数组合并为单个字典(在处理 "<code>print as-value</code>" 时很有用)。file-name 启用将命令输出生成到文件中的选项(可在 "/files" 部分下载)。 | :put [:serialize value=a,b,c to=json]``["a","b","c"]``:local test {a=(1,2,3);b=(4,5,6);c=(7,"text",9)}; :put [ :serialize to=dsv delimiter=";" value=$test order=("c","a","b") ]``c;a;b``7;1;4``text;2;5``9;3;6``:global var ({ "string"="1234"; "number"=1234 });:put [ :serialize to=json value=$var ]``{"number":1234,"string":1234.000000}``:put [ :serialize to=json value=$var options=json.no-string-conversion ]``{"number":1234,"string":"1234"}``:put [:serialize to=dsv options=dsv.remap delimiter="#" [/ip/address/print as-value]]``.id#address#comment#interface#network``*1#192.168.88.1/24#defconf#bridge#192.168.88.0``*2#192.168.69.190/24##ether1#192.168.69.0 |
| deserialize | :deserialize [<value>] from=[arg] | 从 JSON 或 dsv(分隔符分隔值)格式反序列化指定值/数组。from 指定格式 - json、dsv。delimiter 设置“分隔符”。options 指定附加选项:dsv.plain - 将每一行反序列化为数组(输入没有表头或列名);dsv.array - 期望有表头(列名)并返回字典数组,其中值映射到表头中提供的列名;json.no-string-conversion - 防止从 JSON 字符串类型到控制台值(数字、IP 等)的隐式转换。 | :put [:deserialize from=json value="[\"a\",\"b\",\"c\"]"]``a;b;c``:put ([ :deserialize from=dsv delimiter=";" value="a;b;c\n1;findme;3" options=dsv.plain ]->1->1) findme``:put ([ :deserialize from=dsv delimiter=";" value="a;b;c\n1;findme;3" options=dsv.plain ]->0->1) ``b``:put ([:deserialize from=dsv "a;b;c\n1;2;3\n4;5;6" delimiter=";" options=dsv.array]->1->"b") ``5``:put ([:deserialize from=dsv "a;b;c\n1;2;3\n4;5;6" delimiter=";" options=dsv.array]->0->"c") ``3``:put [typeof ([:deserialize "{ \"str\": \"123\" }" from=json options=json.no-string-conversion]->"str")]``str``:deserialize [/file/get file.json contents] from=json |
| time | :time <expression> | 返回执行所提供表达式所需的时间间隔。 | :put [:time {:for i from=1 to=10 do={ :delay 100ms }}]; |
| timestamp | :timestamp [use-tz] | 返回自 epoch(1970 年 1 月 1 日,星期四)以来的时间,不计算闰秒。默认以 UTC 返回时间。使用 use-tz 参数可返回根据路由器上配置的时区调整后的时间戳。 | [admin@MikroTik] > :put [:timestamp]``2735w21:41:43.481891543或[admin@MikroTik] > :put [:timestamp]``2735w1d21:41:43.481891543带天数偏移。 |
| toarray | :toarray <var> | 将变量转换为数组。 | |
| tobool | :tobool <var> | 将变量转换为布尔值。 | |
| toid | :toid <var> | 将变量转换为内部 ID。 | |
| toip | :toip <var> | 将变量转换为 IP 地址。 | |
| toip6 | :toip6 <var> | 将变量转换为 IPv6 地址。 | |
| tonum | :tonum <var> | 将变量转换为整数。 | |
| tostr | :tostr <var> | 将变量转换为字符串。 | |
| totime | :totime <var> | 将变量转换为时间。 | |
| tonsec | :tonsec <var> | 将时间转换为纳秒。 | :put [:tonsec value=10:00] 36000000000000 |
| tocrlf | :tocrlf <var> | 将行尾转换为 CRLF。 | :put [:tocrlf "AAA\r\nBBB\r\nCCC" ]``AAA``BBB``CCC |
| tolf | :tolf <var> | 将行尾转换为 LF。 | :put [:tolf "AAA\nBBB\nCCC" ]``AAA`` BBB`` CCC |
| nothing | :nothing | 返回空值。 | :if ([:nothing] = 0) do={:put true} else={:put false} false :if ([:nothing] > 0) do={:put true} else={:put false} false :if ([:nothing] < 0) do={:put true} else={:put false} |
如果变量类型转换函数无法将新格式应用于所提供的数据,输出将为空。
例如,对值为 "23.8" 或 "cow&chicken" 等非整数的变量运行 :tonum <var> 命令,结果将为空,其数据类型将显示为 nil。
常用命令
以下命令在大多数子菜单中可用:
| 命令 | 语法 | 说明 |
|---|---|---|
| add | add <param>=<value>..<param>=<value> | 添加新项目。此命令通常具有与 set 相同的所有参数,但项目编号参数除外。它会使用你指定的值添加新项目,通常位于项目列表末尾(在项目顺序相关的场景中)。有一些必须提供的必需属性,例如新地址的接口,而其他属性除非你明确指定,否则会设置为默认值。返回所添加项目的内部编号。常用参数:copy-from - 复制现有项目。它从另一个项目获取新项目属性的默认值。如果你不想制作完全相同的副本,可以为某些属性指定新值。复制带名称的项目时,通常需要为副本指定新名称。place-before - 将新项目放置在具有指定位置的现有项目之前。因此,添加项目后无需使用 move 命令。disabled - 控制新添加项目的禁用/启用状态。comment - 保存新创建项目的描述。 |
| remove | remove <id> | 删除所选项目。 |
| enable | enable <id> | 启用所选项目。 |
| disable | disable <id> | 禁用所选项目。 |
| set | set <id> <param>=<value>..<param>=<value> | 更改所选项目的参数,可以同时指定多个参数。可以通过在参数前指定 '!' 来取消设置该参数。示例:/ip firewall filter add chain=blah action=accept protocol=tcp port=123 nth=4,2 print set 0 !port chain=blah2 !nth protocol=udp |
| get | get <id> <param>=<value> | 获取所选项目的参数值。默认不在终端中显示。如果与 :put 命令结合使用,则会在终端中显示检索到的值,例如::put [/system/resource/get version] 将显示设备上安装的 RouterOS 版本。 |
print <param><param>=[<value>] | 打印菜单项。输出取决于指定的打印参数。显示可从特定命令级别访问的所有信息。因此,/system/clock/print 显示系统日期和时间,/ip/route/print 显示所有路由等。如果当前级别中有项目列表且它们不是只读的,print 命令还会分配编号,供操作此列表中项目的所有命令使用。 | |
| export | export [file=<value>] | 从当前菜单及其子菜单(如果存在)导出配置。如果指定了 file 参数,则输出将写入扩展名为 '.rsc' 的文件,否则输出将打印到终端。导出的命令可以通过 import 命令导入。 |
| edit | edit <id> <param> | 在内置文本编辑器中编辑所选项目的属性。可用于编辑包含大量文本的属性值,例如脚本,但它适用于所有可编辑属性。根据终端的能力,会启动全屏编辑器或单行编辑器。控制台脚本的编辑字段限制为 3 万个字符。 |
| find | find <expression> | 返回与给定表达式匹配的项目的内部编号列表。具有与 set 相同的参数,外加 disabled 或 active 等标志参数,根据相应标志的值取 yes 或 no。要查看所有标志及其名称,请查看 print 命令输出的顶部。 |
| move | move <id> [<id>] | 更改列表中项目的顺序。第一个参数指定要移动的项目。第二个参数指定要将所有被移动项目放置在其之前的项目(如果省略第二个参数,则放在列表末尾)。 |
| reset | reset <id> [<param>=<value>] | 将参数重置为默认值。你也可以在同一命令中包含带参数的参数,从而同时重置和配置设置。 |
| comment | comment <id> <value> | 允许你为所选项目添加注释。 |
import
[admin@admin] > do { import test.rsc } on-error={ :put "Failure" }
Failure
可以使用 onerror 参数捕获错误:
[admin@admin] > onerror e in={ import test.rsc } do={ :put "Failure - $e" }
Failure - Script Error: bad command name this (line 1 column 1)
此外,import 命令在 verbose 模式下有新选项 - dry-run 参数用于调试,可在不更改配置的情况下查找错误。
[admin@admin] > import test.rsc verbose=yes dry-run
#line 1
this
bad command name this (line 1 column 1)
...
Script Error: found 5 error(s) in import file
print 参数
print 命令有几个常用参数:
| 参数 | 说明 | 示例 |
|---|---|---|
| append | ||
| as-value | 将输出打印为参数及其值的数组。 | :put [/ip address print as-value] |
| brief | 简要输出通常很精简,以表格形式表示项目,包含最常用的参数。 | |
| detail | 打印详细描述,输出不如简要输出易读,但可能有助于查看所有参数。 | |
| count-only | 仅打印当前菜单中的项目数量。 | |
| file | 将输出写入文件。 | |
| follow | 打印所有当前条目并跟踪新条目,直到按下 <kbd>ctrl</kbd>-<kbd>c</kbd>。可用于查看日志条目等。 | /log print follow |
| follow-only | 仅打印和跟踪新条目,直到按下 <kbd>ctrl</kbd>-<kbd>c</kbd>。可用于例如查看新日志条目。 | /log print follow-only |
| from | 仅从指定项目开始打印参数。 | /user print from=admin |
| interval | 在选定时间间隔内持续打印输出,适合跟踪 follow 不适用时的变化 | /interface print interval=2 |
| terse | 以紧凑且机器友好的格式显示详细信息。 | |
| value-list | 数据以表格形式显示,参数按行拆分,可用项目按列拆分(可用于解析目的)。 | |
| order-by | 按一个或多个属性对输出排序。在属性名前加 - 表示降序。最多可指定三个排序字段。 | /ip/arp/print order-by=address``/interface/print order-by=-link-downs proplist=name,link-downs``/interface/ethernet/print stats order-by=-driver-rx-packet where running |
| without-paging | 如果输出无法在控制台屏幕中完整显示,则一次性打印所有信息,不分页暂停。 | |
| where | 后跟 where 参数的表达式可用于过滤掉不匹配的条目。 | /ip route print where interface="ether1" |
| about | 返回具有 about 参数的条目,例如 "managed by CAPsMAN" 信息或警告 | /interface wifi print where about |
| !comments | 返回没有注释的条目 | /interface firewall filter print !comments |
可以同时指定多个参数,例如 /ip route print count-only interval=1 where interface="ether1"
循环和条件语句
循环
| 命令 | 语法 | 说明 |
|---|---|---|
do..while | :do { <commands> } while=( <conditions> ); :while ( <conditions> ) do={ <commands> }; | 执行命令直到满足给定条件。 |
for | :for <var> from=<int> to=<int> step=<int> do={ <commands> } | 在给定迭代次数内执行命令 |
foreach | :foreach <var> in=<array> do={ <commands> }; | 为列表中的每个元素执行命令。 |
条件语句
| 命令 | 语法 | 说明 |
|---|---|---|
if | :if (<condition>) do={<commands>} else={<commands>} | 如果给定条件为 true,则执行 do 块中的命令,否则执行 else 块中的命令(如果指定)。 |
示例:
{
:local myBool true;
:if ($myBool = false) do={ :put "value is false" } else={ :put "value is true" }
}
函数
函数的定义方式与全局变量类似,使用 global 关键字后跟 do= 运算符,函数体括在花括号({})中。
传递参数有两种方式:
- 传递带有特定名称的参数。
- 传递不带参数名称的值,此时使用参数 "1"、"2" .. "n" 来引用该值。
#define function and run it
:global myFunc do={:put "hello from function"}
$myFunc
output:
hello from function
#pass arguments to the function
:global myFunc do={:put "arg a=$a"; :put "arg '1'=$1"}
$myFunc a="this is arg a value" "this is arg1 value"
output:
arg a=this is arg a value
arg '1'=this is arg1 value
可以使用 :return 命令返回函数值。
:global myFunc do={ :return ($a + $b)}
:put [$myFunc a=6 b=2]
output:
8
你甚至可以从脚本环境克隆现有脚本并将其用作函数。
#add script
/system script add name=myScript source=":put \"Hello \$myVar !\""
:global myFunc [:parse [/system script get myScript source]]
$myFunc myVar=world
output:
Hello world !
脚本语言还允许通过 :parse 命令间接创建函数。
如果函数包含已定义的全局变量,且其名称与传入参数名相同,则出于与旧版本脚本的兼容性,全局定义的变量会被忽略。避免使用与全局变量同名的参数。
例如:
:global my2 "123"
:global myFunc do={ :global my2; :put $my2; :set my2 "lala"; :put $my2 }
$myFunc my2=1234
:put "global value $my2"
输出将为:
1234
lala
global value 123
要从一个函数调用另一个函数(嵌套函数),请像声明变量一样声明其名称:
:global funcA do={ :return 5 }
:global funcB do={
:global funcA;
:return ([$funcA] + 4)
}
:put [$funcB]
Output:
9
捕获运行时错误
RouterOS 脚本允许捕获运行时错误,否则这些错误会意外退出脚本。例如,如果 :resolve 命令失败,它会抛出错误并中断脚本。
[admin@MikroTik] > { :put [:resolve www.example.com]; :put "lala";}
failure: dns name does not exist
你可以捕获此错误并继续执行脚本:
:onerror e {:put [:resolve www.example.com]} do={:put "resolver failed"}
:put "print after failure"
output:
resolver failed
print after failure
数组操作
如果数组中的键名包含小写字符以外的任何字符,应将其放在引号中。 例如:
[admin@ce0] > {:local a { "aX"=1; ay=2 }; :put ($a->"aX")}
1
foreach 命令可用于遍历键和元素:
[admin@ce0] > :foreach k,v in={2; "aX"=1; y=2; 5} do={:put ("$k=$v")}
0=2
1=5
aX=1
y=2
如果 foreach 命令只带一个参数调用,则返回元素值:
[admin@ce0] > :foreach v in={2; "aX"=1; y=2; 5} do={:put ("$v")}
2
5
1
2
如果数组元素具有键,则这些元素会按字母顺序排序;没有键的元素会放在带键元素之前并保留其顺序(如上一个示例所示)。
可以使用键名更改单个元素的值:
[admin@MikroTik] > :global a {x=1; y=2}
[admin@MikroTik] > :set ($a->"x") 5
[admin@MikroTik] > :environment print
a={x=5; y=2}
脚本权限
根据脚本的调用方式,它可能:
- 使用自己的权限。
- 继承调用者的权限。
例如,考虑一个拥有完全权限的用户和一个未设置权限的脚本:
/user/print
Columns: NAME, GROUP, LAST-LOGGED-IN, INACTIVITY-POLICY
# NAME GROUP LAST-LOGGED-IN INACTIVITY-POLICY
;;; system default user
0 admin full 2025-07-22 17:09:59 none
/system script
add dont-require-permissions=no name=add-dhcp-no-perms owner=admin policy="" sour
ce="/ip dhcp-client add interface=ether2; put \"Added DHCP client on ether2!\""
让我们看看使用和不使用 use-script-permissions 参数运行脚本时会发生什么:
[admin@MikroTik] > system/script/run add-dhcp-no-perms use-script-permissions
not enough permissions (9)
[admin@MikroTik] > system/script/run add-dhcp-no-perms
Added DHCP client on ether2!
调度器遵循相同规则。在 on-event 中按名称调用的脚本,或通过 /system script run 调用的脚本,会以调度器条目的权限(其 policy)运行。只有 use-script-permissions 才会让脚本以自己的权限运行。
为便于比较,创建两个以不同方式运行同一脚本的调度器条目:
/system scheduler
add interval=10s name=run-script-use-script-perms on-event="/system script run add-dhcp-no-perms use-script-permissions" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
add interval=10s name=run-script-by-name on-event=add-dhcp-no-perms policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
日志显示,使用 use-script-permissions 的条目失败,因为该脚本没有权限。按名称调用脚本的条目则使用调度器的权限添加了 DHCP 客户端:
/log print
2026-09-25 17:54:29 script,error executing script add-dhcp-no-perms
2026-09-25 17:54:29 script,error,debug not enough permissions (9) (/ip/dhcp-client/add; line 1)
2026-09-25 17:54:29 script,error executing script
2026-09-25 17:54:29 script,error,debug (scheduler:run-script-use-script-perms) not enough permissions (9) (/ip/dhcp-client/add; line 1)
2026-09-25 17:54:30 system,info dhcp client added by scheduler:run-script-by-name/script:add-dhcp-no-perms (*2 = /ip dhcp-client add interface=ether2)
调用者不能运行其策略包含调用者不具备的权限的脚本:具有 policy=read 的调度器条目按名称调用具有 policy=read,write 的脚本会失败,并显示 not enough permissions。use-script-permissions 标志不会覆盖此限制。