Lecture09 - สัปดาห์แรกหลังสอบกลางภาค ---------------------------------- "Take a bit of time to get the hang of it now and it’ll pay off later." "ยอมเสียเวลาวันนี้สักเล็กน้อยเพื่อเรียนรู้และทำความเข้าใจ แล้ววันหนึ่งข้างหน้าจะได้รับผลตอบแทนที่คุ้มค่า" ก่อนจะได้กล่าวถึงการเขียนโปรแกรม shell script ยังมีเรื่องเก็บเล็กผสมน้อยของเชลล์ที่ยังอาจกล่าวถึงไม่ครบถ้วน และมีบางส่วนเพิ่มเติม ซึ่งมีความจำเป็นต่อการเขียนโปรแกรม บางเรื่องอาจเคยกล่าวถึงมาแล้วบ้าง ถือเป็นการทบทวนไปในตัว ดังต่อไปนี้ คำสั่งภายในของเชลล์ (internal command) ------------------------------------ คำสั่งภายในของเชลล์ เป็นคำสั่งเฉพาะของเชลล์แบบใดแบบหนึ่งโดยเฉพาะ คำสั่งเช่นนี้อาจจะมีในเชลล์อื่นหรือไม่ก็ได้ เมื่อเปลี่ยนเชลล์ ใหม่ต้องตรวจสอบว่ามีคำสั่งให้เช่นนี้ให้ใช้งานหรือไม่ หากมีรูปแบบเหมือนกันหรือต่างกันอย่างไร และหากไม่มี มีคำสั่งใดที่มีการทำงานลักษณะเดียวกันหรือไม่ ตัวอย่าง คำสั่ง umask เป็นคำสั่งกำหนดตัวพรางสำหรับสร้างแฟ้ม (file creation mask) ของ bash ซึ่งเคยใช้งานแล้วในแฟ้ม .bash_profile เนื่องจากเป็นคำสั่งของ bash จึงไม่มีรายละเอียดใน online manual page หากใช้คำสั่ง $ man umask จะได้รายละเอียดของบริการ umask() ของระบบปฏิบัติการ ซึ่งอยู่ในหมวดที่ 2 ของ online manual page เป็นบริการที่ใช้สำหรับการเขียนโปรแกรมภาษา C หากต้องการอ่านรายละเอียด umask ของ bash ต้องอ่านจากคำสั่ง bash $ man bash bash เป็นโปรแกรมขนาดใหญ่และมีรายละเอียดมาก จึงต้องใช้การค้นคำที่ต้องการ โดยกดแป้น / เพื่อเข้าสู่ last line mode และพิมพ์คำว่า umask แล้วกด จะปรากฏแถบแสงทับคำว่า umask ซึ่งหากยังไม่ใช่คำที่ต้องการ ให้เลื่อนไปยังคำต่อไปด้วยแป้น N จนกว่าจะพบจุดที่ต้องการ วิธีการนี้มีข้อเสียคืออาจต้องเลื่อนตำแหน่งหลายครั้งกว่าจะพบจุดที่ต้องการอ่าน แต่มีข้อดีที่อาจสามารถใช้ได้กับคำที่สนใจทุกคำ ในกรณีที่ต้องการรายละเอียดของคำสั่งภายใน bash มีคำสั่ง help ไว้ให้แล้ว $ help แสดงคำสั่งภายในทั้งหมดของ bash และรูปแบบการใช้งานอย่างย่อ หากต้องการดูรายละเอียดของคำสั่งใด สามารถใช้คำสั่งนั้นเป็น keyword ของ help ได้ เช่นหากต้องการรายละเอียดของคำสั่ง umask ใช้คำสั่งดังนี้ $ help umask นอกจากจะใช้งานกับคำสั่งโดยตรงแล้ว ผู้ใช้ยังสามารถกำหนด pattern สำหรับการค้นหาได้ด้วยเช่น help u* เป็นการแสดงวิธีการ ใช้งานของคำสั่งภายในทั้งหมดที่ขึ้นต้นด้วยอักษร u นอกจากจะสามารถใช้งานกับคำสั่งภายในโดยทั่วไปของ bash แล้ว คำสั่ง help ยังสามารถใช้แสดงรายละเอียดและวิธีใช้งานของของคำสั่งที่ใช้กำหนดโครงสร้างควบคุมการทำงานเช่น case, for, while และ until ได้ด้วย ซึ่งช่วยอำนวยความสะดวกสำหรับผู้เริ่มต้นเขียน shell script ได้มาก นอกจากคำสั่งภายในของ shell ซึ่งเป็น "คำ" ภาษาอังกฤษแล้ว bash ยังคำสั่งอีกส่วนหนึ่งเป็นอักขระพิเศษ (meta-character) ได้แก่ สัญลักษณ์ ความหมาย ----------- ------------ ( ) เชลล์ย่อย (subshell) $( ) การนำผลลัพธ์มาใช้แทนคำสั่ง (( )) การปะมวลผลนิพจน์คณิตศาสตร์ เป็นรูปแบบย่อของคำสั่ง let $(( )) การขยายนิพจน์คณิตศาสตร์ (arithmetic expansion) [ ] รูปย่อของคำสั่ง test [[ ]] นิพจน์เงื่อนไขสำหรับ string การใช้งานอักขระพิเศษเหล่านี้จะได้กล่าวถึงเป็นลำดับไป สำหรับตอนนี้ขอให้จำสัญลักษณ์เหล่านี้ไว้ก่อน เพื่อจะได้ไม่นำไปใช้โดยรู้เท่าไม่ถึงการณ์และทำให้เกิดความผิดพลาดขึ้น การแยกคำสั่งด้วย semicolon และ newline ---------------------------------- ; และ ใช้แยกคำสั่ง หากกำหนดให้ x, y และ z เป็นคำสั่ง 3 คำสั่ง การสั่งงานสองแบบต่อไปนี้สมนัยกัน $ x $ y -- สมนัยกับ -- $ x ; y ; z $ z \ - continues a command ---------------------- ในกรณีที่คำสั่งยาวเกินกว่า 1 บรรทัด สามารถใช้ \ เพื่อเชื่อมต่อคำสั่งไปยังบรรทัดต่อไปได้ เช่น $ echo "Hello \ ... (1) > world" ... (2) Hello world ท้ายบรรทัดที่ (1) พิมพ์เครื่องหมาย \ ก่อนกด หรือเป็น \ เป็นการเปลี่ยนสถานะของ ให้เป็นอักขระธรรมดา เนื่องจากยังไม่จบบรรทัดคำสั่ง เชลล์จะแสดง secondary prompt ซึ่งกำหนดด้วยตัวแปร PS2 เพื่อให้ผู้ใช้ป้อนคำสั่งต่อไป $ set | egrep "PS[[:digit:]]" PS1='$ ' # primary prompt PS2='> ' # secondary prompt PS4='+ ' set เป็นคำสั่งภายในของเชลล์ ใช้สำหรับกำหนดค่าของตัวแปรระบบ ในกรณีที่ไม่มี argument ใช้แสดงค่าของตัวแปรระบบทั้งหมด & การทำงานในพื้นหลัง (background) ----------------------------- & เป็นสัญลักษณ์สำหรับกำหนดให้มีการทำงานแบบ background กำหนดให้ a, b และ c เป็นคำสั่ง 3 คำสั่ง $ a& b& c # a และ b ทำงานแบบ background, c ทำงานแบบ foreground [1] 12345 # เชลล์ตอบสนองด้วย หมายเลขงาน (job number) [2] 12346 # และหมายเลขโพรเซส (PID) ที่ทำงานแบบ background เมื่อเชลล์ทำงานตามคำสั่ง c เสร็จจะแสดง prompt ทันที โดยไม่สนใจว่า a และ b จะทำงานเสร็จแล้วหรือไม่ ในกรณีที่มีงานแบบ background ทำงานอยู่ ทุกครั้งที่เชลล์จะแสดง prompt จะตรวจสอบสถานะการทำงานของ background process เสมอ หากมีงานใดที่เสร็จเชลล์จะรายงานสถานะเช่น [1]- Done a [2]+ Done b เครื่องหมาย + แสดงว่าเป็นงานสุดท้ายที่ทำ ส่วนเครื่องหมาย - เป็นงานที่ทำก่อน สมมุติว่ามีการใช้งานคำสั่ง a, b และ c เป็น pipeline และสมุติว่า c เป็นโพรเซสที่ใช้เวลาในการทำงานมากที่สุด ผู้ใช้สามารถกำหนดให้ c ทำงานแบบ backgroud ได้ เช่น $ a | b | c& [1] 23456 เมื่อ a และ b ทำงานเสร็จเชลล์จะแสดง prompt เพื่อรับคำสั่งต่อไป โดยปล่อยให้ c ทำงานในแบบ background ต่อไป การจัดกลุ่มคำสั่งด้วย () ----------------- กำหนดให้ a, b, c และ d เป็นคำสั่ง 4 คำสั่ง $ ( a ; b )& c& [1] 23467 [2] 23468 คำสั่งนี้กำหนดให้ a และ b ทำงานต่อเนื่องกัน ทำ a ก่อนเสร็จแล้วจึงทำ b แต่ทั้ง a และ b ซึ่งอยู่ในกลุ่มเดียวกันทำงานในแบบ background ( a ; b )& เป็นงานลำดับที่ [1] -- เครื่องหมายวงเล็บทำให้เกิด subshell เมื่อกำหนดให้ a และ b ทำง่านแบบ background แล้ว จึงกำหนดให้ c ทำงานแบบ background ด้วยเป็นงานลำดับที่ [2] ซึ่งต่างจากคำสั่ง $ a& b& c& ซึ่งจะกำหนดให้ a, b, และ c ทำงานแบบ background ดังนั้นดพรเซสทั้งสามจึงทำงานขนานกันไป (concurrent processes) สำหรับตัวอย่างการใช้ () จัดกลุ่มคำสั่งที่ชัดเจน จะเห็นได้ใน script สำหรับคัดลอกแฟ้มระหว่างไดเรกทอรี ตอนท้ายบทนี้ การควบคุมการทำงานของโพรเซสในระดับ shell ------------------------------------- ผู้ใช้สามารถสร้าง ควบคุมการทำงาน และทำลายโพรเซสได้ในระดับ shell เพื่อให้มีความแตกต่างออกไปจากการดำเนินการของระบบปฏิบัติการและ จึงกำหนดให้เรียกโพรเซสในระดับ shell ว่า job และมีคำสั่งของ shell สำหรับดำเนินการกับ job เหล่านั้น การเรียนรู้ job และคำสั่งควบคุม job ค่อนข้างเข้าใจยาก วิธีการที่ดีแบบหนึ่งคือทำความเข้าใจจากตัวอย่าง เรียกใช้โปรแกรม vi เพื่อพิมพ์แฟ้ม hello.c $ vi hello.c พิมพ์บรรทัดแรก #include กด เพื่อกลับสู่ command mode ของ vi และกด Ctrl-Z (^Z) ระบบจะแสดงข้อความต่อไปนี้ที่บรรทัดล่างสุด [1]+ Stopped vi hello.c Ctrl-Z โดยทั่วไปกำหนดไว้เป็น suspended key ซึ่งทำให้โพรเซสปัจจุบันเข้าสู่สถานะ suspended หรือ stopped ขณะนี้ vi เข้าสู่สถานะ suspended หรือ stopped แล้ว เพื่อไม่ให้เกิดความสับสนระหว่างการควบคุมโพรเซสด้วยโปรแกรมอรรถประโยชน์ เช่น ps และ kill, กับการควบคุมโพรเซสในระดับของเชลล์ จึงนิยมเรียกโพรเซสในระดับเชลล์ว่า "'งาน" หรือ job โดบมีคำสั่งควบคุม "งาน" ในระดับของเชลล์ดังนี้ คำสั่ง jobs --------- jobs - แสดง job ทั้งหมดของผู้ใช้ที่มีอยู่ในขณะนั้น เช่น $ jobs [1]+ Stopped vi hello.c ขณะนี้มีงานอยู่เพียงงานเดียวคือ vi hello.c และอยู่ในสถานะ suspended หรือ stopped โดย shell กำหนดให้เป็นงานหมายเลข 1 ซึ่งหมายเลขนี้เป็นหมายเลขอ้างอิงในคำสั่งควบคุม job อื่นๆ คำสั่ง fg ------ fg - นำ job ที่หยุดการทำงานอยู่หรือทำงานอยู่ใน background มาเป็นงานแบบ foreground $ fg <[%]หมายเลข job> # เครื่องหมาย % จะใช้หรือไม่ก็ได้ เช่น $ fg %1 นำงานหมายเลข 1 ซึ่งได้แก่ vi hello ซึ่งหยุดการทำงานอยู่ ให้กลับมาเป็นงานแบบ foreground ซึ่งผุ้ใช้จะได้หน้าจอของ vi เดิมที่ค้างอยู่และพร้อมที่จะทำงานต่อไป โดยทั่วไปเมื่อผู้ใช้ต้องการเปลี่ยนงานแบบ foreground เป็นงานแบบ background ต้องหยุดการทำงานของงาน foreground ก่อนโดยการกด Ctrl-Z เมื่องานนั้นเข้าสู่สถานะ suspended หรือ stoped แล้วจึงใช้คำสั่ง bg เปลี่ยนงานนั้นเป็น background คำสั่ง bg ------- bg - เปลี่ยนงานที่อยู่ในสถานะ suspened เป็นงานแบบ background $ fg <[%]หมายเลข job> # เครื่องหมาย % จะใช้หรือไม่ก็ได้ เช่นในขณะที่ vi hello.c อยู่ในสถานะ suspended หากใช้คำสั่ง $ bg %1 จะได้ผลลัพธ์เป็น [1]+ vi hello.c & เครื่องหมาย & ที่อยู่ท้ายสุดของบรรทัด แสดงว่างานหมายเลข 1 กำลังทำงานแบบ background ซึ่งในกรณีของ vi ไม่มีความแตกต่างจากสถานะ suspened เนื่องจากเป็นโปรแกรมแบบ interactive ต้องมีการใช้งาน terminal จึงจะสามารถทำงานได้ งานที่ควรกำหนดให้ทำงานแบบ background ควรเป็นงานที่ไม่ต้องอาศัย terminal ในการทำงาน เช่นการ download ข้อมูลจากเครื่องอื่นในเครือข่าย คำสั่ง kill ------- kill - ส่งสัญญาณไปยังงานในสถานะ suspended หรืองานแบบ background เช่น kill -n 9 %1 -n 9 : กำหนดให้ส่งสัญญาณหมายเลข 9 (SIGKILL) ไปยังงานหมายเลข 1 ต้องนำหน้าด้วยเครื่องหมาย % เสมอ หมายเหตุ ------ 1. คำสั่ง kill ของ shell เป็นคำสั่งที่ไม่ค่อยได้รับความนิยมใช้งาน เพราะสามารถใช้คำสั่ง kill ที่เป็น Utilities ได้อยู่แล้ว 2. หากเรียกใช้ vi แก้ไขแฟ้มที่มีอยู่แล้ว เช่น hello.c, vi จะเปลี่ยนชื่อแฟ้มเดิมเป็น .hello.c.swp (เรียกว่า swap file) เมื่อ ผู้ใช้สั่งบันทึกแฟ้ม vi จะนำข้อมูลในหน่วยความจำที่มีการแก้ไขบันทึกลงในชื่อ hello.c และลบแฟ้ม .hello.c.swp หากมีการใช้คำสั่ง kill ไม่ว่าจะเป็นคำสั่งของ shell หรือ utility โพรเซสของ vi จะถูกทำลายไป แต่ยังคงมีแฟ้ม .hello.c.swp เหลืออยู่ เมื่อผู้ใช้คำสั่ง vi hello.c อีกครั้งหนึ่ง vi จะเตือนว่ามี swap file อยู่แล้วดังนี้ Swap file ".hello.c.swp" already exists! [O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort: ซึ่งผู้ใช้สามารถเลือกดำเนินการได้หลายอย่าง เช่นกู้คืน (Recover), ลบทิ้ง (Delete) หรือเลิก (Quit) โดยการป้อนอักษรตัวแรก ในกรณีของการ quit และไม่ต้องการแฟ้มนั้นแล้ว ผู้ใช้ต้องลบ swap file ด้วยตนเอง ความสัมพันธ์ของงานแบบ foreground, suspended และ background ------------------------------------------------------------- fg <หมายเลข job> <---------------------------------------------------------------< | | | Ctrl-Z | ----> foreground ---------------------> suspended ---------------------> background <--------------------- bg <หมายเลข job> fg <หมายเลข job> การควบคุมงานลักษณะนี้ ควรฝึกใช้งานให้คล่อง โดยเฉพาะเมื่อใช้ vi เป็น editor เนื่องจากการกดแป้น Shift-ZZ สำหรับ save และ exit ของ vi และการกด Ctrl-Z เพื่อนำโพรเซสที่กำลังทำงานในปัจจุบันเข้าสู่สถานะ suspended ใกล้เคียงกันมาก อาจเกิดความผิดพลาดได้บ่อย จะได้ตรวจสอบและแก้ไขได้ การจัดการกับไดเรกทอรี - การกลับไปยังไดเรกทอรีที่เคยใช้งานล่าสุด --------------------------------------------------- คำสั่ง cd ตามด้วยเครื่องหมายขีด (cd -) ใช้สำหรับสลับไดเรกทอรี ระหว่างไดเรกทอรีปัจจุบัน และไดเรกทอรีที่เคยใช้งานล่าสุด เมื่อสลับแล้ว จะแสดงชื่อไดเรกทอรีปัจจุบันให้ด้วย ดังตัวอย่างต่อไปนี้ $ pwd /home/staff/jira/public_html/886326/Lectures $ cd /usr/sbin $ pwd /usr/sbin $ cd - /home/staff/jira/public_html/886326/Lectures $ cd - /usr/sbin คำสั่ง cd ที่มีการใช้งานบ่อยๆ --------------------- $ cd # กลับ home directory $ cd .. # เปลี่ยนไปยัง parent directory - ขึ้นไปหนึ่งระดับ $ cd ../.. # เปลี่ยนไดเรกทอรีขึ้นไปสองระดับ Q&A --- Q: คำสั่ง 'cd .' เป็นคำสั่งที่ไม่มึผลลัพธ์ใดและไม่เกิด error เพราะเหตุใดจึงเป็นเช่นนั้น? A: เพราะเป็นคำสั่งเปลี่ยนไดเรกทอรีที่กำลังใช้งานอยู่ในปัจจุบันเป็นไดเรกทอรีเดิม (เปลี่ยนกลับไปอยู่ที่เดิม) จึงไม่มีผลใด ที่ไม่เกิด error เพราะเป็นคำสั่งที่ถูกต้องตามรูปแบบการใช้งานของคำสั่ง cd Q: เมื่อผู้ใช้คำสั่ง 'man cd' เพื่อดูรายละเอียดของคำสั่ง cd จึงปรากฏผลดังนี้ $ man cd No manual entry for cd # ไม่มีข้อมูลสำหรับคำสั่ง cd A: เพราะ cd เป็นคำสั่งภายในของ bash ดังนั้นหากต้องการดูรายละเอียดต้องใช้คำสั่ง 'help cd' การเขียนโปรแกรม shell script --------------------------- The Bourne Again Shell - bash ------------------------------ Bourne Shell เป็นเชลล์ที่ได้รับการพัฒนาขึ้นโดย Steve Bourne แห่งห้องปฏิบัติการวิจัยเบลล์ (AT&T's Bell Lab) ในปี 1977 ส่วน Bourne Again Shell (bash) พัฒนาขึ้นโดย Brian Fox เป็นซอฟต์แวร์ให้เปล่าสำหรับใช้ทดแทน Bourne shell ในโครงการ GNU Project ในปี 1989 และได้รับการกำหนดเป็นเชลล์โดยปริยายของระบบปฏิบัติการ Linux และระบบปฏิบัติการ macOS ของบริษัท Apple นอกจาก Bourne shell (sh) แล้ว ยังมีเชลล์อื่นที่ได้รับความนิยมอีกหลายแบบ เช่น C-shell (csh) ซึ่งพัฒนาขึ้นช่วงปลาย 1970s โดย Bill Joy ในขณะที่ยังเป็นนักศีกษาปริญญาโทที่มหาวิทยาลัยแคลิฟอร์เนีย เบิร์กลีย์ เพื่อใช้เป็นเชลล์ของระบบปฏิบัติการ BSD Unix (Berkeley Software Distribution) โดยจัดให้มีโครงสร้างควบคุมการทำงานตล้ายภาษา C และ Korn shell (ksh) ซึ่งพัฒนาขึ้นในช่วงต้นทศศวรรษ 1980s โดย David Korn แห่งห้องปฏิบัติการวิจัยเบลล์ โดยใช้โปรแกรมต้นฉบับของ Bourne shell เป็นต้นแบบ เพิ่มเติม ปรับปรุง และผสมผสานคุณลักษณะของ C-shell ตามความต้องการของผู้ใช้ในห้องปฏิบัติการวิจัยเบลล์ หน้าที่ของเชลล์ ------------ 1. ทำหน้าที่เป็นตัวแปลคำสั่ง (Command interpreter) โดยแปลบรรทัดคำสั่งที่ได้รับจากแป้นพิมพ์ทีคำสั่ง ซึ่ง "คำสั้ง" อาจเป็น - คำสั่งภายในของเชลล์ (internal command), หรือ - โปรแกรมอรรถประโยชน์ (utilities) ของระบบปฏิบัติการ 2. ทำหน้าที่เป็นภาษาระดับสูงสำหรับเขียนโปรแกรม (High-level programming language) ทำหน้าที่อ่านและประมวลผลคำสั่งจากแฟ้มคำสั่ง เรียกว่า shell script ซึ่งเชลล์จะทำหน้าที่นี้ได้ต้องมี - ตัวแปร สำหรับเก็บข้อมูล - คำสั่งและโปรแกรมอรรถประโยชน์สำหรับประมวลผลข้อมูล - โครงสร้างควบคุมการทำงาน (control flow command) การเขียนโปรแกรมด้วย shell script จุดมุ่งหมายของการเขียนโปรแกรม ➤ เขียนโปรแกรมเพื่อแก้ปัญหา โดยการประมวลผลข้อมูล ➤ ต้องเข้าใจปัญหา - ต้อง "วิเคราะห์งาน" ต้องการ output เป็นอะไร -- ต้องป้อน input อะไร จึงจะได้ผลลัพธ์เช่นนั้น -- ทำอย่างไรจึงจะเปลี่ยน input เป็น output ได้ วิเคราะห์ output, วิเคราะห์ input, วิเคราะห์การประมวลผล (process) ➤ เขียนผังงานหรือคำสั่งลำลองจากงานที่วิเคราะห์ได้ • ผังงาน (flow chart) นิยมใช้แสดงภาพโดยรวมของระบบ ⋄ ใช้แผนภาพ เห็นความต่อเนื่อง ดูง่าย เข้าใจง่าย ไม่ค่อยมีรายละเอียด • คำสั่งลำลอง (pseudocode) แสดงรายละเอียดและขั้นตอนของการทำงาน ⋄ คล้ายภาษาธรรมชาติที่ใช้ในชีวิตประจำวัน ใช้ keyword และการย่อหน้า แสดงโครงสร้างการทำงาน ⋄ โครงสร้างการทำงานคล้ายกับภาษาสำหรับเขียนโปรแกรม เปลี่ยนคำสั่งลำลองเป็นโปรแกรมได้ง่าย การแก้ปัญหา คือ การประมวลผลข้อมูล ต้องมีที่เก็บข้อมูล - ตัวแปรชนิดต่างๆ ต้องมีการประมวลผล - ตัวแปรมีชนิด หรือ ตัวแปรเป็นสมาชิกของเซต เซตมี operator ประจำ เช่น เซตของจำนวนเต็ม มี บวก, ลบ, คูณ, div, mod, ต้องมีโครงสร้างควบคุมการทำงาน - เรียงลำดับ, มีการเลือก, ทำซ้ำ, โปรแกรมย่อย, ... ต้องมีขั้นตอนวิธีในการทำงาน - algorithm การเขียน Shell Script -- การประมวลผลส่วนใหญ่เป็นการเรียกใช้ Utilities (โปรแกรมอรรถประโยชน์) ของระบบปฏิบัติการ ดังนั้นผู้ที่จะเขียน script ได้ดี นอกจากจะต้องมีความรู้ในเรื่องอักขระพิเศษ ตัวแปร และโครงสร้างควบคุมการทำงานแล้ว ยังต้องศึกษาและทำความคุ้นเคยกับ Utilities ทั้งรูปแบบ และ option ต่างๆ เพื่อให้ได้ผลลัพธ์ตามรูปแบบที่ต้องการ โดยเฉพาะเมื่อต้องนำคำสั่งมาต่อกันเป็น pipeline หรือต้องเปลี่ยนทิศทางข้อมูล (redirection) การเขียน shell script อย่างง่าย ---------------------------- • คำสั่งใดๆที่สามารถป้อนได้ที่ prompt สามารถใช้ใน shell script ได้ รวมทั้ง ambiguous file reference (การใช้ *, ? และ character class ในชื่อแฟ้ม), การเปลี่ยนทิศทาง input/output และการทำนำคำสั่งมา ต่อกันเป็น pipeline • สามารถใช้คำสั่งควบคุมการทำงาน (Control flow) สร้างทางเลือก การทำซ้ำ ฯลฯ • เชลล์จะอ่านคำสั่งจากแฟ้ม ขยายความหมายของอักขระพิเศษ และทำงานไปทีละคำสั่ง ตามลำดับทีอ่านได้ • shell script (ซึ่งเป็นแฟ้มคำสั่งของเชลล์) ผู้ใช้ต้องมีสิทธิ read และ execute read - เพื่อให้สามารถเปิดแฟ้ม และอ่านข้อมูลในแฟ้มได้ execute - เพื่อให้สามารถทำงานตามคำสั่งที่อ่านจากแฟ้มได้ คำสั่งอย่างง่ายสำหรับ "เพิ่ม" สิทธิในการ execute (x) ให้แก่เจ้าของแฟ้ม หรือ user (u) $ chmod u+x ชื่อแฟ้ม shell script • ในกรณีที่ต้องการให้ bash ทำหน้าเป็นตัวแปลภาษาระดับสูงทำได้ 2 แบบ ⋄ $ bash ชื่อแฟ้ม shell script ⋄ ขึ้นต้นแฟ้ม shell script (บรรทัดแรกและคอลัมน์แรก) ด้วย #!/bin/bash ⋄ เครื่องหมาย #! มีชื่อเรียกเฉพาะว่า shebang ตามด้วย absolute pathname ของเชลล์ที่ต้องการ ⋄ เครื่องหมาย #! (# - hash, ! - bang) จะมีผลในการเลือกเชลล์เฉพาะเมื่อเรื่มต้นที่ row = 0, col =0 หากอยู่ที่ตำแน่งอื่น # แทนจุดเริ่มต้นของ comment และสำหรับ comment "Comments make shell scripts and all codes easier to read and maintain by 'you' and 'other'." "หมายเหตุช่วยให้ shell scripts และโปรแกรมภาษาอื่นๆ อ่านและปรับปรุงแก้ไขได้ง่าย ทั้งผู้เขียนเองและผู้อื่น" ตัวแปรของ shell -------------- shell script สามารถใช้งานตัวแปรได้เช่นเดียวกับภาษาสำหรับเขียนโปรแกรมโดยทั่วไป ตัวแปรของ shell ไม่มีการกำหนดชนิด ใช้เก็บ จำนวน, อักขระ, หรือ สายอักขระ การสร้างตัวแปรจะเกิดขึ้นโดยอัตโนมัติเมื่อมีการกำหนดค่าให้กับชื่อตามรูปแบบ name=value # หรือ ชื่อตัวแปร=ค่า เป็นการสร้างและกำหนดค่า โดยไม่จำเป็นต้องมีการประกาศตัวแปรก่อน เช่น ในตัวแปลภาษาในกลุ่ม Compiler ทั้งนี้เพราะ shell เป็นตัวแปลภาษาชนิด Interpreter ตัวอย่างที่่ 1 : "Hello World!" โดยใช้ตัวแปร -------------------------------------- $ cat -n hello.sh 1 #!/bin/bash 2 STR="Hello World!" 3 echo $STR ::: อธิบายการทำงานของโปรแกรม ::: ✦ บรรทัดที่ 1: #!/bin/bash กำหนดให้ shell ที่ทำหน้าที่เป็นตัวแปลภาษาเป็น Bourne Again Shell หรือ bash ซึ่งเป็นแฟ้มโปรแกรมใน /bin และบรรทัดนี้ทำให้ระบบมองเห็นแฟ้มนี้้เป็น Shell Script เมื่อตรวจสอบด้วยคำสั่ง file ซึ่งใช้สำหรับตรวจสอบ ชนิดของแฟ้ม เช่น $ file hello.sh hello.sh: Bourne-Again shell script, ASCII text executable หมายความว่า hello.sh เป็น Shell script ของ Bourne-Again Shell (bash) เนื้อหาของแฟ้มเป็น text file ชนิดสามารถทำงานได้ หากตัดบรรทัดที่ 1 ออก คำสั่ง file จะรายงานผลดังนี้ $ file hello.sh hello: ASCII text หมายว่า hello.sh เป็น text file ตามปกติ อย่างไรก็ดีแฟ้ม Script ที่สร้างขึ้นไม่ว่าระบบจะมองเห็นเป็น text file ธรรมดา หรือ shell script ผู้ใช้ยังไม่สามารถสั่งให้ทำงานได้ เพราะยังไม่ได้กำหนดให้แฟ้มนี้มีสิทธิ execute เช่น $ hello.sh -bash: ./hello.sh: Permission denied # ไม่มีสิทธิสั่งให้ทำงาน $ chmod 700 hello.sh # เปลี่ยน mode ของแฟ้มเป็น -rwx------ $ hello.sh # ทำงานได้ Hello World! การหาเส้นทางไปยังแฟ้มโปรแกรม bash ทำได้โดยใช้คำสั่ง which เช่น $ which bash /bin/bash หมายเหตุ: แฟ้ม shell script ไม่จำเป็นต้องมีส่วนขยาย แต่ในทางปฏิบัตินิยมกำหนดให้มีส่วนขยายเป็น .sh ✦ บรรทัดที่ 2: STR="Hello World!" เป็นการกำหนดสายอักขระ "Hello World!" ให้กับตัวแปร STR มีรูปแบบทั่วไปเป็น ชื่อตัวแปร=ค่า ระหว่างชื่อตัวแปรและเครื่องหมาย = และระหว่างเครื่องหมาย = และค่า ต้องไม่มีวรรค $ STR ="Hello World!" # วรรคระหว่างชื่อตัวแปร และ =, ระบบจะเข้าใจว่า STR เป็นคำสั่ง STR: command not found # รายงานว่าไม่พบคำสั่ง STR $ STR= "Hello World!" # วรรคระหว่าง = และค่า, STR= เป็นการยกเลิกค่าของตัวแปร STR Hello World!: command not found # และเข้าใจว่า "Hello World!" เป็นคำสั่ง จึงรายงานว่าไม่พบคำสั่งนี้ ในบรรทัดคำสั่ง whitespace ใช้เป็นตัวแยก argument ออกจากกัน คามรูปแบบ คำสั่ง argument-list ✦ บรรทัดที่ 3: echo $STR อ่านค่าที่เก็บในตัวแปร STR มาใช้เป็น argument ให้กับคำสั่ง echo ต้องใช้เครื่องหมาย $ นำหน้าชื่อตัวแปร คำสั่ง $ชื่อตัวแปร ในกรณีที่ลืมใช้เครื่องหมาย $ เช่น $ echo STR ยังคงเป็นคำสั่งที่ถูกต้อง เนื่องจากคำสั่ง echo จะตีความว่า STR เป็นสายอักขระที่ต้องการให้พิมพ์ออกทาง standard output จึงได้ผลลัพธ์เป็น STR ตัวอย่างที่ 2 ที่จะกล่าวถึงค่อไปมีการใช้งาน tar (tape archive) จึงต้องเริ่มจากรายละเอียดของคำสั่งนี้ก่อน คำสั่ง tar ------- คำสั้ง tar เป็นคำย่อมาจาก tape archive หรือการสำรองข้อมูลในจานแม่เหล็กด้วยเทปแม่เหล็ก ซึ่งจะต้องเปลี่ยนโครงสร้างแบบต้นไม่ของระบบแฟ้มในจานแม่เหล็กเป็นโครงสร้างแบบเรียงลำดับในเทป และสามารถเปลี่ยนกลับมาได้ถูกต้องเหมือนเดิม เมื่อมีการเรียกคืนระบบแฟ้มสู่สภาพเดิม (restore) ในปัจจุบันนอกจากจะใช้ในการรวมแฟ้มเพื่อสำรองข้อมูลแล้ว ยังใช้รวมแฟ้มเพื่อส่งผ่านเครือข่าย หรือบันทึกลงในสื่อเพื่อนำไปติดตั้งในเครืองปลายทาง คำสั่ง tar เป็นคำสั่งตามมาตรฐาน POSIX รูปแบบการใช้งาน tar [-ตัวเลือก] ชื่อแฟ้มรวมไฟล์(.tar) ชื่อไดเรกทอรีที่ต้องรวมแฟ้ม ตัวเลือกที่มีการใช้งานบ่อยๆ เช่น -c (create) - สร้างแฟ้มรวมไฟล์ หรือสร้าง archive ใหม่ -x (extract) : แยกแฟ้มออกจาก archive -v (verbose) : แสดงชื่อแฟ้มที่กำลังดำเนินการ -f (filename) : กำหนดชื่อแฟ้มรวมไฟล์ (archive) และกำหนดให้เครื่องหมาย dash (-) ใช้แทน stdin และ stdout การสร้างแฟ้มรวมไฟล์ หรือ tar file $ tar -cvf ชื่อแฟ้มรวมไฟล์ ชื่อไดเรกทอรี การแยกแฟ้มออกจาก tar file $ tar -xvf ชื่อแฟ้มรวมไฟล์ ชื่อไดเรกทอรี ในปัจจุบันมีการใช้งาน tar ในการรวมแฟ้มทั้งหมดที่ต้องการเป็น tar file หรือ tar archive (หรือนิยมเรียกกันเล่นๆ ว่า tar ball) เพื่อความสะดวกในการนำไปติดตั้งในเครื่องอื่น หรือรวมเป็นแฟ้มไว้เพื่อความสะดวกในการ download นอกจากนี้ยังสามารถใช้งานร่วมกับโปรแกรมบีบอัดข้อมูลเพื่อลดขนาดของ tar file ด้วย โดยกำหนดตัวเลือก -z เพื่อบีบอัดหรือคลายข้อมูลด้วยโปรแกรม gzip และ gunzip แฟ้มที่มีการบีบอัดไปพร้อมกับการสร้าง tar file นิยมกำหนดให้มีส่วนขยายเป็น .tgz เพื่อแสดงว่าเป็น tar file ที่มีการบีบอัดข้อมูลด้วย gzip และหากมีการสร้างแฟ้ม tar file (*.tar) เสร็จแล้ว จากนั้นจึงเรียกใช้โปรแกรม gzip บีบอัด tar file จะกำหนดส่วนขยายเป็น *.tar.gz ตัวอย่างที่ 2: การสำรองข้อมูลอย่างง่าย ------------------------------- $ cat -n backup.sh 1 #!/bin/bash 2 OF=jira-backup-$(date +%Y%m%d).tgz 3 tar -czvf $OF ./tmp backup.sh เป็นโปรแกรมสำหรับทำการ backup ข้อมูลในไดเรกทอรีที่กำหนด (ในโปรแกรมนี้คือ ./tmp) เป็นแฟ้ม archive โดยใช้โปรแกรม tar องค์ประกอบสำคัญของชื่อแฟ้มคือวันเดือนปีที่ดำเนินการ วิธีการนี้ใช้ได้หากมีการสำรองข้อมูลไม่เกิน วันละ 1 ครั้ง ::: อธิบายการทำงานของโปรแกรม ::: ✦ บรรทัดที่ 2: OF=jira-backup-$(date +%Y%m%d).tgz กำหนดให้ตัวแปร OF เก็บชื่อแฟ้ม ซึ่งมีสองส่วนคือ ก. ค่าคงที่ string ซึ่งแล้วแต่ผู้ใช้จะกำหนด ในโปรแกรมนี้คือ "jira-backup-" และต่อด้วย ข. วันเดือนปีปัจจุบันในรูป yyyymmdd เพื่อสะดวกในการเรียงชื่อแฟ้มตามลำดับการสำรองข้อมูล วันเดือนปีปัจจุบัน หาได้โดยใช้คำสั่ง date ซึ่งใช้แสดงวันเวลาของระบบ และหากเป็นผู้ดูและระบบ (superuser) สามารถตั้งเวลาของระบบได้มีรูปแบบการใช้งานดังนี้ date [-option] [+format] สังเกตุการใช้เครื่องหมาย + ในการกำหนด format ให้แตกต่างจาก option การแสดงผลสามารถจัดได้หลายรูปแบบ โดยมีเครื่องหมาย % นำหน้าอักษรแสดงรูปแบบ (แบบเดียวกับ format ของ printf) format ของคำสั่ง date มีหลายรูปแบบ เฉพาะที่ใช้ในงานนี้ได้แก่ %Y แสดงปี ค.ศ. เป็นเลขสี่หลัก หากใช้ %y จะแสดงเฉพาะสองหลักสุดท้าย %m แสดงหมายเลขเดือนเป็นตัวเลขสองหลัก %d แสดงวันที่เป็นตัวเลขสองหลัก ดังนั้นวันเดือนปีปัจจุบันในรูป yyyymmdd จึงต้องกำหนดรูปแบบเป็น %Y%m%d เมื่อนำไปใช้ในคำสั่ง date ต้องนำหน้าด้วย + เพื่อแสดงว่าเป็น format เมื่อได้วันเดือนปีแล้วต้องนำมาต่อกับสายอักขระที่กำหนด จึงต้องแทนที่คำสั่ง date ด้วยผลลัพธ์ หรือ ทำ command substitution โดยใช้ $(...) หรือ `...` (back tick หรือ back quote) เพื่อนำไปใช้ในการกำหนดค่าให้ตัวแปร OF (เก็บชื่อของ Output File) ✦ บรรทัดที่ 3: tar -czvf $OF ./tmp ทำการรวมแฟ้มในไดเรกทอรีย่อย tmp พร้อมบีบอัดเข้าเป็น tar file โดยกำหนดให้ชื่อแฟ้มเป็นไปตามค่าที่กำหนดไว้ในตัวแปร OF ตัวแปร STR ในตัวอย่างที่ 1 และตัวแปร OF ในตัวอย่างที่ 2 เป็นตัวแปรที่มีการสร้างขึ้นใช้งานในระหว่างที่ script ทำงาน เมื่อทำงานเสร็จจะถูกทำลายไป ทดสอบได้ โดยใช้คำสั่ง "echo $ชื่อตัวแปร" หลังจาก shell script ทำงานเสร็จแล้ว ซึ่งจะไม่มีการแสดงค่าใด แสดงว่าไม่มี ตัวแปรนั้นนสิ่งแวดล้อม ความเป็นจริงทางเทคนิคคือ เมื่อผู้ใช้เรียกให้ shell script ทำงาน, shell ปัจจุบันจะทำการสร้างโพรเซสลูก และกำหนดให้โพรเซสลูกทำงานตามคำสั่งใน shell script นั้น เมื่อทำงานเสร็จ โพรเซสลูกจะสลายตัวไป ทำให้ตัวแปรทั้งหมดที่โพรเซสลูกสร้างขึ้นจะถูกทำลายไปด้วย ตัวอย่างที่ 3: ตัวแปรเฉพาะถิ่น (Local variables) ------------------------------------------ Shell script สามารถมีโปรแกรมย่อยได้เช่นเดียวกับโปรแกรมทั่วไป และสามารถกำหนดให้ตัวแปรของโปรแกรมย่อยนั้นเป็นตัวแปรเฉพาะถิ่นได้โดยใช้คำสำคัญ local เช่น $ cat -n ocalvar.sh 1 #!/bin/bash 2 HELLO=Hello 3 function hello 4 { 5 local HELLO=World 6 echo $HELLO 7 } 8 echo $HELLO 9 hello 10 echo $HELLO -- Script นี้แสดง "แนวคิด" หลายอย่าง -- 1. การประกาศและการกำหนดรายละเอียดของฟังก์ชัน hello ในบรรทัดที่ 4 - 7 2. การประกาศตัวแปร HELLO เป็นตัวแปร local ในฟังก์ชัน hello ซึ่งมีชื่อพ้องกับตัวแปร global แต่กำหนดค่าต่างกัน 3. ความสับสนระหว่างชื่อ identifier และค่าคงที่ - HELLO เป็นชื่อตัวแปร ทั้งตัวแปร global และตัวแปร local - hello เป็นชื่อโปรแกรมย่อย - Hello เป็นค่าคงที่สายอักขระ นำมาใช้ในการกำหนดค่าโดยตรง ไม่ได้ quote ด้วย single หรือ double quote และไม่มีความจำเป็นที่จะต้อง quote เพราะไม่อักขระพิเศษของ shell การตั้งชื่อในลักษณะนี้ไม่มีปัญหาใดต่อตัวแปลภาษาของ shell เพราะเป็นการจำแนกโดยใช้ตำแหน่งทางไวยกรณ์ แต่จะมีปัญหา ต่อผู้เขียนโปรแกรม จึงเป็นสิ่งที่ไม่ควรทำ แต่ไม่สามารถห้ามผู้อื่นทำได้ เมื่อจำเป็นต้องอ่านโปรแกรมลักษณะนี้ต้องทำตัวให้เหมือน ตัวแปลภาษา เช่น HELLO=Hello เครื่องหมาย = หรือ assignment operator ใช้ในการกำหนดค่าให้กับตัวแปร ด้านซ้ายมือจึงเป็นชื่อของตัวแปร ด้านขวามือเป็นค่า ที่สตริงที่ใช้กำหนดให้กับตัวแปร, สำหรับ hello เป็นฟังก์ชันที่ชัดเจนมาก อนึ่งโปรแกรมลักษณะ comment ที่ดีจำเป็นมาก เช่น 1 #!/bin/bash 2 HELLO=Hello # สร้่างและกำหนดค่าตัวแปร global 3 function hello # บรรทัดที่ 3 - 7: กำหนดรายละเอียดการทำงานของฟังก์ชัน hello 4 { 5 local HELLO=World # สร้้างและกำหนดค่าตัวแปร local ซึ่งมีชื่อพ้องกับตัวแปร global 6 echo $HELLO 7 } 8 9 echo $HELLO # การทำงานจริงของโปรแกรม แสดงค่าของตัวแปร global 10 hello # เรียกใช้งานฟังก์ชัน hello เพื่อแสดงค่าของตัวแปร local 11 echo $HELLO # แสดงค่าของตัวแปร global อีกครั้งหนึ่ง Command Line Arguments และตัวแปรพิเศษ ----------------------------------- รูปแบบทั่วไปของโปรแกรมอรรถประโยชน์ (Utilities) ของ Unix คำสั่ง [-option] argument-list +------------------------+ + command line argument ช่องทางสื่อสารระหว่างผู้ใช้และโปรแกรม ตัวอย่าง: บรรทัดคำสั่งแสดงชื่อแฟ้มภาษา C ทั้งหมด (แฟ้มโปรแกรม และ header file) ในไดเรกทอรีปัจจุบัน $ ls -l *.c *.h องค์ประกอบ ls -l *.c *.h เป็น บรรทัดคำสั่ง (command line) ls เป็นคำสั่ง (command) -l *.c *.h เป็น argument ของบรรทัดคำสั่ง (command line argument) มี 3 ชุด แต่ละชุดคั่นด้วยเครื่องหมายวรรคตอน การอ้างอิงถึงองค์ประกอบของบรรทัดคำสั่ง ใช้ตัวแปรพิเศษ เรียกว่า positional parameter กำหนดลำดับดังนี้ ls -l *.c *.h $0 $1 $2 $3 argument ทั้งหมดของบรรทัดคำสั่งแทนด้วย $@ หรือ $* (แทน "-l *.c *.h" ในตัวอย่าง) จำนวนของ argument ทั้งหมด $# (ไม่รวมคำสั่ง) shell script สาธิตการทำงานของ Positional parameter $ cat -n args.sh 1 #!/bin/bash 2 3 i=0 4 n=1 5 argv=("$@") 6 7 echo 'argument count, $# = ' $# 8 echo argument values: 9 while [ $i -lt $# ]; do 10 echo \$$n = ${argv[$i]} 11 i=$(($i + 1)) 12 n=$(expr $n + 1) 13 done ผลการทำงานของ Script -------------------- $ args.sh xx yy zz argument count, $# = 3 argument values: $1 = xx $2 = yy $3 = zz รายละเอียดของ shell script: args.sh ---------------------------------- โครงของโปรแกรมแบ่งออกเป็น 3 ตอนคือ - header ของ bash shell script (บรรทัดที่ 1) - การสร้างและการกำหนดค่าเริ่มต้นของตัวแปร (บรรทัดที่ 3 - 6) - การประมวลผลของโปรแกรม (บรรทัดที่ 7 - 13) - พิมพ์จำนวนของ argument ที่ได้รับ และบรรทัดหัวเรื่อง - โครงสร้างทำซ้ำชนิด while สำหรับพิมพ์ค่าของ argument ที่ได้รับจนครบทุกตัว การสร้างและการกำหนดค่าเริ่มต้นของตัวแปร -------------------------------- 3 i=0 4 n=1 5 argv=("$@") การสร้างและกำหนดค่าของตัวแปร i และ n เป็นไปตามรูปแบบปกติ สำหรับบรรทัดที่ 5 เป็นการนำค่าของ arguments ทั้งหมด ที่ได้รับ (ตัวแปร $@ - เฉพาะ argument จริง ไม่รวมคำสั่ง) มากำหนดให้กับตัวแปร argv - ค่าของ $@ อาจเป็นค่าว่าง, มีค่าเดียว, หรือ อาจมีได้หลายค่า (zero or more) การแสดงว่าค่าที่นำมากำหนดให้กับตัวแปร ทำได้โดยการกำหนดค่านั้นไว้ในวงเล็บ และเพื่อป้องกัน shell ขยายค่าอักขระพิเศษ ที่อาจมี จึง quote ด้วย double quote - เมื่อค่าทางขวามือมีหลายค่า argv จึงต้องเป็นตัวแปรที่สามารถเก็บค่าได้หลายค่า (เป็น array) โดยเก็บค่าแรกใน argv[0], ค่าที่สองใน argv[1], ... --> argv=("S@") จึงเป็นนำ argument มาสร้างเป็น array ของ argument นั้นเอง โครงสร้างทำซ้ำชนิด while ---------------------- 9 while [ $i -lt $# ]; do 10 echo \$$n = ${argv[$i]} 11 i=$(($i + 1)) 12 n=$(expr $n + 1) 13 done รูปแบบทั่วไปของโครงสร้างทำซ้ำแบบ while while [ นิพจน์เปรียบเทียบ ]; do while [ นิพจน์เปรียบเทียบ ] ... คำสั่งประมวลผลใน loop ... do done ... คำสั่งประมวลผลใน loop ... done 9 while [ $i -le $# ]; do [ ... ] เป็นรูปย่อของคำสั่ง test สำหรับ "ทดสอบ" นิพจน์ ให้ผลลัพธ์เป็นค่าความจริง "นิพจน์" มีหลายแบบ เช่น นิพจน์เปรียบเทียบ และ นิพจน์สำหรับทดสอบแฟ้ม เป็นต้น นิพจน์เปรียบเทียบ แยกเป็น - เปรียบเทียบสายอักขระ; =, != # เท่า หรือ ไม่เท่า - เปรียบเทียบจำนวนเต็ม; eq, ne, gt, ge, lt, le # เฉพาะกรณีที่แน่ใจว่าค่าของตัวแปรเป็นตัวเลข [ $i -le $# ] ค่าของตัวแปร i <= จำนวนของ arguments 10 echo \$$n = ${argv[$(echo $i)]} ต้องการแสดงผลเช่น $1 = ค่าของ rgument ตัวแรก , $2 = ค่าของ rgument ตัวแรก, ... พิมพ์เครื่องหมาย $ ซึ่งเป็นอักขระพิเศษ จึงต้อง quote ด้วย \$, หากไม่ quote จะได้ $$ ซึ่งเป็นตัวแปรพิเศษเก็บ หมายเลขโพรเซส (process id - pid) พิมพ์ตัวเลขซึ่งเป็นค่าของตัวแปร n (เริ่มจาก 1) ตามด้วย = ;e cho \$$n = --> ได้ผลลัพธ์เป็น $1 = พิมพ์ค่าของ argument ตัวแรก ซึ่งเป็นสมาชิกลำดับที่ 0 ของ argv ซึ่งเป็น array - การเข้าถึงสมาชิกของ array มีรูปแบบทั่วไปเป็น -- ${ชื่อ-array[ดัชนี]} ค่าของดัชนีคือในโปรแกรมนี้คือค่าของตัวแปร i -- ${argv[$i]} บรรทัดที่ 11 - 12 การเพิ่มค่าของตัวแปร แสดงวิธีการที่นิยมใช้ 2 แบบ ซึ่งมีการทำงานเทียบเท่ากัน 11 i=$(($i + 1)) เนื่องจากค่าของตัวแปร i ในโปรแกรมเป็นจำนวนเต็ม จึงสามารถนำมาใช้ในนิพจน์คณิคศาสตร๋ได้ และคำนวณค่าของนิพจน์ โดยใช้ $((...)) -- วงเล็บสองชั้น คือ คำนวณค่านิพจน์คณิตศาสตร์ เป็นคำสั่งที่มีใน shell รุ่นใหม่ 12 n=$(expr $n + 1) ใช้คำสั่ง expr สำหรับหาค่าของนิพจน์คณิตศาสตร์ และนำผลลัพธ์ของ expr ไปกำหนดให้ตัวแปร โดยใช้ $() -- วงเล็บชั้นเดียว คือ command substitution ตัวอย่าง : การใช้งาน Command Line Argument -------------------------------------- โปรแกรม backup.sh สำหรับการสำรองข้อมูลอย่างง่ายที่ผ่านมา จำกัดการใช้งานไว้เฉพาะผู้ใช่ jira และกำหนดไดเรกทอรีไว้ ตายตัวคือ ./tmp ซึ่งหากเป็นโปรแกรมซึ่งมีผู้ใช้งานคนเดียวและสำรองข้อมูลในไดเรกทอรีที่กำหนดเสมอ อาจไม่มีปัญหาใด แต่ หากผู้ใช้ต้องการเปลี่ยนไดเรกทอรีที่จะสำรองข้อมูลต้องแก้โปรแกรมใหม่ หรือหาต้องการติดตั้งไว้เป็นโปรแกรมส่วนกลางสำหรับ ผู้ใช้งานทุกคนในระบบ ต้องปรับปรุงให้สามารถรับชื่อผู้ใช้และไดเรกทอรีที่ต้องการได้ ผ่านทาง command line argument ผู้ใช้เรียกโปรแกรม backup.sh และกำหนด ชื่อผู้ใช้ และ ไดรกทอรีที่ต้องการสำรองข้อมูล ดังนี้ $ backup ชื่อผู้ใช้ ชื่อไดเรกทอรี โปรแกรม backup.sh ที่ดัดแปลงแล้ว เป็นดังนี้ $ cat -n backup.sh 1 #!/bin/bash 2 OF=$1-backup-$(date +%Y-%m-%d).tgz # ใช้ตัวแปร $1 เป็นส่วนหนึ่งของชื่อแฟ้ม 3 tar -czvf $OF $2 # ใช้ตัวแปร $2 เป็นชื่อไดเรกทอรี ตัวอย่างการทำงาน ผู้ใช้ somchai ต้องการสำรองข้อมูลในไดเรกทอรี MyProject ใช้คำสั่งดังนี้ $ backup somchai MyProject โปรแกรมจะทำการรวมแฟ้มทั้งหมดในไดเรกทอรี MyProject และบีบอัดเป็น archive file ชื่อ somchai-backup-2016-03-17.tgz ปัญหาของโปรแกรมที่รับข้อมูลผ่าน command line argument คือ หากผู้ใช้เรียกใช้งานเฉพาะคำสั่ง โดยไม่มี argument จะเกิด ความผิดพลาดในการทำงานจาก Utilities ที่เรียกใช้ เช่น $ backup2.sh tar: Cowardly refusing to create an empty archive # tar ไม่กล้าที่จะสร้างแฟ้ม archive ว่าง Try 'tar --help' or 'tar --usage' for more information. # ใช้ tar --help เพื่อดูวิธีการใช้งาน เนื่องจากเป็น error ที่เกิดขึ้นจากโปรแกรม tar ที่ script เรียกใช้ จึงเป็น error ที่ไม่สามารถแก้ไขได้ แต่ สามารถป้องกันได้ โดยการตรวจสอบ command line argument ให้ดีที่สุดเท่าที่จะทำได้ ทั้งจำนวน และ ความสมเหตุสมผลของ argument ซึ่ง ต้องใช้โครงสร้างแบบมีการเลือก และอาจต้องใช้โครงสร้างแบบทำซ้ำเข้าช่วย การเขียน script เป็นแบบฝึกหัดที่ดีในการทำหัดทำ error handling ซึ่งเป็นองค์ประกอบที่สำคัญสำหรับการเขียนโปรแกรมเป็น อาชีพ ตัวอย่างการใช้ () จัดกลุ่มคำสั่ง ------------------------ shell script ต่อไปนี้ใช้คำสั่ง tar (tape archive) ในการคัดลอกแฟ้มทั้งหมดจากไดเรกทอรีหนึ่งไปยังอีกไดเรกทอรีหนึ่งScript สำหรับคัดลอกแฟ้มทั้งหมดจากไดเรกทอรีหนึ่งไปยังอีกไดเรกทอรีหนึ่ง $ cat cpdir ( cd $1 ; tar -cf - . ) | ( cd $2 ; tar -xvf - . ) ถ้าต้องการคัดลอกแฟ้มทั้งหมดจาก ~/886326/week04 ไปยัง ~/886326/temp เรียกใช้ด้วย $ cpdir ~/886326/week04 ~/886326/temp ข้อมูลที่จำเป็น $1 และ $2 เป็นตัวแปรระบบ ซึ่งเชลล์จะผ่าน argument ที่ได้รับจากบรรทัดคำสั่งเข้าไปยัง shell script กล่าวอีกนัยหนึ่งคือ $1 และ $2 เป็น positional parameters นั่นเอง $ cpdir ~/886326/week04 ~/886326/temp -------------- ------------- $1 $2 เมื่อแทนค่าตัวแปรใน cpdir จะได้ ( cd ~/886326/week04 ; tar -cf - . ) | ( cd ~/886326/temp ; tar -xvf - .) เมื่อ script ทำงาน จะแยกการทำงานออกเป็นสอง subshell คำสั่งในแต่ละวงเล็บจะทำงานใน subshell ของตนเอง วงเล็บแรก แทนคำสั่งกลุ่มแรก ได้แก่การเปลี่ยนไดเรกทอรีไปยัง ~/886326/week04 จากนั้นจึงทำการรวมแฟ้มทั้งหมดในไดเรกทอรีปัจจุบันเป็น archive ส่งออกทาง pipe วงเล็บที่สองคือแทนคำสั่งกลุ่มที่สอง ได้แก่การเปลี่ยนไดเรกทอรีไปยัง ~/886326/temp จากนั้นจึงอ่าน archive จาก pipe แล้วจึงแตกแฟ้มทั้งหมดออกจาก archive เก็บลงในไดเรกทอรีปัจจุบัน