Redirection - Pipeline ------------------------------ Redirection - เปลี่ยนทิศทาง input/output ของโพรเซส จากแป้นพิมพ์/จอภาพ เป็นแฟ้มที่กำหนด - เปลี่ยนทิศทางของ input จากแป้นพิมพ์เป็นข้อมูลจากแฟ้ม test.dat $ a.out < test.dat - เปลี่ยนทิศทางของ output ของคำสั่ง ls เป็นแฟ้มที่กำหนด $ ls > log - แสดงชื่อไดเรกทอรีปัจจุบันทางจอภาพ และเก็บเฉพาะรายชื่อแฟ้มในแฟ้ม log $ pwd ; ls -l > log # ; คั่นระหว่างคำสั่ง - เก็บชื่อไดเรกทอรีปัจจุบัน และรายชื่อแฟ้มในแฟ้ม log $ ( pwd ; ls -l ) > log # (...) รวมคำสั่งเป็นกลุ่มเดียวกัน Pipeline - ส่ง output ของโพรเซสหนึ่ง เป็น input ของอีกโพรเซสหนึ่ง ผ่าน pipe ซึ่งเป็นช่องทางสื่อสารข้อมูลระหว่างโพรเซส (IPC) - เปลี่ยนทิศทาง output ของคำสั่ง ls เป็น input ของคำสั่ง more $ ls -l | more การแสดงผลของคำสั่ง ls -------------------------- คำสั่ง ls - list directory contents (แสดงรายชื่อแฟ้มในไดเรกทอรีที่กำหนด ไดเรกทอรีปัจจุบันเป็นไดเรกทอรีโดยปริยาย) การแสดงรายชื่อแฟ้มเมื่อกำหนดด้วย option ต่างๆ มีผลดังนี้ -C แสดงรายชื่อแฟ้มเป็นคอลัมน์ จำนวนของ column ขึ้นกับความยาวของชื่อแฟ้ม และ ความกว้างของจอ (คั่นชื่อแฟ้มแต่ละชื่อในบรรทัดเดียวกันด้วย white space) -1 แสดงรายชื่อแฟ้มบรรทัดละชื่อ (คั่นชื่อแฟ้มแต่ละชื่อด้วย newline) -l แสดงรายชื่อแฟ้มพร้อมด้วยข้อมูลรายละเอียดของแฟ้ม บรรทัดละชื่อ นำด้วยบรรทัดแสดงจำนวน block ทั้งหมดที่แฟ้มใน ไดเรกทอรีนั้นใช้งาน (total nnn) เมื่อนำมาใช้ในการนับจำนวนแฟ้มในไดเรกทอรีที่กำหนด เช่น $ ls /usr/bin | wc -l ls ส่ง output ซึ่งเป็นรายชื่อแฟ้มใน /usr/bin ไปเป็น input ของ wc ซึ่งจะทำหน้าที่นับจำนวน "บรรทัด" ของข้อมูลที่ได้รับ คำถาม: จากข้อมูลการแสดงผลของ ls เมื่อใช้ option ต่างๆ ผลลัพธ์ที่ได้เป็นจำนวนแฟ้มที่มีใน /usr/bin ใช่แน่นอนหรือไม่? จะมีวิธีการทดสอบอย่างไร? และหากคำตอบคือ "ใช่" เป็นเพราะเหตุใด? การทดสอบ: นับจำนวนแฟ้มจริงใน /usr/bin เปรียบเทียบกับผลลัพธ์ที่ได้ ได้คำตอบที่แน่นอน แต่เสียเวลามาก วิธ๊การที่ดีกว่าคือ ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ทดลองกับไดเรกทอรีที่มีแฟ้มจำนวนน้อย เช่น 5 - 6 แฟ้ม สัก 2-3 ไดเรกทอรี เพื่อให้นับได้ง่าย ทดลองได้ไดเรกทอรีเพื่อให้สามารถยืนยันให้แน่ใจได้ คำถาม: ผลลัพธ์จากคำสั่ง ls <ไดเรกทอรี> | wc -l เป็นจำนวนแฟ้มในไดเรกทอรีใช่หรือไม่? คำตอบ: ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... เพราะเหตุใด? จะตอบคำถามนี้ได้ ต้องอ่าน Online manual page ของคำสั่ง ls ประกอบกับการทดลองดังต่อไปนี้ การทดลอง ------------ สร้างแฟ้มว่าง (แฟ้มที่มีขนาดเป็น 0 ไบต์) ชื่อ a.dat, b.dat, และ c.dat โดยใช้คำสั่ง touch ดังนี้ $ touch a.dat b.dat c.dat $ ls a.dat b.dat c.dat คำสั่ง ls จะแสดงรายชื่อแฟ้มหลายแฟ้มในบรรทัดเดียวกัน และแสดงแต่ละบรรทัดเป็นคอลัมน์ (คั่นแต่ละขื่อด้วย white space) หรือมีการ แสดงผลเช่นเดียวกับการใช้ option -C เมื่อนำคำสั่ง ls มาใช้ร่วมกับก่ารเปลี่ยนทิศทาง output เช่น $ ls > ls.txt # เปลี่ยนทิศทางของ output จาก standard output ไปยังแฟ้ม ls.txt $ cat ls.txt # แสดงข้อมูลในแฟ้ม ls.txt (cat - concatenation) a.dat b.dat c.dat ls.txt หรือเมื่อใช้ร่วมกับคำสั่งอื่นใน pipeling เช่น $ ls | more # ส่ง output ของคำสั่ง ls (ผ่าน pipe) ไปเป็น input ของ more a.dat b.dat c.dat ls.txt คำสั่ง ls จะแสดงชื่อแฟ้มบรรทัดละชื่อ (คั่นชื่อแฟ้มแต่ละชื่อด้วย newline) หรือมีการแสดงผลเช่นเดียวกับการใช้ option -1 คำอธิบาย: คำสี่ง ls เป็นคำสั่งที่มีการตรวจสอบปลายทางของ output stream หากเป็น terminal (จอภาพ) จะกำหนดให้ -c เป็น option โดยปริยาย หากไม่ใช่จะกำหนดให้เป็น -1 --------------------------------------------------------------------------------------------------------------------------------------------- === สำหรับผู้ที่เรียนหรือมีความรู้ด้าน System Programming === โปรแกรม ls เขียนด้วยภาษา C และเรียกใช้ isatty() ซึ่งเป็นฟังก์ชันใน #include isatty() - ทดสอบว่า file descriptor ที่กำหนด เป็น terminal หรือไม่? คืนค่าเป็น 1 (จริง) หาก file descriptor ต่อกับ terminal, และ 0 (เท็จ) หากไม่ใช่ เช่น ต้องการทดสอบว่า standard output (อุปกรณ์แสดงผลมาตรฐาน) ซึ่งมี file descriptor เป็น 2 ต่อกับจอภาพใช่หรือไม่ ret_val = isatty(2); ตัวอย่างการประยุกต์ใช้งานเช่น if ( standard input เป็น terminal ) ทำงานแบบโต้ตอบกับผู้ใช้ - interactive mode แสดงคำแนะนำประกอบการรับข้อมูล else ทำงานโดยรับข้อมูลอย่างเดียว (batch mode) รับข้อมูลโดยไม่ต้องแสดงข่าวสาร end {if} --------------------------------------------------------------------------------------------------------------------------------------------- จำนวนโปรแกรมอรรถประโยขน์ (Utlities) ของระบบปฏิบัติการ Unix ---------------------------------------------------------------------- เมื่อแน่ใจแล้วว่าการนับจำนวนแฟ้มโดยใช้ "ls <ไดเรกทอรี> | wc -l" ทำงานได้ถูกต้อง จึงนำมาใช้ในการนับ Utilities ของระบบ ปฏิบัติการ Unix ซึ่งเก็บในไดเรกทอรีหลัก 4 แห่งคือ /bin, /sbin, /usr/bin, และ /usr/sbin ซึ่งจำนวน utilities ในแต่ละไดเรกทอรั แตกต่างกันออกไปตามชนิดและรุ่นของระบบปฏิบัติการ โปรแกรมที่เก็บในไดเรกทอรีทั้ง 4 มีรายละเอียดดังนี้ /bin ----- เก็บ utilities พื้นฐานสำหรับการทำงานทั่วไป เช่น cat, ls, mkdir เพื่อให้ผู้ใช้สามารถทำงานพื้นฐานได้ ก่อนที่จะมีการ ติดตั้ง (mount) partition ที่มีไดเรกทอรี /usr /sbin ------ เก็บ utilities สำหรับผู้ดูและระบบ ผู้ที่จะใช้งานได้ต้องมีสิทธิในระดับ superuser (superuser privilege) เช่นคำสั่ง fdisk, mk2efs, fsck, mount และ unmount เป็นต้น /usr/bin --------- เก็บ utilities ทั่วไป คล้ายกับ /bin แต่ค่อนข้างเป็นโปรแกรมเฉพาะเช่น เครื่องคิดเลข (bc), ตัวแปลภาษา (เช่น gcc และ g++), โปรแกรมบีบอัดข้อมูล (เช่น zip และ unzip), และโปรแกรมบริหารจัดการฐานข้อมูล (mysqladmin) เป็นต้น /usr/sbin ----------- เก็บ utilities สำหรับผู้ดูและระบบ คล้ายกับ /sbin แต่ค่อนข้างเป็นโปรแกรมเฉพาะ เช่น โปรแกรมสร้างผู้ใช้ในระบบ (adduser) และ โปรแกรมกำหนดให้โปรแกรมที่ต้องการทำงานเมื่อถึงเวลาที่กำหนด (cron) เป็นต้น การนับจำนวนโปรแกรม Utilities ทั้งหมด คือการนับจำนวนโปรแกรมใน 4 ไดเรกทอรีนี้ การทดสอบคำสั่งเบื้องต้น -------------------------- คำสั่ง ls เมื่อใช้กับไดเรกทอรีที่แตกต่างกัน 2 ไดเรกทอรี เช่น $ ls ./886326 ./886327 จะแสดงผลดังนี้ ./886326 รายชื่อแฟ้มในไดเรกทอรี 886326 <บรรทัดว่าง> ./886327 รายชื่อแฟ้มในไดเรกทอรี 886327 เมื่อนำมาใช้กับ pipeline $ ls ./886326 ./886327 | wc -l คำตอบที่ได้จะเกินความเป็นจริงอยู่ 3 คือ บรรทัด ./886326, <บรรทัดว่าง>, และ ./886327 วิธีการนี้ให้ผลลัพธ์สูงกว่าความเป็นจริง ส่วนจะมากกว่าเท่าไร ขึ้นอยู่กับจำนวนไดเรกทอรีที่ต้องการให้นับ โดยมีความสัมพันธ์ดังนี้ จำนวนแฟ้ม = จำนวนที่นับได้ - (2*จำนวนไดเรกทอรี) - 1 # เฉพาะเมื่อมีจำนวนไดเรกทอรี > 1 การคำนวณทำได้โดยใช้โปรแกรม bc (basic calculator) แต่หากนำไปเขียนเป็น Shell script ไม่สามารถกำหนดให้ทำงานโดย อัตโนมัติได้ เพราะผู้ใช้ต้องกำหนดจำนวนไดเรกทอรีด้วยตนเอง นอกจากนี้หากมีการใช้งานกับไดเรกทอรีแล้ว ต้องเพิ่มโครงสร้าง if เพื่อทำงานตามเงื่อนไขด้วย ซึ่งจะกลายเป็นงานที่ซับซ้อน ผิดหลักการ KISS ซึ่งเป็นคำที่ย่อมาจาก "Keep it simple, stupid" ซึ่งเป็นหลักในการออกแบบของราชนาวีสหรัฐในทศวรรษ 1960 หลักการนี้กล่าวว่าระบบที่ทำงานได้ดีส่วนใหญ่เป็นระบบที่ง่าย (simple) ไม่ซับซ้อน เป็นหลักการออกแบบที่ได้รับความนิยมแพร่หลายในทศวรรษ 1970 ซึ่งมีการเปลี่ยนคำไปหลายแบบ เช่น "keep it short and simple", "keep it simple and straightforward" และ "keep it small and simple" เป็นต้น วิธีการที่ง่ายกว่า และ "ดีกว่า" คือนับจำนวนโปรแกรมในแต่ละไดเรกทอรีแล้วนำมารวมกัน โดยเรียกใช้ Utilities ที่สามารถประมวลผล นิพจน์คณิตศาสตร์ได้เช่น bc (basic calculator), expr (evaluate expressions - ซึ่งใช้หาค่าของนิพจน์ได้ทั้ง นิพจน์ตรรกะ, นิพจน์ คณิตศาสตร์ และการหาpattern ของสายอักขระ) แต่อย่างไรก็ดีการใช้ expr สำหรับผู้เริ่มต้นอาจมีปัญหาเรื่องการ quote อักขระที่ซ้ำ กับ meta character ของ shell เช่น <, >, * เพื่อให้ทำหน้าที่เป็นอักขระปกติ (literal character) ในปฏิบัติการคราวนี้จึงเลือกใช้ basic calculator ซึ่งทำงานได้สองลักษณะคือเป็นโปรแกรมที่ทำงานโต้ตอบกับผู้ใช้ (Interactive program) และเลิกการทำงานด้วยคำสั่ง quit และทำงานแบบ pipeline ซึ่งจะเลิกทำงานเมื่อพบ end-of-file ปัญหาที่เหลืออยู่ ----------------- ปัญหาที่สำคัญอีกอย่างหนึ่งคือจะนำผลลัพธ์ของชุดคำสั่ง "ls <ไดเรกทอรี> | wc -l" เป็นข้อมูลเข้าของโปรแกรม bc ได้อย่างไร? คำตอบคือใช้การเปลี่ยนแทนคำสั่งด้วยผลลัพธ์ หรือ Command substitution Command substitution -------------------------- Command substitution เป็นการนำผลลัพธ์ที่ได้จากการทำงานของโปรแกรมหนึ่งไปใช้เป็น argument ของอีกโปรแกรมหนึ่ง วิธีการนี้เริ่ม มีการใช้งานเป็นครั้งแรกใน Bourne shell ของระบบปฏิบัติการ Unix 7th Edition (1979) และกลายมาเป็นวิธีการมาตรฐานในทุกเชลล์ นอก จากนี้ยังมีการใช้งานในคัวแปลภาษาแบบ Script เช่น Perl, PHP, และ Ruby เป็นต้น นอกจากระบบปฏิบัติการ Unix แล้ว ยังมีการใช้งานใน Windows Powershell และโปรแกรม cmd.exe ในระบบปฏิบัติการของบริษัทไมโครซอฟต์อีกด้วย Command substitution นำผลของโปรแกรมหนึ่งไปใช้เป็น (ก). argument ของอีกโปรแกรมหนึ่ง, (ข). กำหนดค่าให้กับตัวแปร, และ (ค). ใช้เป็น argument ของ for loop หมายเหตุ: 1. Windows Powershell เป็นเชลล์ชนิด Command Line Interface และตัวแปลภาษา Script สำหรับใช้ในการบริหารจัดการระบบสำหรับ Microsoft Windiws Server โดย ทำงานร่วมกับสถาปัตยกรรม .NET Framework 2. cmd.exe เป็นเชลล์ชนิด Command Line Interface และตัวแปลภาษา Script อย่างง่ายสำหรับระบบปฏิบัติการ Microsoft Windows รูปแบบการใช้งาน (Syntax) ----------------------------- Command substitution มีรูปแบบการใช้งานสองรูปแบบคือ 1. Classic form ใช้เครื่องหมาย backquote (หรือ bactick หรือ grave accent), `...`. ล้อมคำสั่งที่ต้องการ 2. POSIX form ใช้ $(...) ล้อมคำสั่งที่ต้องการ ตัวอย่าง: $ echo "Today is `date`" หรือ $ echo "Today is $(date)" Today is Mon Jan 18 14:44:46 ICT 2016 # นำผลลัพธ์ของคำสั่ง date แทนที่ลงในตำแหน่งของคำสั่ง หมายเหตุ ---------- 1. คำสั่ง date ใช้สำหรับแสดงวันเวลาของระบบ เช่น Mon Jan 18 14:44:46 ICT 2016, ICT คือ IndoChina Time - เวลามาตรฐาน ชองประเทศในคาบสมุทรอินโดจีน ได้แก่ ไทย ลาว กัมพูชา และ เวียตนาม (UTC +7:00) 2. คำสั่ง echo ใช้สำหรับแสดงข้อความที่กำหนดทาง standard output 3. เครื่องหมาย ` (ระบบปฏิบัติการของบริษัท Micrososft เรียกว่า Grave Accent) ใช้เป็นแป้นสำหรับเปลี่ยนแป้นพิมพ์ระหว่าง ภาษาอังกฏษและภาษาไทย เมื่อจะใช้งาน ต้องกำหนดแป้นสำหรับเปลี่ยนภาษาใหม่ ซึ่งใน Microsoft Windows 7 ทำได้ดังนี้ - click ขวาที่แถบอักษรแสดงภาษา (EN|TH) ด้านขวาของ Task bar เลือก Setting... จะได้กรอบเมนู -- Text Services and IAnput Languages -- - เลือก Advanced Key Settings และเลือก Change Key Sequence ซึ่งอยู่ส่วนล่างของกรอบ - เปลี่ยน Switch Input Language จาก Grave Accent (`) เป็น Ctrl + Shift หรือ Left Alt + Shift ตามต้องการ และเลือก OK - เลือก Apply และเลือก OK วิธีการทำงาน (Semantics) ---------------------------- การทำงานตามคำสั่งเช่น echo "Today is $(date)" เมื่อเริ่มทำงาน shell จะสร้างโพรเซสลูกและกำหนดให้ทำงานตามคำสั่ง date และรอ รับผลการทำงานผ่าน pipe เมื่อโพรเซสของ date ทำงานเสร็จ ปิด pipe และสลายตัว, shell จะทำการแยก (parsing) ผลที่ได้รับ จากนั้น จึงสร้างโพรเซสลูกและกำหนดให้ทำงานตามคำสั่ง echo และส่งผลัพธ์ที่แยกแล้วผ่าน pipe เพื่อใช้เป็น argument ของ echo pipeline และ command substitution ---------------------------------------- ความแตกต่างระหว่าง pipe และ command subsitution อยู่ที่ input และ argument, input คือข้อมูลที่โพรเซสรับผ่าน standard input เช่น แป้นพิมพ์ เมื่อโพรเซสทำงานแล้ว ส่วน argument เป็นข้อมูลที่กำหนดในบรรทัดคำสั่ง คำสั่ง echo รับข้อมูลที่ต้องการพิมพ์ผ่าน command line argument (ไม่มีการรับ input หรือข้อความที่จะพิมพ์ทางแป้นพิมพ์) คำสั่ง $ date | echo จึงไม่มีการแสดงผลลัพธ์ใด เพราะเป็นการส่ง output ของ date คือ "Mon Jan 18 14:44:46 ICT 2016" เป็น input ของคำสั่ง echo เนื่องจาก echo ไม่มีการรับข้อมูลจาก standard input จึงไม่มีการดำเนินการใดกับข้อมูลที่ได้รับ ส่วนคำสั่ง $ echo "Today is $(date)" Today is Mon Jan 18 14:44:46 ICT 2016 เป็นการนำสายอักขระ "Mon Jan 18 14:44:46 ICT 2016" แทนที่ $(date) ใน command line argument ของ echo จึงแสดงผลได้ อย่างถูกต้อง การนับจำนวน Utilities ของระบบปฏิบัติการ Unix (รอบสุดท้าย) ----------------------------------------------------------------- เราจะนำ Command substitution มาใช้ในการนับจำนวน Utilities ขอให้ดูตัวอย่างต่อไปนี้ หากใช้คำสั่ง $ echo `ls /bin | wc -l` + `ls /sbin | wc -l` + `ls /usr/bin | wc -l` + `ls /usr/sbin | wc -l` หรือ $ echo $(ls /bin | wc -l) + $(ls /sbin | wc -l) + $(ls /usr/bin | wc -l) + $(ls /usr/sbin | wc -l) จะได้ผลลัพธ์เป็น (มกราคม 2559) 152 + 191 + 921 + 176 ซึ่ง output ของคำสั่ง echo เป็นนิพจน์คณิตศาสตร์ที่สามารถนำไปใช้เป็น input ของ bc ได้ -- ทำ pipeline คำสั่ง $ echo `ls /bin | wc -l` + `ls /sbin | wc -l` + `ls /usr/bin | wc -l` + `ls /usr/sbin | wc -l` | bc หรือ $ echo $(ls /bin | wc -l) + $(ls /sbin | wc -l) + $(ls /usr/bin | wc -l) + $(ls /usr/sbin | wc -l) | bc จะได้ผลลัพธ์เป็น (มกราคม 2559) 1440 นอกจาก bc (basic calculator) ซึ่งสามารถคำนวณได้ทั้งจำนวนเต็ม (default), จำนวนจริง และฟังก์ชันคณิตศาสตร์ใน math library ของ ภาษา C (เรียกใช้ด้วย option -l) ในกรณีที่มีการใช้งานเฉพาะจำนวนเต็ม สามารถกำหนดให้มีการคำนวณนิพจน์คณิตศาสตร์ด้วย $((...)), หรือใช้โปรแกรม expr ก็ได้ $((นิพจน์)) - Arithmetic expansion --------------------------------------- ใช้ในการหาค่าของนิพจน์ที่กำหนดตามวิธีการของคณิตศาสตร์จำนวนเต็ม เช่น $ echo $(( $(ls /bin | wc -l) + $(ls /sbin | wc -l) + $(ls /usr/bin | wc -l) + $(ls /usr/sbin | wc -l) )) คำสั่ง expr - หาค่าของนิพจน์ ------------------------------ เป็นคำสั่งที่จำเป็นต้อง quote อักขระชที่กำหนดเป็น meta character ของ shell เมื่อต้องการใช้งานเป็นอักขระธรรมดา (literal character) ตัวอย่างของ meta character ของ shell เช่น *, >, และ < เป็นต้น ตัวอย่างการคำนวณค่า เช่น $ expr $(ls /bin | wc -l) + $(ls /sbin | wc -l) + $(ls /usr/bin | wc -l) + $(ls /usr/sbin | wc -l) คำสั่ง expr ในปัจจุบันแทบไม่มีการใช้งานแล้ว เพราะสามารถใช้รูปแบบที่ง่ายกว่าคือ $((นิพจน์)) และ ((นิพจน์)) ซึ่งง่ายกว่าและมีการทำงาน ที่ครอบคลุมกว่า แต่จำเป็นต้องเรียนรู้สำหรับการอ่าน Shell script รุ่นเก่า $((...)), ((...)) และ expr นิพจน์ใช้งานได้เฉพาะกับจำนวนเต็ม โดยสามารถดำเนินการดังนี้ - คณิตศาสตร์จำนวนเต็ม คือ +, -, *, /, % # / integer division, % modulus - เปรียบเทียบ คือ =, !=, >, >=, <, <= - ตรรกะ คือ |, & สำหรับ bc (basic calculator) สามารถใช้งานได้เช่นเดียวกับนิพจน์คณิตศาสตร์ในภาษา C