ศึกษาคู่มือการเขียน Script Mikrotik (Terminal)


คู่มือการเขียน Script Mikrotik เรียนรู้ไปพร้อมๆ กันที่ http://wiki.mikrotik.com/wiki/Manual:Scripting
*** ไม่อาจหาญสอนใครๆ ผมบันทึกไว้อ่านเองน่ะครับ ***

ภาษาสคริปต์สร้างขึ้นเพื่อให้ทำงานอัติโนมัติตามชุดคำสั่งที่เราออกแบบเอาไว้ การเขียนสคริปต์มักจะเก็บไว้ในเมนู System > Scriptsซึ่ง Mikrotik wiki เรียกมันว่า Script repository และการเรียกใช้งานสคริปต์ ทำได้ดังนี้
วางคำสั่ง สคริปต์ วางลงใน Terminal(เมนู New Terminal) ได้โดยตรง การใช้งานต้องวางสคริปต์ลงไปใหม่ทุกครั้ง
ทำงานตามเวลาที่ตั้งไว้ใน Schedule (เมนู System > Scheduler) เมื่อถึงเวลาที่ตั้งไว้ ก็จะเรียกสคริปต์ขึ้นมาทำงาน
ใช้เครื่องมือตรวจจับเหตุการณ์ต่างๆ เช่น Traffic Monitor (Tools > Traffic Monitor) และ Netwatch (Tools> Netwatch) เพื่อดักจับเหตุการณ์และสั่งให้สคริปต์ทำงาน

Line structure


สคริปต์ทำงานทีละบรรทัดไปจนจบสคริปต์ หรือจนกว่าจะพบข้อผิดพลาด

