Skip to content

CLI tips and tricks

Daniel Frantík edited this page Oct 16, 2026 · 2 revisions

CLI tips and tricks

A cookbook for writing RouterOS command lines by hand — for the raw CLI level, where tik4net sends your line unchanged. Every line here works the same on Telnet, Ssh, MacTelnet, WinboxCli and WinboxCliMac.

If you are using the API dialect (/ip/address/print with ?interface=ether1), none of this is your concern: the translation applies these rules for you — see Command translation on non-API transports.

Reading a table

Intent Line Watch out
all rows :put [/ip address print as-value] A bare print as-value prints nothing — the console produces as-value output only in script context, so wrap it in :put [ … ].
every field :put [/interface print detail as-value] Without detail you get the summary columns: 8 fields of a commented /interface row, against 14 with it.
chosen fields :put [/interface print as-value proplist=name,type] Comes back as .id, name, type.
a filter … where interface=ether1
several conditions … where running and type=ether and, or, !, parentheses. A bare field name tests "is set".
subnet membership … where address in 192.0.2.0/24
a value with /, :, . or a space … where address="10.0.0.1/24" Unquoted, / and : are operators and the filter matches nothing. Before 7.20 a bare dotted value can be refused. Quote anything outside [A-Za-z0-9._-].
a boolean … where disabled=no Only yes / no. =false is refused (expected yes or no); ="false" is accepted and matches nothing.
set vs. empty … where comment / … where comment="" Opposite queries: the first is "has a comment", the second "comment equals empty".
counters :put [/interface print stats as-value] Counters are not in detail, and detail stats behaves like stats alone — read config and counters separately and join them on .id.
secrets (7.x) :put [/ppp secret print detail show-sensitive as-value] Without show-sensitive a secret is absent, not empty. RouterOS 6 has no such word and prints secrets as they are.
a singleton :put [/system resource print as-value] No detail here — a singleton refuses it (bad parameter detail).
a count /interface print count-only Not inside :put [ … ]: print count-only prints the number itself and :put prints it again, so the answer comes back twice.
exact free text :put [:serialize to=json [/system script print as-value]] as-value has no escaping: a ;, = or newline in a value reads as more fields. JSON is exact. RouterOS 7.13+.

Reading one value — get

Intent Line Watch out
a singleton's field :put [/system identity get name] No as-value — get takes the value name positionally, so as-value is read as a field called "as-value".
a row's field :put [/interface get number=*2 value-name=name] The row is number=, never .id= (bad parameter .id). An .id is accepted as the number.
a whole row :put [/interface get *2] One as-value string — the same text the binary API puts in =ret=.
a field of every row :foreach i in=[/interface find where type=ether] do={ :put [/interface get $i name] } One value per line.

Writing

Intent Line Watch out
add, and learn the .id :put [/ip firewall address-list add list=x address=192.0.2.1] Prints the new .id (*1A). Without :put [ … ], add prints nothing.
add at a position /ip firewall filter add chain=forward action=accept place-before=*5 Ordered menus only. One command instead of add + move.
set one row /ip firewall address-list set numbers=*1A comment=checked Prints nothing on success.
set by condition /ip firewall address-list set [find where list=x] timeout=7d A [find] that matches nothing is not an error — it prints nothing, exactly like success. Use numbers=*N when "it was not there" matters: that answers no such item (4). where is required inside [find].
clear a field /ip firewall filter unset numbers=*1A value-name=src-address Not every menu has unset — /ip firewall address-list does not (bad command name unset). Tab after the menu lists its verbs.
enable / disable /ip firewall filter disable numbers=*1A
the comment verb /ip firewall filter comment numbers=*1A comment=x A real menu verb, not a synonym for set comment=.
remove /ip firewall address-list remove numbers=*1A,*1B A list of ids is fine on every write verb.
move /ip firewall filter move numbers=*A,*B destination=*5 Keeps the order the ids are listed in; no destination moves them to the end. A built-in (dynamic) row refuses move, set and remove.

Values in a write

Value Write it as Why
anything outside [A-Za-z0-9._-] comment="two words" Unquoted, a value is parsed by the parameter's type — on a script-typed parameter such as source, :, [, ( and friends are code.
an empty value comment="" A bare comment= mid-line is a syntax error (expected end of command).
$ comment="price \$5" Inside double quotes $name is substituted — unescaped, $5 silently becomes nothing.
\ comment="C:\\temp" \t, \n and others are escapes — "C:\temp" silently stores a TAB.
" comment="say \"hi\""

Escape in the order \ → " → $. There is no single-quote string form in RouterOS.

Actions, monitors and tools

Intent Line Watch out
run a script /system script run [find where name=hello] Prints nothing — run it with ExecuteNonQuery(). run numbers=*B is refused (bad parameter numbers).
export /ip firewall filter export Free text: ExecuteScalar(). export where … narrows it. Dynamic rows are left out — an address-list entry with a timeout is dynamic, so it never appears in an export.
one reading of a monitor :put [/interface monitor-traffic ether1 once as-value] Without once a monitor repaints the screen forever.
ping :put [/ping address=192.0.2.1 count=3 as-value] No once — use count=. Inside :put [ … ] nothing arrives until the last reply; the bare /ping … count=3 streams line by line, as text.
traceroute :put [/tool traceroute address=192.0.2.1 count=1 as-value] once is refused here too.
profile :put [/tool profile duration=1 as-value] profile refuses once.

Commands that ask for confirmation (/system reboot, /system reset-configuration) are not covered here.

A continuous stream — listen, torch — is not something a single raw line can carry; a terminal has no server push. Use ExecuteWithCallback / LoadListenWithCallback in the API dialect, which poll — see Monitors and streaming.

Scripting

Everything the console's scripting language can do is one line away.

Intent Line
count :put [:len [/interface find where running and type=ether]]
loop :foreach i in=[/ip firewall address-list find where list=x] do={ /ip firewall address-list set $i timeout=7d }
arithmetic :put ([/system resource get free-memory] / 1024)
a variable :local n [/system identity get name]; :put "router $n"
a condition :if ([:len [/ip address find where interface=ether2]] = 0) do={ :put "ether2 has no address" }

Keep a script on one line, with ; between statements: each call is one command line. A construct left open — [, ( or { — swallows the next command, which then fails at "line 2".

Ask the router

Menus, verbs and argument names differ between RouterOS versions, and a misspelt field in where is not an error — it can match every row. Two ways to check a name before you rely on it:

  • /console inspect (RouterOS 7): :put [/console inspect request=child path="ip,route,add" as-value] lists the arguments of /ip route add.
  • Tab completion, on every version: cli.CompleteCli("/ip route add ") types the line and a Tab into the terminal and returns what the console offers — see Tab completion.
  • In code, structured: DescribeMenu answers both from tik4net.

See also

Clone this wiki locally