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 get
  • find 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(字符串)字符序列。
ipIP 地址。
ip-prefixIP 前缀。
ip6IPv6 地址。
ip6-prefixIPv6 前缀。
id(内部 ID)以 * 字符为前缀的十六进制值。每个菜单项都有一个分配的唯一编号 - 内部 ID。
time日期和时间值。
array组织在数组中的值序列。
nil未赋值时的默认变量类型。

常量转义序列

以下转义序列可用于在字符串中定义特定特殊字符:

"插入双引号。
\插入反斜杠。
\n插入换行符。
\r插入回车符。
\t插入水平制表符。
$输出 $ 字符。否则,$ 用于链接变量。
\_空格。
\aBEL (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

变量

脚本语言有两种类型的变量:

  • 全局 - 当前用户创建的所有脚本都可访问,由 global 关键字定义。
  • 局部 - 仅在当前作用域内可访问,由 local 关键字定义。

变量可能未定义。此时解析器会查找应用程序提供的内置变量。例如,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。

常用命令

以下命令在大多数子菜单中可用:

命令语法说明
addadd <param>=<value>..<param>=<value>添加新项目。此命令通常具有与 set 相同的所有参数,但项目编号参数除外。它会使用你指定的值添加新项目,通常位于项目列表末尾(在项目顺序相关的场景中)。有一些必须提供的必需属性,例如新地址的接口,而其他属性除非你明确指定,否则会设置为默认值。返回所添加项目的内部编号。常用参数:copy-from - 复制现有项目。它从另一个项目获取新项目属性的默认值。如果你不想制作完全相同的副本,可以为某些属性指定新值。复制带名称的项目时,通常需要为副本指定新名称。place-before - 将新项目放置在具有指定位置的现有项目之前。因此,添加项目后无需使用 move 命令。disabled - 控制新添加项目的禁用/启用状态。comment - 保存新创建项目的描述。
removeremove <id>删除所选项目。
enableenable <id>启用所选项目。
disabledisable <id>禁用所选项目。
setset <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
getget <id> <param>=<value>获取所选项目的参数值。默认不在终端中显示。如果与 :put 命令结合使用,则会在终端中显示检索到的值,例如::put [/system/resource/get version] 将显示设备上安装的 RouterOS 版本。
printprint <param><param>=[<value>]打印菜单项。输出取决于指定的打印参数。显示可从特定命令级别访问的所有信息。因此,/system/clock/print 显示系统日期和时间,/ip/route/print 显示所有路由等。如果当前级别中有项目列表且它们不是只读的,print 命令还会分配编号,供操作此列表中项目的所有命令使用。
exportexport [file=<value>]从当前菜单及其子菜单(如果存在)导出配置。如果指定了 file 参数,则输出将写入扩展名为 '.rsc' 的文件,否则输出将打印到终端。导出的命令可以通过 import 命令导入。
editedit <id> <param>在内置文本编辑器中编辑所选项目的属性。可用于编辑包含大量文本的属性值,例如脚本,但它适用于所有可编辑属性。根据终端的能力,会启动全屏编辑器或单行编辑器。控制台脚本的编辑字段限制为 3 万个字符。
findfind <expression>返回与给定表达式匹配的项目的内部编号列表。具有与 set 相同的参数,外加 disabled 或 active 等标志参数,根据相应标志的值取 yes 或 no。要查看所有标志及其名称,请查看 print 命令输出的顶部。
movemove <id> [<id>]更改列表中项目的顺序。第一个参数指定要移动的项目。第二个参数指定要将所有被移动项目放置在其之前的项目(如果省略第二个参数,则放在列表末尾)。
resetreset <id> [<param>=<value>]将参数重置为默认值。你也可以在同一命令中包含带参数的参数,从而同时重置和配置设置。
commentcomment <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 标志不会覆盖此限制。

另请参阅