Scripting
RouterOS 脚本功能通过由系统调度或流量监控等事件触发的用户自定义脚本实现自动化。脚本使用带有可选前缀、命名参数和物理行格式的命令行语法,同时支持单行和多行执行。
脚本
RouterOS 包含一门功能强大的脚本语言,用于自动化维护任务。你创建用户自定义脚本,并将它们绑定到事件上。
你可以将脚本存储在 Script repository 中,或直接在 CLI 中编写。常见的触发器包括 System Scheduler、Traffic Monitoring Tool 和 Netwatch Tool 事件。
如果你已经熟悉 RouterOS 中的脚本编写,可以查看我们的技巧与窍门。
行结构
RouterOS 脚本分为若干命令行。命令行会逐条执行,直到脚本结束或发生运行时错误。
命令行
RouterOS CLI 使用以下命令语法:
[prefix] [path] command [uparam] [param=[value]] .. [param=[value]]
- [prefix] -
:或/字符,表示后面的词是全局命令还是路径。从根菜单或相对于所调用路径/命令的菜单调用时不需要前缀。 - [path] - 指向所需菜单级别的相对路径。可能不需要。
- command - 指定菜单级别中可用的命令之一。
- [uparam] - 未命名参数,如果命令需要则必须指定。
- [params] - 一系列命名参数,后跟值(如果参数需要一个值)。
命令行末尾由 token ; 或 NEWLINE 表示。
用 ()、[] 或 {} 括起来的命令不需要任何命令结束字符。命令的结束由整个脚本的内容决定
: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"
行连接
可以使用反斜杠字符(\)将两个或多个物理行连接为逻辑行。
使用反斜杠作为行连接工具时适用以下规则:
- 以反斜杠结尾的行不能带有注释。
- 反斜杠不会延续注释。
- 除字符串字面量外,反斜杠不会延续 token。
- 在字符串字面量之外,反斜杠在行上的其他任何位置都是非法的。
: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)
token 之间的空白
空白可用于分隔 token。只有当两个 token 的连接可能被解释为不同的 token 时,才必须在它们之间使用空白。示例:
{
: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
作用域
变量只能在脚本的特定区域中使用,这些区域称为作用域。这些区域决定变量的可见性。在块内声明的变量只能在该块及其嵌套块内访问,并且只能在声明点之后访问。
作用域有两种类型 —— global 和 local。
全局作用域
全局作用域或根作用域是脚本的默认作用域。它会自动创建,且无法关闭。
由另一个脚本设置的全局变量,可以通过在不赋值的情况下声明它来访问。
例如,第一个脚本将 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
分隔符
以下 token 在语法中充当分隔符:
() [] {} : ; $ /
数据类型
RouterOS 脚本语言具有以下数据类型:
| 类型 | 描述 |
|---|---|
| num (number) | 64 位有符号整数,可输入十六进制; |
| bool (boolean) | 值可以是 true 或 false。 |
| str (string) | 字符序列。 |
| ip | IP 地址。 |
| ip-prefix | IP 前缀。 |
| ip6 | IPv6 地址。 |
| ip6-prefix | IPv6 前缀。 |
| id (internal 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;
}
命令
全局命令
每个全局命令都应以 : token 开头;否则它会被当作变量处理。
| 命令 | 语法 | 描述 | 示例 |
|---|---|---|---|
| / | 转到根菜单。 | ||
| .. | 返回上一菜单级别。 | ||
| ? | 列出所有可用的菜单命令及简要说明。 | ||
| 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 块将看不到该局部变量。除非另有指定(使用 :return 或 :error 等命令),:onerror 将返回 false(如果没有错误)和 true(如果有错误),因此可用于 :if 条件语句脚本中。 | :onerror errorName in={ :error "failure" } do={ :put "Critical $errorName" } |
| parse | :parse <expression> | 解析字符串并返回解析后的控制台命令。可用作函数。 | :global myFunc [:parse ":put hello!"]; $myFunc; |
| pick | :pick <var> <start> [<end>] | 返回元素范围或子字符串。如果未指定 count,则只从数组返回一个元素。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>} | 尝试以给定的尝试间隔 delay(秒)执行给定命令 max 次。失败时,执行 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 字符串类型到控制台值(number、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 以来的时间,其中 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> | 将变量转换为 time。 | |
| tonsec | :tonsec <var> | 将 time 转换为纳秒。 | :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 | 返回空值(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 相同的所有参数,但项目编号参数除外。它会使用你指定的值添加一个新项目,通常添加在项目列表末尾,在项目顺序相关的位置。有些必需属性必须提供,例如新地址的 interface,而其他属性除非你显式指定,否则会设置为默认值。返回它添加项目的内部编号。常见参数: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>] | 打印菜单项。输出取决于指定的 print 参数。显示可从特定命令级别访问的所有信息。因此,/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!
类似地,使用调度器运行脚本有多种方式。当调度器运行脚本时,它可能以调度器的权限执行。
你可以使用调度器按名称调用脚本;其工作方式与 /system script run 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-direct on-event=add-dhcp-no-perms policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
add interval=10s name=run-script-scheduler-perms on-event="/system script run add-dhcp-no-perms" policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
当所有创建的调度器都执行该脚本时,日志将显示使用 use-script-permissions 或按名称调用脚本的两种方法因权限不足而失败。
相比之下,run-script-scheduler-perms 成功执行该脚本,因为它继承了调度器的权限。
/log print
2025-07-22 18:11:25 script,error executing script add-dhcp-no-perms from scheduler (run-script-direct) failed, please check it manually
2025-07-22 18:11:25 script,error,debug not enough permissions (9) (/ip/dhcp-client/add; line 1)
2025-07-22 18:11:25 script,error executing script from scheduler (run-script-use-script-perms) failed, please check it manually
2025-07-22 18:11:25 script,error,debug (scheduler:run-script-use-script-perms) not enough permissions (9) (/ip/dhcp-client/add; line 1)
2025-07-22 18:11:25 system,info dhcp client added by scheduler:run-script-scheduler-perms/script:add-dhcp-no-perms (*7 = /ip dhcp-client add interface=ether2)
具有比用户/调度器更高或更多权限的脚本无法运行;use-script-permissions 不会覆盖这一点。