Lect07 เนื้อหาต่อเนื่องจากสัปดาห์ที่แล้ว และนิพจน์ปรกติ (Regular Expression) จากเนื้อหาในสัปดาห์ที่แล้ว ยังมีเรื่องของ pipeline อีกหลายเรื่องที่ต้องทำความเข้าใจ ๑. การสื่อสารระหว่างโพรเซส - Interprocess Communication (IPC) --------------------------------------------------------- เครื่องคอมพิวเตอร์ที่ใช้ระบบปฏิบัติการ Unix ประกอบด้วยเครื่องคอมพิวเตอร์ (host), แกนกลางของระบบปฏิบัติการ (kernel), และโพรเซสที่กำลังทำงานจำนวนอยู่จำนวนหนึ่ง ระบบปฏิบัติการมีหน้าที่สร้างสิ่งแวดล้อมที่เหมาะสมและเอื้ออำนวยต่อการทำงานของโพรเซส เช่น การจัดหน่วยความจำเสมือน (Virtual memory), การจัดสรรทรัพยากรต่างๆ ของระบบให้แก่โพรเซสตามที่ต้องการ และการจัดให้มีช่องทางสื่อสารระหว่างโพรเซส ซึ่งนิยมเรียกว่าทับศัพท์ว่า Interprocess communication หรือเรียกย่อว่า IPC โพรเซสเป็นหน่วยพื้นฐานที่ทำหน้าที่ "compute" (คำนวณ) และจัดการทรัพยากร ในกรณีที่โพรเซสตั้งแต่สองโพรเซสขึ้นไปต้องใช้ทรัพยากรร่วมกัน ต้องมีการติดต่อประสานจังหวะการใช้ทรัพยากร เพื่อให้การทำงานให้เป็นไปโดยราบรื่นไม่ขัดแย้งกัน การสื่อสารระหว่างโพรเซสโดยทั่วไปอยู่ในรูปของข่าวสาร (message) ซึ่งเป็นโครงสร้างข้อมูลที่มีความหมายเฉพาะในการทำงาน การสื่อสารระหว่างโพรเซสที่ง่ายที่สุด "แฟ้ม" โดยโพรเซส "ผู้ส่ง" เขียนข่าวสารลงในแฟ้ม จากนั้นโพรเซส "ผู้รับ" อ่านข่าวสารนั้นจากแฟ้ม ระบบนี้ใช้งานได้ดีในกรณีที่ผู้ส่ง "เขียน" ข่าวสารที่ต้องการเสร็จ ก่อนที่ผู้รับจะเริ่ม "อ่าน" แต่เป็นวิธีการที่ไม่ควรใช้กับโพรเซสที่ทำงานไปพ้อมกัน เพราะ "ผู้รับ" ไม่มีวิธีการที่จะรู้ว่าข่าวสารจากผู้ส่งหมดแล้วหรือยัง นอกจากผู้รับจะคอยตรวจสอบแฟ้มที่ใช้ร่วมกันว่ามีขนาดคงที่แล้วหรือยัง แต่ก็จะมีคำถามตามมาอีกว่าผู้รับจะต้องตรวจสอบนานเท่าใด จึงจะแน่ใจว่าผู้ส่งไม่ได้อยู่ในสถานะ Suspended แต่เสร็จสิ้นการส่งและปิดแฟ้มแล้ว ด้วยเหตุนี้การสื่อสารผ่านระหว่างโพรเซสผ่านแฟ้มจึงเป็นวิธีที่มีข้อจำกัดอยู่มาก จึงจำเป็นต้องหาวิธีการที่ เหมาะสมและมีประสิทธิภาพดีกว่า ในปัจจุบันการสื่อสารระหว่างโพรเซสของระบบ ปฏิบัติการ Unix มี 6 แบบ คือ • Pipes เป็นช่องทางสื่อสารระหว่างโพรเซสที่อยู่ภายในเครื่องเดียวกัน แบ่งออกเป็น ∘ Half-duplex pipes ใช้สื่อสารระหว่างโพรเซสแม่-โพรเซสลูก และ/หรือ โพรเซสลูกที่เกิดจากโพรเซส แม่เดียวกันเป็นการสื่อสารที่มีพื้นฐานจาก standard input และ standard output ∘ FIFOs หรือ named pipe ใช้สื่อสารระหว่างโพรเซสที่ไม่จไเป็นต้องมีความสัมพันธ์กัน โดยช่องทางสื่อ สารเป็นส่วนหนึ่งของระบบแฟ้ม • Signals เป็นสัญญาณที่โพรเซสหนึ่งส่งไปยังอีกโพรเซสหนึ่งเพื่อควบคุมการทำงาน หรือประสานจังหวะการ ทำงาน ไม่สามารถส่งข้อมูลได้ • Semaphores เป็นตัวแปรหรือโครงสร้างข้อมูลอย่างง่าย สำหรับใช้ในการควบคุมการเข้าถึงทรัพยากรที่ โพรเซสใช้งานร่วมกัน • Message queues เป็นช่องทางสื่อสารที่ทำงานในลักษณะคิว สำหรับโพรเซสเขียน/อ่าน เพื่อสื่อสารระหว่าง โพรเซสที่ไม่มีความสัมพันธ์กัน มีที่มาจาก Unix System V ของบริษัท AT&T • Shared memory segments เป็นหน่วยความจำที่โพรเซสใช้งานร่วมกัน มีที่มาจาก Unix System V ของ บริษัท AT&T • Networking sockets เป็นช่องทางสื่อสารระหว่างโพรเซสที่อยู่ต่างเครื่องกัน (และเครื่องเดียวกัน) ผ่านระบบ เครือข่าย มีที่มาจากระบบปฏิบัติการ Unix จากมหาวิทยาลัยแคลิฟอร์เนียที่เบิร์กลีย์ หรือ BSD Unix Unix pipeline ------------ การสื่อสารระหว่างโพรเซสในระดับเชลล์ เป็น pipe ธรรมดา สำหรับส่งผ่านข้อมูลระหว่างโพรเซสลูกที่เชลล์สร้างขึ้นจากคำสั่งของผู้ใช้ โดยมีลำดับการทำงานจากซ้ายไปขวา เช่นการใช้คำสั่ง head และ tail ใน pipeline เพื่อแสดงข้อมูลบรรทัดที่ 25 ถึง 75 ในแฟ้ม myfile $ cat myfile | head -75 | tail -50 cat ส่งข้อมูล "ทั้งหมด" ของแฟ้ม myfile ลงสู่ pipeline, คำสั่ง head "กรอง" เอาไว้บรรทัดเฉพาะ 75 บรรทัดแรก และส่งข้อมูล 75 บรรทัดนี้ให้คำสั่ง tail ซึ่งจะกรองเอาไว้เฉพาะ 50 บรรทัดท้ายสุด ประะเด็นที่สำตัญตือ tail ได้รับข้อมูลเพียง 75 บรรทัดเท่านั้น ไม่ใช่ทั้งแฟ้ม โปรแกรมหรือคำสั่งที่จะนำมาใช้ใน pipeline เป็นโปรแกรมที่เรียกว่า filter โปรแกรม filter ------------- คำสั่งที่จะนำมาใช้ใน Unix pipeline ต้องเป็นโปรแกรมที่ทำงานได้กับสายอักขระหรือข้อความ โดยรับข้อมูลชนิดตัวอักขระเข้าทาง standard input ประมวลผล และส่งผลลัพธ์เป็นข้อมูลชยิดตัวอักขระออกทาง standard output ตัวอย่างของโปรแกรมที่เป็น filter เช่น awk, cat, comm, csplit, cut, diff, expand, fold, grep, head, join, less, more, paste, sed, sort, spell, tr, unexpand, uniq และ wc เป็นต้น รายละเอียดของโปรแกรมเหล่านี้ ศึกษาได้จาก Online manual page การเลิกการทำงานของ pipeline กลางคัน -------------------------------- ตัวอย่างเช่น การใช้คำสั่ง more เพื่อควบคุมให้มีการแสดงผลครั้งละหนึ่งหน้าจอ เช่น การแสดงผลของไดเรกทอรี /etc ครั้งละหน้าจอ $ ls -l /etc | more more - file perusal filter for crt viewing ("โปรแกรมดัวกรอง" สำหรับอ่านแฟ้มทางจอภาพ) ยังคงใช้คำว่า crt - cathode ray tube - หลอดรังสีคาโธดซึ่งหลอดสุญญากาศ ปัจจุบันเป็น LCD (Liquid Crystal Display), และ LED (Light Emitting Diode) หมดแล้ว เมื่อผู้ใช้กดแป้น q (quit) เพื่อเลิกการทำงานของโปรแกรม more ผลลัพธ์ที่ค้างอยู่ของ ls ไปไหน? คำตอบอยู่ในวิธีการที่โพรเซสใน pipeline ติดต่อสื่อสารกัน เมื่อ kernel สร้างโพรเซส, file descriptor ของแต่ละโพรเซส (standard input, standard output และ standard error) จะได้รับการจัดสรรหน่วยความจำสำหรับรับ/ส่งข้อมูล เมือมีการสร้าง pipeline ข้อมูลจาก output buffer ของ ls จะถูกคัดลอกลงใน input buffer ของคำสั่ง more, เมื่อคำสั่ง more เลิกทำงาน ระบบปฏิบัติการจะยกเลิกการทำงานของคำสั่ง ls ด้วย เพราะไม่สามารถคัดลอก output ของ ls ได้ -- เมื่อโพรเซส "เขียน" ข้อมูลลงใน pipe ที่ไม่มีผู้อ่าน โพรเซสจะได้รับสัญญาณ SIGPIPE SIGPIPE 13 Term Broken pipe: write to pipe with no readers Term Default action is to terminate the process ความแตกต่างระหว่างการเปลี่ยนทิศทางและ pipe ผู้เริ่มต้นมักจะสับสนระหว่างสัญลักษณ์เปลี่ยนทิศทาง < และ > กับ | ความแตกต่างที่สำคัญคือ i/o redirection เชื่อมโพรเซสเข้ากับ "แฟ้ม", pipe เชื่อมโพรเซสหนึ่งเข้ากับอีก "โพรเซส" หนึ่ง ตัวอย่าง pipeline สำหรับยกเลิกการทำงานของโพรเซสจากชื่อของโปรแกรม (สัปดาห์ที่แล้ว) คือ $ ps -ef | grep top | grep -v grep | awk '{print $2}' | xargs kill -9 ยังมีรายละเอียดเพิ่มเติมของคำสั่ง และการทำงานของ pipeline ดังนี้ ๒.๑ คำสั่ง ps - รายงานสถานะของโพรเซส --------------------------------- ps - report a snapshot of the current processes. รายงานสถานะของโพรเซสที่กำลังทำงานอยู่ในปัจจุบัน (เฉพาะในขณะที่มีการใช้คำสั่ง ps) เนื่องจากคำสั่ง ps (process status) เป็นคำสั่งที่สำคัญ และมีตัวเลือก (option) จำนวนมาก เพื่อดูสถานะของโพรเซสในรูปแบบต่างๆ ประกอบกับระบบปฏิบัติการ Unix มีการพัฒนาควบคู่กันไป ระหว่างระบบปฏิบัติการ Unix ของห้องปฏิบัติการวิจัยเบลล์ เรียกว่า AT&T Unix และระบบปฏิบัติการ Unix ของมาหวิทยาลัยแคลิฟอร์เนีย เบิร์กลีย์ (University of California, Berkeley) เรียกว่า Berkeley Software Distribution (BSD Unix) ทำให้มีการเลือกใช้สัญลักษณ์ที่ต่างกันแทน option เดียวกัน ทำให้เกิดความสับสนแก่ผู้เริ่มต้นไม่น้อย ตัวอย่างที่ชัดเจนคือ ตัวเลือกสำหรับขอดูโพรเซสทั้งหมดในระบบ มีการใช้งานสองรูปแบบดังนี้ รูปแบบมาตรฐาน: ps -ef BSD Unix : ps aux # มีรายละเอียดมากกว่า สำหรับระบบปฏิบัติการ Linux สนับสนุนการใช้งานตัวเลือกทั้งสองแบบ และเนื่องจากตัวเลือกของ BSD Unix ไม่ได้ขึ้นต้นด้วยเครื่องหมายขึด ทำให้พิมพ์ผิดบ่อยๆ จึงกำหนดให้สามารถใช้งานได้ทั้ง ps aux และ ps -aux ตัวเลือก -e แสดงโพรเซสทั้งหมดที่มีในระบบ -f แสดงรายละเอียดของโพรเซสในแบบ full format listing -H แสดงโพรเซสตามลำดับชั้น (hierachy of processes) สำหรับดูความสัมพันธ์ระหว่างโพรเซส ตัวอย่างการใช้งานเช่น (ความหมายของแต่ละฟิลด์ อ่านจากคำสั่ง ps ในแฟ้ม Unix02.txt) $ ps PID TTY TIME CMD 10994 pts/4 00:00:00 bash 11141 pts/4 00:00:00 ps $ ps -H PID TTY TIME CMD 10994 pts/4 00:00:00 bash 11008 pts/4 00:00:00 ps # มีการย่อหน้าโพรเซสลูก $ ps -f UID PID PPID C STIME TTY TIME CMD jira 10994 10993 0 14:28 pts/4 00:00:00 -bash jira 11017 10994 0 14:28 pts/4 00:00:00 ps -f ๒.๒ เปลี่ยนแทน awk ในบรรทัดคำสั่งด้วย cut $ ps -ef | grep top | grep -v grep | awk '{print $2}' | xargs kill -9 จากรายละเอียดของคำสั่ง cut ในสัปดาห์ที่ 5 จะเห็นได้ว่า cut สามารถแยกฟิลด์ได้ โดยถือว่าอักขระคั่นที่ใช้คั่นระหว่างฟิลด์ (delimiter) เป็น tab หากเป็นอักขระอื่น ให้กำนดอักขระนั้นด้วยตัวเลือก -d และกำหนดลำดับของฟิลด์ที่ต้องการด้วยตัวเลือก -f ตามรูปแบบดังนี้ cut -d -f อย่างไรก็ดี ข้อจำกัดของ cut ในการแยกฟิลด์คือ อักขระคั่นระหว่างฟิลด์ที่ระบุในตัวเลือก -d มีได้เพียงตัวเดียว หากกำหนดเกินจะได้รับคำเตือน เช่น cut: the delimiter must be a single character # อักขระคั่นต้องมีเพียงตัวเดียว จากผลลัพธ์ที่ได้จาก "ps -ef | grep top | grep -v grep" เช่น jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 มีอักขระคั่นเป็น "วรรค" และเมื่อกำหนด -d ' ', หมายเลขโพรเซส (pid) จึงกลายเป็นฟิลด์ที่ 6 แทนที่จะเป็นฟิลด์ที่ 2 (ฟิลด์ที่ 2 คือ "วรรค" ที่อยู่ถัด "วรรค" ตัวแรกไป) จึงไม่สามารถใช้งานได้ หากใช้ตัวเลือก -c เพื่อกำหนดตำแหน่งอักขระเริ่มต้นและสิ้นสุด จะมีปัญหาเพราะจำนวนหลักของ pid ไม่เท่ากัน จึงต้องใช้คำสั่ง tr (transliterate) เพื่อ "บีบ" วรรคหลายตัวให้เหลือตัวเดียว จะได้บรรทัดข้อมูลที่แต่ละฟิลด์คั่นด้วยวรรคเพียงตัวเดียว แล้วจึงตัดฟิลด์ด้วย cut -f คำสั่ง tr (translate or delete character) ใช้สำหรับเปลี่ยนจากอักขระชุดหนึ่งเป็นอีกชุดหนึ่ง, ลบอักขระที่กำหนด (delete, -d) , หรือ "บีบ" เพื่อลดอักขระที่ซ้ำกันให้เหลือเพียงตัวเดียว (squeeze, -s) รายละเอียดจะได้กล่าวถึงในสัปดาห์ต่อไป สำหรับในกรณีนี้ เป็นการใช้ตัวเลือก -s ของคำสั่ง tr เพื่อแทนที่อักขระที่ซ้ำกันด้วยอักขระนั้นเพียงตัวเดียว หรือ "บีบ" อักขระที่ซ้ำกันให้เหลือเพียงตัวเดียว มีรูปแบบการใช้งานดังนี้ tr -s อักขระที่ต้องการดำเนินการ # ในที่นี้คือวรรค, ' ' ลำดับงานจากเดิมจะเปลี่ยนไปดังนี้ # เลือกเฉพาะบรรทัดที่มีคำว่า "top" $ ps -ef | grep top # เลือกเฉพาะบรรทัดที่มีคำว่า "top" jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 jira 27760 23407 0 06:47 pts/1 00:00:00 grep top # เลือกเฉพาะบรรทัดที่ไม่มีคำว่า "grep" -- กำจัดบรรทัดอื่นที่ไม่เกี่ยวออกไป $ ps -ef | grep top | grep -v grep jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 # บีบ "วรรค" ที่อาจมีหลายตัวที่อยู่ระหว่างฟิลด์ให้เหลือตัวเดียว $ ps -ef | grep top | grep -v grep | tr -s ' ' jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 # ตัดมาเฉพาะฟิลด์ที่สอง (pid - หมายเลขโพรเซส) จากบรรทัด หรือเรคอร์ด ที่มีอักขระคั่นเป็นวรรค $ ps -ef | grep top | grep -v grep | tr -s ' ' | cut -d' ' -f2 23422 # ทดลองส่งหมายเลขโพรเซสที่ตัดได้เป็นอาร์กิวเมนต์ให้คำสั่ง echo เพื่อดูผล # ต้องมีวรรคระหว่าง tr -s และ ' ' แต่ไม่จำเป็นต้องมี ระหว่าง cut -d' ' -- ดูหมายเหตุ $ ps -ef | grep top | grep -v grep | tr -s ' ' | cut -d' ' -f2 | xargs echo 23422 # ส่งหมายเลขโพรเซสที่ตัดได้เป็นอาร์กิวเมนต์ให้คำสั่ง kill $ ps -ef | grep top | grep -v grep | tr -s ' ' | cut -d' ' -f2 | xargs -9 kill คำสั่ง kill ใช้สำหรับส่งสัญญาณ (signal) หมายเลข 9 ซึ่งมีชื่อสัญญาณเป็น SIGKILL ไปยังโพรเซสที่ระบุ เมื่อโพรเซสได้รับสัญญาณนี้แล้วจะจบการทำงานของตัวเอง (Terminate) -- หรือเป็นสัญญาณให้ทำลายตัวเอง หมายเหตุ: เพราะเหตุใดจึงต้องเว้นวรรคระหว่าง tr -s และ ' ' แต่ไม่จำเป็นต้องเว้นวรรคระหว่าง cut -d' ' วิเคราะห์คำสั่ง tr: tr -s ' ' คำสั่ง ตัวเลือก อักขระที่ต้องการให้ดำเนินการ # จำเป็นต้องใช้วรรคคั่นระหว่างอาร์กิวเมนต์แต่ละตัว วิเคราะห์คำสั่ง cut: cut -d' ' ตัวเลือก -d ใช้ระบุอักขระที่ใช้แบ่งฟิลด์ ด้วยเหตุนี้ ' ' จึงเป็นอาร์กิวเมนต์ของตัวเลือก (option) ซึ่งจะมีวรรคคั่นหรือไม่ก็ได้ ๓. คำสั่ง awk ----------- awk เป็นภาษา script ตั้งชื่อขึ้นตามผู้เขียนโปรแกรมนี้สามคน คือ Alfred Aho, Peter Weinberger, และ Brian Kernighan โดยคำศัพท์แล้ว awk เป็นคำคุณศัพท์ แปลว่า งุ่มง่าม หรือ เก้กัง ในระบบปฏิบัติการ Linux โปรแกรม awk ที่ใช้งานเป็น symbolic link ไปยังโปรแกร gawk ซึ่งเป็นโปรแกรมที่พัฒนาขึ้นภายใต้โครงการ GNU เรียกว่า GNU awk ซึ่งนอกจากจะทำงานได้เช่นเดียวกับโปรแกรมเดิม ชื่อใหม่ยังมีความหมายคล้ายคือ gawk แปลว่า เงอะงะ ถึงแม้ว่า awk จะเป็นภาษาตรวจหารูปแบบและประมวลผลข้อความที่สมบูรณ์ในตัวเอง ในบทความนี้จะกล่าวถึงเฉพาะเมื่อนำมาใช้งานเป็น filter ใน Unix pipeline เท่านั้น โดยใช้ในการแยกฟิลด์ หรือใช้จัดรูปแบบแสดงผลของผลลัพธ์ที่ได้จากคำสั่งอื่น เช่น $ date Tue Feb 13 21:33:58 +07 2018 $ date | awk '{print $2 " " $6}' Feb 2018 แยกฟิลด์ที่ 2 (ชื่อเดือน) และฟิลด์ที่ 6 (ปี ค.ศ.) ออกวันเวลาซึ่งเป็นผลลัพธ์ของคำสั่ง date และแสดงฟิลด์ทั้งสองโดยมีวรรคคั่นหนึ่งวรรค รูปแบบ "อย่างง่าย" ของ awk ในการใช้เป็น filter โดยรับข้อมูลเข้าจาก standard input และส่งผลลัพธ์ทาง standard outputเป็นดังนี้ awk 'pattern {action}' เมื่อ pattern เป็นรูปแบบของ "เงื่อนไข" สำหรับกรองข้อมูลในกรณีที่มีหลายบรรทัด action เป็นการดำเนินการ ในที่นี้จะใช้เฉพาะคำสั่ง print สำหรับแสดงผลฟิลด์ (นอกจากนี้สามารถใช้คำสั่งของ Unix ได้อีกหลายคำสั่ง) ๔. คำสั่ง grep - เพิ่มเติมจากที่มีอยู่แล้วในแฟ่ม Unix02.txt ------------------------------------------------ grep และ egrep - พิมพ์บรรทัดที่มีข้อความตรงกับรูปแบบ (pattern) ที่กำหนด (print lines matching a pattern) • รูปแบบการใช้งาน grep [option] <รูปแบบ> [<ชื่อเส้นทางไปยังแฟ้ม>...] เมื่อ <รูปแบบ> เป็น นิพจน์ปรกติสำหรับค้นหาสายอักขระ <ชื่อแฟ้ม> หากไม่กำหนด ให้รับข้อมูลจาก standard input และหากใช้เป็น - กำหนดให้ำทำงานกับ standard input และ standard output • คำอธิบาย รูปแบบที่ใช้งานกับโปรแกรม grep เป็นนิพจน์ปรกติพื้นฐาน (Basic Regular Expression - BRE ) หากต้องการใช้งานร่วมกับนิพจน์ปรกติ ส่วนขยาย (Extended Regular Expression - ERE) ต้องกำหนดตัวเลือก -E หรือใช้โปรแกรม egrep อักขระพิเศษของ BRE ประกอบด้วย $, *, ., [, \, และ ^ อักขระพิเศษของ ERE ประกอบด้วย $, *, ., [, \, ^, (, ), +, ?, {, และ | นิพจน์ปรกติที่ใช้งานในปัจจุบัน สร้างขึ้นโดย Ken Thompson จากหลักการของภาษา Regular Language สำหรับค้นหาสายอักขระในโปรแกรม editor เช่น QED และ ed โดยมีรูปแบบการใช้งานเป็น g//p จึงเรียกย่อได้เป็น g/re/p ซึ่งต่อมากลายเป็นชื่อของโปรแกรม grep รูปแบบของการใช้งานนิพจน์ปรกติในบทความนี้ เป็นรูปแบบที่ใช้ในโปรแกรม grep และเป็นการใช้งานอย่างง่าย การใช้งานที่ซับซ้อนและมีประสิทธิภาพสูงสามารถค้นคว้าได้จาก web เช่น https://www.regular-expressions.info/ หรือจากหนังสือเช่น Friedl, J. (2006) Mastering Regular Expression, 3rd Ed., O'Reilly. ในปัจจุบัน โปรแกรมประยุกต์และภาษาสำหรับเขียนโปรแกรมส่วนใหญ่สนับสนุนการใช้งานนิพจน์ปรกติ มีหลักการทำงานเดียวกัน แต่อาจมีรายละเอียดแตกต่างกันออกไป เมื่อจะนำไปใช้กับโปรแกรมหรือภาษาอื่นขอให้ตรวจสอบจากคู่มือของโปรแกรมนั้นๆ ก่อนใช้งานจริง การใช้งานพื้นฐาน ------------- การใช้งานแบบง่ายที่สุดของ grep คือการค้นหาสายอักขระที่กำหนด (literal pattern) ในเนื้อหาของแฟ้มอักขระ (text file) และพิมพ์ข้อความทุกบรรทัดที่ปรากฏคำค้นนั้น ตัวเลือก (option) : -i, -v, และ -n ------------------------------- ค่าโดยปริยายของ grep คือการหาสายอักขระที่เหมือนกับรูปแบบที่กำหนดทุกประการ ทำให้อักษรตัวเล็กอละอักษรตัวใหญ่ในภาษาอังกฤษต่างกัน หากต้องการให้ grep หาสายอักขระโดยไม่สนใจว่าเป็นอักษรตัวเล็กหรือตัวใหญ่ ทำด้วยตัวเลือก -i (ignore case) หากต้องการให้พิมพ์บรรทัดข้อความทุกบรรทัดที่ "ไม่มี" รูปแบบที่กำหนด ทำด้วยตัวเลือก -v (invert match) ในกรณีที่เป็นการค้นหาข้อมูลจากแฟ้ม บางครั้งการรู้หมายเลขของบรรทัดที่มีข้อความตรงกับรูปแบบที่กำหนด อาจเป็นประโยชน์ในการทำงานหลายอย่าง ทำด้วยตัวเลือก -n (line-number) ตัวเลือกเหล่านี้ของ grep สามารถนำมาผสมผสานกันได้ เช่น -vn การจับคู่ตามตัวอักษร (Literal match) ------------------------------ เป็นการใช้งานที่มีการกำหนด รูปแบบ เป็น "คำ" เพื่อหาข้อความที่ตรงกันทุกตัวอักษร หรือตรงกันตัวต่อตัว เช่น การค้นคำว่า "shell" ในแฟ้ม myfile $ grep "shell" myfile อักขระพิเศษที่ใช้กำหนดตำแหน่ง (Anchor Matches) ------------------------------------- anchor คิออักขระพิเศษที่ใช้ในการระบุตำแหน่งในบรรทัด มีสองตัวคือ "^" กำหนดตำแหน่งที่ต้นบรรทัด และ "$" กำหนดตำแหน่งที่ท้ายบรรทัด • อักขระพิเศษแสดงตำแหน่งต้นบรรทัด "^" ( caret) เมื่อใช้นำหน้ารูปแบบใด หมายถึงสายอักขระที่ตรงกับรูปแบบนั้น "ต้อง" เกิดที่ต้นบรรทัด เช่น "^GNU" หมายถึงคำว่า GNU เฉพาะที่ต้นบรรทัด หากเกิดในตำแหน่งอื่นของบรรทัด จะไม่ตรงกัน เช่น GNU operating system. # ตรงกัน เพราะ GNU เป็นคำแรก The GNU project # ไม่ตรง ถึงแม้จะมคำว่า GNU ในบรรทัดแต่ไม่ใช่คำแรก ลองพิจารณาประโยคต่อไปนี้ ซึ่งตรงกับรูปแบบ "^GNU" GNU is a recursive acronym for "GNU's Not Unix!" ผู้เริ่มต้นใหม่อาจไม่แน่ใจว่า ตำแหน่งที่ตรงกันอยู่ตรงไหน เพราะมีคำว่า "GNU" สองตำแหน่ง หากผลลัพธ์เป็นข้อความบรรทัดเดียว สามารถกำหนดให้ grep แสดงตำแหน่งที่ตรงกันเป็น byte-offset ได้โดยใช้ตัวเลือก -b เช่น # กำหนดให้รับข้อมูลจากแป้นพิมพ์ ถ้าพบรูปแบบที่กำหนด grep จะแสดงประโยคนั้น หากไม่พบจะไม่มีการแสดง # กิจกรรมนี้จะดำเนินเช่นนี้ไปเรื่อยๆ จนกว่าผู้ใช้จะกดแป้น ctrl-D ซึ่งเป็นรหัสปิดแฟ้มด้วย $ egrep -b '^GNU' # หาคำ "GNU" และให้แสดง byte-offset ของคำที่ด้วย GNU is a recursive acronym for "GNU's Not Unix! # ผู้ใช้ป้อนประโยคทางแป้นพิมพ์ 0:GNU is a recursive acronym for "GNU's Not Unix! # grep แจ้งว่าการตรงกันอยู่ที่ตำแหน่ง 0 - ต้นบรรทัด ^D # เลิกการป้อนข้อมูล - รหัสปิดแฟ้ใ • อักขระพิเศษแสดงตำแหน่งท้ายบรรทัด "$" (dollar sign) เมื่อใช้ต่อท้ายรูปแบบใด หมายถึงสายอักขระที่ตรงกับรูปแบบนั้น "ต้อง" เกิดที่ท้ายสุดของบรรทัด เช่น การหาคำว่า "and" ที่ท้ายรรทัด กำหนดด้วย "and$" อักขระพิเศษที่ใช้แทนอักขระใดๆ --------------------- อักขระพิเศษ "." (period) ใช้แทนอักขระใดๆ จำนวนหนึ่งตัว เช่นกำหนดนิพจน์ปรกติเป็น "..cept" หมายถึง มีอักขระใดๆ จำนวนสองตัวนำหน้าคำ "cept" เช่น "accept", "except" และ "incept" เป็นต้น character class --------------- การกำหนดอักขระจำนวนหนึ่งไว้ในวงเล็บก้ามปู เรียกว่า character class เป็นตัวแทนของอักขระจำนวนหนึ่งตัวในกลุ่มนั้น เช่นต้องการบรรทัดที่มีคำว่า "two" หรือ "too" กำหนดเป็นนิพจน์ปรกติได้ดังนี้ "t[ow]o" สีเทาในภาษาอังกฤษเขียนได้สองแบบคือ gray และ grey กล่าวคือ gray เป็นคำที่นิยมใช้ในอเมริกา (American English) ส่วน grey เป็นคำที่นิยมใช้ในอังกฤษ (UK English) การกำหนดนิพจน์ปรกติให้ครอบคลุมคำทั้งสองนี้ ทำได้โดยการกำหนดให้ a และ e เป็น character class คือ [ae] และเนื่องจาก character class มีลักษณะเป็นเซต ลำดับของสมาชิกไม่มีความสำคัญ ดังนั้น [ae] = [ea] จึงกำหนดนิพจน์ปรกติได้เป็น 'gr[ae]y' character class, [...] อาจมีอักขระในกลุ่มหลายตัว แต่ใช้เป็นตัวแทนของอักขระเพียงตัวเดียว ดังนั้นนิพจน์ปรกติ 'gr[ae]y' จึงไม่สามารถจับคู่ได้กับ graey หรือ greay ผู้ใช้สามารถระบุช่วงของอักขระใน character class ได้ โดยใช้เครื่องหมายขีด (-) เช่น [0-9] แทนตัวเลขตัวใดตัวหนึงในช่วง 0-9, หรือ [0-9A-Fa-f] แทนเลขฐานสิบหกหนึ่งหลักโดยไม่สนใจว่าเป็นอักษรตัวเล็กหรือตัวใหญ่, หรือ [0-9a-fA-FX] แทนเลขฐานสิบหกหนึ่งหลัก หรืออักษร X หนึ่งตัว การกำหนดพิสัยแบบนี้ต้องแน่ใจว่ารหัสของอักขระที่กำหนดต้องต่อเนื่องกัน สำหรับรหัส ASCII (มาตรฐาน ISO 646-1983) เป็นดังนี้ 0-9 มีรหัสต่อเนื่องกัน ('0' = 48, '1' = 49, ..., '9' = 57), A-Z มีรหัสต่อเนื่องกัน ('A'= 65, 'B' = 66, ... , 'Z'= 90), และ a-z มีรหัสต่อเนื่องกัน ('a'= 97, 'b' = 98, ... , 'z'= 122) • นิเสธของ character class นิเสธของ character class (หรือเรียกให้ตรงความหมายคือ complement ของ character set) ทำโดยเพิ่ม caret, ^ เป็นอักขระตัวแรก ซึ่งจะเป็นตัวแทนของอักขระใดๆ ที่ไม่ใช่อักขระใน class นั้น รวมถึงรหัสขึ้นบรรทัดใหม่ด้วย (ซึ่งต่างจาก dot, ., ที่ใช้แทนอักขระใดๆ จำนวนหนึ่งตัวไม่รวมรหัสขึ้นบรรทัดใหม่) หากไม่ต้องการให้รวมรหัสขึ้นบรรทัดใหม่ต้องรวมรหัสขึ้นบรรทัดใหม่ไว้ในกลุ่มด้วยคือ คือ \r\n ด้วย เช่น [^0-9\r\n] ซึ่งเป็นตัวแทนของอักขระใดๆ จำนวนหนึ่งตัวที่ไม่ใช่ตัวเลขหรือ line break ประเด็นที่สำคัญและก่อให้เกิดความเข้าใจผิดได้ง่ายคือ เมื่อนิเสธแล้วยังเป็นตัวแทนของอักขระ 1 ตัวที่ไม่ได้อยู่ใน class นั้น เช่น 'q[^u] ไม่ได้หมายความว่า "q ที่ไม่ได้ตามด้วย u" แต่หมายถึง "q ที่ตามด้วยอักขระอื่นที่ไม่ใช่ u" นิพจน์ "q[^u]" ดูเหมือนจะตรงกับ Iraq ในประโยค Iraq is a country ซึ่งในความเป็นจริง เป็นการจับคู่ระหว่าง [^u] กับ วรรค หลัง q ตัวอย่างคำของอื่นที่ตรงกับนิพจน์นี้เช่น qantus, iraqi, shu qi, และ qin shi haung เป็นต้น • อักขระพิเศษใน character class ภายใน character class มีอักขระพิเศษที่มีความหมายมีเฉพาะ วงเล็บก้ามปูปิด (]), backslash, (\), caret (^) และขีด(-) เท่านั้น อักขระพิเศษอื่นไม่มีความหมายพิเศษและไม่จำเป็นต้องต้องกำกับด้วย "\" เช่นต้องการค้นเครื่องหมาย + หรือ * กำหนดเป็น [+*] ได้ ไม่ควร quote เป็น [\+\*] เพราะทำให้อ่านยากและสับสนได้ง่าย และสำหรับอักขระพิเศษของ character class คือ (], \,^, -) ที่ต้องการนำมาใช้เป็นอักขระปรกติ ทำได้โดยการ escape หรือวางในตำแหน่งที่ไม่สามารถเป็นอักขระพิเศษได้ (โดยปกติคืออักขระตัวท้ายสุด) ในกรณีที่ไม่สามารถทำได้ต้องกำกับด้วย "\" • Character Class ตามมาตรฐาน POSIX [:digit:] [0-9] [:alnum:] [A-Za-z0-9] [:alpha:] [A-Za-z] [:blank:] [] [:xdigit:] [0-9A-Fa-f] - Hexadecimal notation [:punct:] . , " ' ? ! ; : # $ % & ( ) * + - / < > = @ [ ] \ ^ _ { } | ~ [:print:] Any printable character [:space:] Any white space - [] [:graph:] Any printable characters, exclude [:upper:] [A-Z] [:lower:] [a-z] [:control:] control characters NL CR LF TAB VT FF NUL SOH STX EXT EOT ENQ ACK SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC IS1 IS2 IS3 IS4 DEL เมื่อนำมาใช้งานใน grep กำหนดให้เพิ่มวง้ล็บก้ามปูเพิ่มขึ้นอีกชั้นหนึ่ง เช่น [[:alnum:]] เพื่อให้สามารถ "ผสม" อักขระอื่นเข้าไปในกลุ่มได้ด้วย เช่น เพิ่มอักษร a - d เข้ากับกลุ่มตัวเลข เป็น [[:digit:]a-d] การเกิดซ้ำ (Repetition) -------------------- อักขระสำหรับการเกิดซ้ำ มีสามตัวคือ ?, *, และ + โดยมีความหมายดังนี้ "?" (question mark) ใช้แทนอักขระใดๆ หนึ่งตัว ซึ่งอักขระนี้อาจจะมีหรือไม่มีก็ได้ หรือใช้แทนอักขระ 0 หรือ 1 ตัว (zero or one) เช่น "computers?" หมายถึงคำว่า computer (เอกพจน์) หรือ computers (หูพจน์) นั่นคือ s? หมายถึงจะมี s หรือไม่ก็ได้ หากมีให้มีได้เพียงตัวเดียว อาจประยุกต์ใช้กับคำว่า "สี" ซึ่งสะกดได้สองแบบคือ color และ colour เขียนเป็นนิพจน์ปรกติได้เป็น "colou?r" "*" (asterisk) ใช้แทนการเกิดซ้ำของอักขระที่อยู่ด้านหน้า ซึ่งอักขระนี้อาจจะมีหรือไม่มีก็ได้ ในกรณีที่มีอาจมีได้หลายตัว หรือใช้แทนอักขระ 0 หรือมากกว่า (zero or more) เช่นนิพจน์ "ab*c" ตรงกับสายอักขระ ac, abc, abbc, abbbc, ... "+" (plus) ใช้แทนการเกิดซ้ำของอักขระที่อยู่ด้านหน้า ซึ่งอักขระนี้ต้องมีอย่างน้อยหนึ่งตัว หรืออาจมีได้หลายตัว หรือใช้แทนอักขระ 1 หรือมากกว่า (one or more) เช่นนิพจน์ "ab+c" ตรงกับสายอักขระ abc, abbc, abbbc, ... นอกจากการกำหนดอักขระเป็น 0 หรือ 1 (ด้วย ?), 0 หรือมากกว่า (ด้วย *), และ 1 หรือมากกว่า (ด้วย +) แล้ว ยังสามารถกำหนดจำนวนครั้งของการเกิดซ้ำได้ในวงเล็บปีกกา เช่น {3} หมายถึงการเกิดซ้ำของอักขระ หรือกลุ่มอักขระที่จัดด้วยวงเล็บ จำนวนสามครั้ง มีการใช้งานหลายรูปแบบคือ {n} เกิดซ้ำจำนวน n ครั้ง {n,} เกิดซ้ำจำนวน n ครั้ง หรือมากกว่า {m,n} เกิดซ้ำระหว่าง m ถึง n ครั้ง {,m} เกิดซ้ำไม่เกิน m ครั้ง หมายเหต: 1. * และ + เป็น operator ที่มีประสิทธิภาพมาก แต่ต้องใช้งานด้วยความระมัดระวัง เพราะอาจทำให้เกิดการจับคู่ตัวอักขระมากเกินความจำเป็น เพราะขั้นตอนวิธีที่ใช้กับนิพจน์ปรกติ เป็นขั้นตอนวิธีเชิงละโมบ (greedy algorithm) จพยายามจับคู่เรื่อยไปจนกว่าจะไม่สามารถดำเนินการได้ 2. ? และ * เป็น operator ที่สร้างความสับสนได้ง่าย เพราะเป็นอัขระชุดเดียวกับ wildcard ที่ใช้ในชื่อแฟ้มของเชลล์ เวลาใช้งานต้องดูตำแหน่งให้ดีว่ากำลังพิจารณานิพจน์ปรกติ หรือชื่อแฟ้ม 3. หากกำหนดการเกิดซ้ำของ character class ด้วย ?, * หรือ + เป็นการกำหนดการเกิดซ้ำของอักขระทั้งหมดใน class นั้น (ไม่ใช่ เป็นการเกิดซ้ำของอักขระตัวแรกที่จับคู่ได้ เช่น '[0-9]+' จับคูู่กับ 22, 222, ... ได้เช่นเดียว 34, 456, ... ทางเลือก (Alternation) --------------------- จากที่ผ่านมา character class เป็นตัวแทนของอักขระตัวใดตัวหนึ่งในกลุ่มนั้นเพียงตัวเดียว, ทางเลือกมีลักษณะคล้ายกัน แต่มีขอบเขตที่กว้างกว่า คือเป็นตัวแทนของนิพจน์ย่อยตัวใดตัวหนึ่งจากทั้งหมดที่กำหนดไว้ ตัวอย่างเช่นต้องการหาสัตว์เลี้ยงซึ่งอาจเป็นแมวหรือหมาก็ได้ กำหนดนิพจน์ปรกติเป็น cat|dog ซึ่งเป็นเป็นการกำหนดให้เลือกใช้อักขระที่มีอยู่ทั้งหมดด้านซ้ายมือ หรืออักขระที่มีทั้งหมดด้านขวามือของเครื่องหมาย | ไม่ว่าจะมีจำนวนอักขระแต่ละด้านเป็นจำนวนเท่าใด การจำกัดขอบเขตทำได้โดยการจัดกลุ่มด้วยวงเล็บ เช่น (cat|dog) จำกัดจำนวนอักขระด้านซ้ายและด้านขวาของ | ไว้เพียง 3 ตัวคือ cat และ dog ตามลำดับ นอกจากนี้แล้วผู้ใช้ยังกำหนดจำนวนตัวเลือกได้ตามต้องการ เช่น อาจกำหนดเป็น 4 ทาง เช่น (cat|dog|mouse|fish) กรณีศึกษา ------- (1). ต้องหา HTML tag จึงกำหนดนิพจน์ปรกติดังนี้คือ "<.+>" หมายถึงสายอักขระที่ขึ้นต้นด้วย "<" ตามด้วยอักขระใดตั้งแต่หนึ่งตัวขึ้นไป ".+" และปิดท้ายด้วย ">" แต่เมื่อนำไปใช้จริง ปรากฏว่า นิพจน์ปรกตินี้ครงกับบรรทัด เช่น ... ... ... (1) Page Title ... ... ... (2) ... ... ... (3) บรรทัดที่ (1) และ (3) เป็นไปตามที่คาดหมายไว้ แต่บรรทัดที่ (2) อาจเกินความคาดหมาย ที่เป็นเช่นนี้เพราะ ".+" แทนอัขระใดก็ได้ตั้งแต่หนึ่งตัวขึ้นไป (one or more) และขั้นตอนวิธีที่ใช้กับนิพจน์ปรกติ เป็นขั้นตอนวิธีเชิงละโมบ (greedy algorithm) ทำให้เกิดการจับคู่ถึงอักขระ ">" ที่ท้ายบรรทัด การใช้งาน "+" และ "*" ต้องเข้าใจเรื่องนี้ด้วย (2). นิพจน์ปรกติ "da*y" ตรงกับสายอักขระ dy, day, daay, daaay, ... เนื่องจาก "a*" หมายถึงการเกิดซ้ำของอักษร a ตั้งแต่ 0 ตัวขึ้นไป (zero or more) จึงจับคู่ได้กับ dy ด้วย (3). รูปแบบสากลของวันที่เป็นเดือน วัน ปี และใช้เครื่องหมาย /, -, และ . ในรูป mm/dd/yyyy, mm-dd-yyyy, และ mm.dd.yyyy นิพจน์ปรกติ "[0-9]{2}.[0-9]{2}.[0-9]{4}" สามารถแยกแยะวันที่ได้ทั้งสามแบบเช่น 02/19/2018, 02-19-2018, และ 02.19.2018 แต่ปัญหาคือนิพจน์นี้ตรงกับ 0201902018 ด้วย ปรับให้ชัดเจนขึ้นโดยกำหนด เครื่องหมายวรรคตอนทั้งสามตัวเป็น character classs คือ [-/.] ได้นิพจน์ปรกติใหม่เป็น "[0-9]{2}[-/.][0-9]{2}[-/.][0-9]{4}" ข้อสังเกต: 1. เครืองหมาย . จะเสียความหมายของอักขระพิเศษใน character class จึงไม่ต้อง escape 2. หากเปลี่ยน character class เป็น [/-.] จะตรงกับ 02/19/2018 และ 02.19.2018 แต่ไม่ตรงกับ 02-19-2018 เพราะขั้นตอนวิธีตีความว่า เป็นเซตของอักขระตั้งแต่เครื่องหมาย / จนถึง (-) เครื่องหมาย . ดังนั้นหากมีเครื่องหมาย - ในเซตด้วย ต้องกำหนดไว้เป็นตัวแรก นิพจน์ปรกติใหม่นี้จะตรงกับ 00/00/0000 - 99/99/9999 หากต้องการให้เฉพาะลงไปอาจปรับแต่งนิพจน์ต่อไปเป็น "[0-1][0-9][-/.][0-3][0-9][-/.][0-2][0-9]{3}" แต่ก็ยังตรงกับวันเดือนปีที่อยู้ในย่าน 00/00/0000 - 19/39/2999 ตัวอย่างนี้เป็นตัวอย่างที่ดี ที่แสดงให้เห็นข้อจำกัดของนิพจน์ปรกติ (4). ต้องการนิพจน์ปรกติสำหรับหาตัวเลขที่มีค่าระหว่าง 1000 - 9999 วิเคราะห์งาน: อักขระตัวแรกที่เป็นไปได้คือ 1-9 [1-9] อักขระอีกสามตัวที่เหลือที่เป็นไปได้คือ 0-9 [0-9]{3} ∴ นิพจน์ปรกติคือ "[1-9][0-9]{3}" นิพจน์ปรกตินี้นอกจากจะตรงกับ 1000 - 9999 ตามที่ต้องการแล้ว ยังตรงกับสายอักขระตัวเลขที่มี 1000 - 9999 เป็นสายอักขระย่อย (substring) ด้วย เช่น 10000, 12345678, ... ในกรณีที่รู้สีกสับสน สามารภกำหนดให้ grep แสดงเฉพาะสายอักขระที่ตรงกับนิพจน์ปรกติได้ โดยใช้ตัวเลือก -o เช่น $ egrep -o "[1-9][0-9]{3}" - 10000 # ข้อมูล 1000 # สายอักขระที่ตรงกับนิพจน์ปรกติ 12345678 # ข้อมูล 1234 # สายอักขระ "ชุดแรก" ที่ตรงกับนิพจน์ปรกติ 5678 # สายอักขระ "ชุดที่สอง" ที่ตรงกับนิพจน์ปรกติ +66 (0)38 10 3061 # หมายเลขโทรศัพท์ของคณะ (จากหน้าเว็บ) 3061 # สายอักขระที่ตรงกับนิพจน์ปรกติ ^D หมายเหตุ: เครื่องหมาย - ในบรรทัดคำสั่งตรงตำแหน่งของชื่อแฟ้ม กำหนดให้อ่านข้อมูลจากแป้นพิมพ์ หรือชื่อแฟ้มเป็น stdin ในกรณีที่ต้องการเฉพาะ 1000 - 9999 ที่เป็นตัวเลขอิสระ ไม่เป็นส่วนใดส่วนหนึ่งของสายอักขระอื่น ต้องกำหนดขอบของคำด้วย อักขระพิเศษ \b -- ซึ่งเป็นอักขระพิเศษที่ต้องใช้งานเป็น escape sequence เช่น $ egrep -o "\b[1-9][0-9]{3}\b" - 1234 # ข้อมูล 1234 # ตรงกับนิพจน์ปรกติ 12345678 # ไม่ตรง +66 (0)38 10 3061 # ไม่ตรง ^D หมายเหตุ: \b เป็นอักขระพิเศษในกลุ่ม achor เช่นเดียวกับ ^ และ $ ใช้แทนตำแหน่งที่เรียกว่า "ขอบของคำ" (word boundary) ซึ่งมีความยาวเป็น 0