[prefix] [path] command [uparam] [param=[value]] .. [param=[value]
[prefix] คือคำขึ้นต้นด้วย “:” หรือ “/” เพื่อใช้แยกความแตกต่างระหว่าง ชุดคำสั่ง และ path ที่อยู่ของไฟล์ อาจจะมีหรือไม่มีก็ได้
[path] relative path ใช้อ้างอิง path ที่อยู่ของชุดคำสั่ง อาจจะมีหรือไม่มีก็ได้
command คือชุดคำสั่งที่ใช้
[uparam] ตัวแปร unnamed บางคำสั่งจำเป็นต้องระบุรายละเอียดตัวแปรเพื่อใช้งานด้วย
[params] ค่าตัวแปร named ซึ่งต้องดูตามคำสั่งนั้นๆ ว่าสามารถระบุตัวแปรอะไรได้บ้าง เรียงกันไปเป็นลำดับ

รูปแบบจบท้ายคำสั่ง(token) ด้วยตัวอักษร “;” หรือขึ้นบรรทัดใหม่ บางครั้งก็ไม่จำเป็นต้องใส่

คำสั่งที่อยู่ภายในวงเล็บ ( ), [ ] หรือ { } ไม่จำเป็นต้องต้องใส่ตัวอักษรจบท้ายบรรทัด


:if ( true ) do={ :put “lala” }



คำสั่งที่อยู่ด้านใน ต้องใส่ไว้ใน “[ ]” ทั้งหมด (คำตอบของคำสั่งด้านในวงเล็บ “[ ]” ส่งค่าออกไปให้คำสั่งด้านนอก)


ตัวอย่าง สคริปต์

:put [/ip route get [find gateway=1.1.1.1]];

ประกอบไปด้วย 3 คำสั่งคือ
:put
/ip route get และ
find gateway

คำสั่ง 1 บรรทัดนี้ เขียนแบบขึ้นบรรทัดใหม่แบ่งออกเป็น 3 บรรทัด ก็ได้ ตามกฎการรวมคำสั่ง

ลองใช้คำสั่งแบบใส่ และ ไม่ใส่ token ปิดท้ายคำสั่ง จะพบว่า กรณีคำสั่ง 1 บรรทัด ไม่ต้องมี token ปิดท้ายคำสั่งก็ได้



Physical Line อักขระบอกการขึ้นบรรทัดใหม่

unix – ASCII LF;
windows – ASCII CR LF;
mac – ASCII CR;

ในภาษา C อักขระในการขึ้นบรรทัดใหม่คือใส่ “\c” ลงใน code

Comments การใส่หมายเหตุ เพื่อใส่คำอธิบายโปรแกรม มีกติกาดังนี้

ใส่ “#” ตำแหน่งแรกของบรรทัด ใส่ก่อนหน้าคำ comment
ห้ามมีเคาะวรรคหน้า “#“
และเครื่องหมาย “#” หากไปโผล่ที่อื่น นอกเหนือจากอยู่ตำแหน่งหน้าบรรทัด ไม่ถือว่าเป็น comment
***ในบรรทัดที่เป็น comment โปรแกรมจะไม่ประมวลผล บรรทัดนั้นๆ

ตัวอย่าง
# this is a comment << เครื่องหมาย "#" อยู่ตำแหน่งแรกของบรรทัด # bad comment << ผิดกฏการ comment เพราะมีเว้นช่องว่าง 1 เคาะเว้นวรรค(white line) หน้าเครื่องหมาย "#" :global a; # bad comment << ผิดกฏการ comment เพราะใส่ #comment ไว้ท้ายประโยคโดยไม่ขึ้นบรรทัดใหม่ :global myStr "lala # this is not a comment" << ไม่ใช่ comment เพราะเครื่องหมาย "#" ไม่อยู่ตำแหน่งเริ่มต้นบรรทัด




Line joining – การเชื่อมต่อบรรทัด


การทำงานของสคริปเป็นลักษณะทำงานรายบรรทัดไปจนจบ กรณีบรรทัดที่เขียนเป็นตรรกกะยาว จนอ่านลำบากในบรรทัดเดียว จึงมีวิธีต่อบรรทัดโดยใช้สัญลักษณ์ “\” เป็นตัวแต่ใช้เชื่อมประโยค comment ยาวๆ ไม่ได้

ตัวอย่าง


:if ($a = true \
and $b=false) do={ :put “$a $b”; } << เป็น pseudo code ทำงานจริงไม่ได้ ใช้เพื่ออธิบายเท่านั้น

:if ($a = true \ # bad comment << ผิดกฏการ comment จะอยู่ตำแหน่งอื่นนอกจากตำแหน่งแรกสุดของบรรทัดไม่ได้
and $b=false) do={ :put “$a $b”; }

# comment \
continued – invalid (syntax error) << ผิดกฎ การเชื่อมต่อบรรทัดใช้กับ comment ไม่ได้



การเว้นวรรค ระหว่างชุดคำสั่ง (Whitespace between tokens)
{ :local a true; :local b false; # whitespace is not required, การใช้ && operator ไม่ต้องเคาะวรรคก็ได้ จะเคาะวรรคก็ไม่มีปัญหา :put (a&&b); # whitespace is required, การใช้ and operator จำเป็นต้องเคาะวรรค เพื่อแยกระหว่างตัวแปรกับ operator :put (a and b); }


ห้ามเว้นวรรค


ระหว่างตัวแปรกับเครื่องหมาย “=” ห้ามเว้นวรรค ‘=‘ เช่น a = 1(ผิด), a=1(ถูก), a= 1(ถูก) และ

ระหว่าง ‘from=‘ ‘to=‘ ‘step=‘ ‘in=‘ ‘do=‘ ‘else=‘ ก็เช่นเดียวกัน

ตัวอย่าง


#แบบที่ผิด เว้นวรรคระหว่าง ‘=‘
:for i from = 1 to = 2 do = { :put $i }

#แบบที่ถูกตามรูปแบบ ‘=‘
: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





Scopes ขอบเขตค่าตัวแปรและการเรียกใช้ค่าตัวแปรต่างๆ มี 2 แบบคือ
Global scope
Global scope หรือ root scope is default scope of the script. It is created automatically and can not be turned off.
Local scope
ตัวแปร หรือค่าต่างๆ ที่อยู่ในวงเล็บ “{ }” ซึ่งจะถูกเรียกใช้ได้บรรทัดถัดไป หลังจากการประกาศตัวแปร หรือการประมวลผลใดๆ และเรียกใช้จากนอกวงเล็บไม่ได้

ตัวอย่าง


{
:local a 3;
{
:local b 4;
:put ($a+$b);
}
#line below will generate error
:put ($a+$b);
}



** :put ($a+$b) ทำงานไม่ได้ ก็เพราะเรียกใช้งานตัวแปร $b นอกวงเล็บของ :local b 4; นั่นเอง

** ข้อควรจำ การพิมพ์โปรแกรมลงไปใน terminal เป็นการทำงานแบบ local scope ดังนั้นการประกาศตัวแปรในบรรทัดแรก จะไม่สามารถเรียกตัวแปรนั้นในในบรรทัดต่อไป

ตัวอย่าง


:local myVar a;
:put $myVar



**คำเตือน ประกาศตัวแปร global ภายใน local scope ถือว่าฟาวล์ ใช้ไม่ได้นะค้าพ

ตัวอย่าง
{ :local a 3; { :global b 4; } :put ($a+$b); }


Keywords


คำสงวนที่ห้ามใช้เด็ดขาดในการเขียนสคริปต์ ซึ่งก็คือคำซ้ำกับ operator นั่นเอง ได้แก่คำว่า and or not in

Delimiters


เครื่องหมายคั่นคำที่ยอมให้ใช้งานได้ () [] {} : ; $ /

Data types – ชนิดของข้อมูลที่ใช้งานได้ประเภทต่างๆ

TypeDescriptionnum(number) – 64bit signed integer – possible hexadecimal input;
bool(boolean) – values can bee true or false;
str (string) – character sequence;
ip – IP address;
ip6-prefix – IPv6 prefix
id (internal ID) – hexadecimal value prefixed by '*' sign. Each menu item has assigned unique number – internal ID;
time – date and time value;
array – sequence of values organized in an array;
nil – default variable type if no value is assigned;



Constant Escape Sequences การพิมพ์ตัวอักษรพิเศษๆ ทำได้โดยการใส่ back slash ไว้ด้านหน้าตัวอักษรนั้น ดังนี้

\"Insert double quote – การเพิ่มอักษร "ฟันหนู"\\ Insert backslash – การเพิ่มอักษร "\"
\n Insert newline – การขึ้นบรรทัดใหม่
\r Insert carriage return – การเพิ่มรหัสให้ cursor ไปต้นบรรทัด
\t Insert horizontal tab – การเพิ่ม tab เว้นวรรค
\$ Output $ character. Otherwise $ is used to link variable. – การแสดงผลตัวอักษร "$" (dollar sign) ถ้าไม่มี "\" อยู่ด้านหน้าจะหมายถึงค่าตัวแปรในสคริปต์
\? Output ? character. Otherwise ? is used to print "help" in console. – การแสดงผลตัวอักษร "?" ถ้าไม่มี "\" จะหมายถึงการเปิด help ใน console terminal
\_ – space การเว้นวรรค
\a – BEL (0x07)
\b – backspace (0x08)
\f – form feed (0xFF)
\v Insert vertical tab
\xx แสดงผลเลขฐาน 16 โดยตัวเลขฐาน 16 ต้องระบุเป็นตัวพิมพ์ใหญ่เท่านั้น



ตัวอย่าง


:put “\48\45\4C\4C\4F\r\nThis\r\nis\r\na\r\ntest”;





Operators – การดำเนินการ

Arithmetic Operators – การดำเนินการทางคณิตศาสตร์

OpearatorDescriptionExample+" binary addition :put (3+4);
–" binary subtraction :put (1-6);
*" binary multiplication :put (4*5);
/" binary division :put (10 / 2); :put ((10)/2)
–" unary negation { :local a 1; :put (-a); }





** การหารค่า ควรเว้นวรรคระหว่างคำด้วย เช่น 10 / 2 เพื่อป้องกันการสับสนกับค่า ip address (10.0.0.1/24)

Relational Operators

OpearatorDescriptionExample<" less :put (3<4 br="">>" greater :put (3>4);
=" equal :put (2=2);
<=" less or equal
>=" greater or equal
!=" not equal





Logical Operators

OpearatorDescriptionExample“!” หรือ “not” logical NOT :put (!true);
“&&” หรือ “and” logical AND :put (true&&true)
“||” “or” logical OR :put (true||false);
“in” ค่าที่มีใน … :put (1.1.1.1/32 in 1.0.0.0/8);







Bitwise Operators การกลับบิทของเลขฐาน 2


Bitwise operators ใช้งานได้กับตัวเลข และ IP Address

“~” bit inversion – การกลับ bit (เลขฐาน 2) ตัวอย่าง :put (~0.0.0.0)


“|” bitwise OR. Performs logical OR operation on each pair of corresponding bits. In each pair the result is “1” if one of bits or both bits are “1”, otherwise the result is “0”. – การทำงานแบบ OR , บิทที่เปรียบเทียบกันหากมีค่าเป็น 1 ผลลัพธ์ของบิทนั้นจะมีค่าเป็น 1 ทันที เช่น 1 OR 1 = 1, 0 OR 0 = 0, 1 OR 0 = 1, 0 OR 1 =1



“^” bitwise XOR. The same as OR, but the result in each position is “1” if two bits are not equal, and “0” if bits are equal. – เทียบหลักของบิทตัวเลข หลักที่เหมือนกัน จะได้ผลลัพธ์เป็นศูนย์ หลักที่เลขต่างกันได้ผลลัพธ์เป็นหนึ่ง พูดย่อๆ เหมือนสมัยเรียนได้ว่าเหมือนศูนย์ต่างหนึ่ง



“&” bitwise AND. In each pair the result is “1” if first and second bit is “1”. Otherwise the result is “0”. – การทำงานแบบ AND จะมีค่าผลลัพธ์เป็น 1 เมื่อ บิทที่เปรียบเทียบทั้งคู่มีค่า 1 เหมือนกันเท่านั้น เช่น 1 AND 1 = 1, 1 AND 0 = 0, 0 AND 1 = 0, 0 AND 0 = 0



“<<” left shift by given amount of bits
“>>” right shift by given amount of bits

Concatenation Operators – ใช้เชื่อมข้อความ การนำผลลัพธ์มาเรียงต่อกัน


“.” นำสองข้อความมาเชื่อมกัน ตัวอย่าง :put (“concatenate” . “ “ . “string”);
“,” นำ Array มาเชื่อมต่อกัน หรือเพิ่มสมาชิกเข้าไปใน Array ตัวอย่าง :put ({1;2;3} , 5 );



การนำข้อความมาต่อกันอีกวิธี เหมือนการเขียนโปรแกรมทั่วๆ ไป

:global myVar “world”;
:put (“Hello ” . $myVar);

# next line does the same as above
:put “Hello $myVar”;





เขียนการแสดงผลลัพธ์ค่าตัวแปรด้วยการใช้สัญญลักษณ์ “$[ ]” และ “$( )”:local a 5; :local b 6; :put " 5x6 = $($a * $b)"; :put " We have $[ :len [/ip route find] ] routes";




Operator อื่นๆ

OpearatorDescriptionExample1. “[ ]” command substitution. Can contain only single command line :put [ :len "my test string"; ];
2. “( )” sub expression or grouping operator :put ( "value is " . (4+5));
3. “$” substitution operator :global a 5; :put $a;
4. “~” binary operator that matches value against POSIX extended regular expression Print all routes which gateway ends with 202 – /ip route print where gateway~"^[0-9 \\.]*202"
5. “->” Get an array element by key :global aaa {a=1;b=2}; :put ($aaa->"a"); :put ($aaa->"b") << ทดสอบแล้วไม่มีผลลัพธ์





Variables ตัวแปรมี 2 ชนิดคือ
ตัวแปร global เข้าถึงได้จากทุกสคริปต์ ใช้คำว่า :global นำหน้าการประกาศตัวแปร
ตัวแปร local เข้าถึงตัวแปรได้เฉพาะใน scope (ในวงเล็บ) ใช้คำว่า :local ในการประกาศตัวแปร


คำเตือน : Starting from v6.2 there can be undefined variables. When variable is undefined parser will try to look for variables set, for example, by DHCP lease-script or Hotspot on-login

ต้องประกาศตัวแปรก่อนการใช้งานเสมอ เพื่อป้องกันผลลัพธ์ผิดพลาด compilation error ตัวอย่างที่ผิด เช่น


# following code will result in compilation error, because myVar is used without declaration
:set myVar “my value”;
:put $myVar



ซึ่งถ้าเราเข้าใจเนื้อหาด้านบน การพิมพ์เข้า console เองโดยตรงนั้น ตัวแปรจะเป็น local scope เฉพาะในบรรทัดนั้นๆ ดังนั้นจึงเขียนใหม่ไว้ในบรรทัดเดียวกันจึงจะเป็นวิธีที่ถูก

จากนั้น ลองประกาศตัวแปรไว้ก่อนใช้งาน จึงเป็นวิธีที่ถูกต้อง และได้ผลลัพธ์ออกมา




หากใช้กับตัวแปรของระบบ ไม่จำเป็นต้องประกาศตัวแปร ตัวอย่าง


/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”

ตัวแปรเช่น $leaseActIP และตัวแปรอื่นๆ ไม่ได้ประกาศมาก่อน แต่ก็สามารถสร้างลงใน scripts repo. ได้


/ip dhcp-server set myServer lease-script=myLeaseScript

ตัวแปรควรจะมีเพียงตัวอักษร และตัวเลข หากมีตัวอักขระพิเศษ เช่น “–“, “_” ก็ให้ใช้ฟันหนูครอบตัวแปรไว้ เช่น


#valid variable name
:local myVar;

#invalid variable name
:local my-var;

#valid because double quoted
:global “my-var”;

การประกาศตัวแปรโดยไม่ใส่ค่าเข้าไปโดยมากข้อมูลจะถูกตั้งค่าเป็น ‘nil‘ ก็คือค่าศูนย์อัติโนมัติ หรือเป็นค่าเริ่มต้นตามชนิดของข้อมูล ซึ่งscript engine เป็นผู้ตัดสินใจเอง เลื่อนขึ้นไปอ่าน Data type/ชนิดข้อมูล ด้านบนประกอบ หรือบางครั้งเราก็มีความจำเป็นต้องแปลงรูปแบบก่อนก่อนใช้งาน ตัวอย่างเช่น


#convert string to array
:local myStr “1,2,3,4,5”;
:put [:typeof $myStr];
:local myArr [:toarray $myStr];
:put [:typeof $myArr]



ตัวแปร ใช้หลักการ “คำตรงตัว” ตัว a กับ A ไม่เท่ากัน


: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;

ประกาศตัวแปร Global MyVar ให้มีค่า “myValue” ดูค่าตัวแปร Global ได้ใน Scripts Environment



ใช้คำสั่ง set เพื่อลบค่าตัวแปร ดูในช่อง Environment อีกทีเพื่อตรวจสอบ