Lect08 Unix tr command --------------- คำสั่ง: tr - เปลี่ยนแทน หรือลบอักขระ (translate or delete characters) รูปแบบการใช้งาน: tr [ตัวเลือก] [เซตแรก [เซตที่สอง]] คำสั่ง tr รับข้อมูลทาง standard input ปลี่ยนแทน, ลบ หรือ ลดจำนวนอักขระที่ซ้ำกัน ตามที่ผู้ใช้กำหนด แล้วส่งผลลัพธ์ออกทาง standard output โดย "เซตแรก" และ "เซตที่สอง" เป็นเซตของอักขระซึ่งสามารถใช้งานได้โดยตรง หรือใช้เป็น complemt (ส่วนเติมเต็ม) ของเซตนั้น โดยกำหนดด้วยตัวเลือก -c คำสั่ง tr มีรูปแบบการใช้งานพิเศษออกไปจากคำสั่งอื่น เนื่องจากเป็นคำสั่งที่รับข้อมูลจาก standard input ได้เพียงอย่างเดียว ไม่สามารถกำหนดชื่อแฟ้มเป็น input ได้ หากต้องการใช้งานกับแฟ้ม ต้องใช้การเปลี่ยนทิศทาง input/output เช่น $ tr [ตัวเลือก] [<เซตแรก> [<เซตที่สอง>]] < แฟ้มข้อมูลเข้า > แฟ้มข้อมูลออก เนื่องจากรูปแบบการใช้งานเป็นเช่นนี้ ตัวอย่างการใช้งานคำสั่ง tr มีเฉพาะตัวคำสั่ง เช่น tr "abcd" "0123" เมื่อต้องการทดสอบการทำงาน ต้องกำหนด pipeline หรือก่ารเปลี่ยนทิศทางข้อมูลเข้า/ออกเอง เช่น $ echo "abcdefabcdef" | tr "abcd" "0123" # ข้อมูลเข้ามาจาก pipeline $ tr "abcd" "0123' < data.txt # ข้อมูลเข้ามาจากแฟ้ม data.txt หากเป็นบรรทัดคำสั้งที่สมบูรณ์จะนำด้วย prompt, $ การเปลี่ยนแทน (translate) ----------------------- ข้อมูลเข้าที่เป็นอักขระในเซตแรก จะถูกเปลี่ยนแทนด้วยอักขระในเซตที่สองที่มีตำแหน่งตรงกัน เป็นการเปลี่ยนแทนตัวต่อตัว เรียกว่า 1:1 transliteration ตัวอย่าง เช่น tr "abcd" "wxyz" เป็นการเปลี่ยนข้อมูลเข้าที่เป็นอักษร a ด้วย w, b ด้วย x, ... เป็นลำดับไปจนกว่าจะหมดข้อมูล โดยปกติแล้วจำนวนอักขระที่กำหนดในเซตทั้งสองควรเท่ากัน หากไม่เท่ากัน เช่นอักขระในเซตที่สองมีจำนวนน้อยกว่าอักขระในเซตแรก tr "abcdefgh" "1234" tr จะทำการขยายอักขระตัวสุดท้ายของอักขระเซตที่สองให้มีจำนวนเท่ากับอักขระในเซตแรกดังนี้ a b c d e f g h 1 2 3 4 4 4 4 4 ในกรณีที่จำนวนอักขระในเซตที่สองมากกว่าจำนวนอักขระในเซตแรก อักขระส่วนที่เกินมาจะไม่ถูกนำมาใช้งาน อักขระในแต่ละเซตสามารถใช้งานได้หลายแบบ ทั้งแบบ การกำหนดสมาชิกทุกตัวในเซต , การกำหนดพิสัย, และหรือกำหนดด้วย POSIX character class เช่น 1. กำหนดสมาชิกทุกตัวในเซต (enumeration) เหมาะสำหรับเซตที่มีจำนวนอักขระน้อย หรือเป็นอักขระที่มีรหัสไม่ต่อเนื่องกัน เช่น tr "{}" "()" # เปลี่ยนวงเล็บปีกกา (brace) เป็นวงเล็บเล็ก (parentheses) 2. กำหนดด้วยพิสัย ลักษณะเดียวกับที่ใช้ในคำสัง grep เช่น tr "a-z" "A-Z" # เปลี่ยนอักษรตัวเล็ก (lowercase) เป็นอักษรตัวใหญ่ (uppercase) 3. กำหนดด้วย POSIX character class ลักษณะเดียวกับที่ใช้ในคำสัง grep เช่น tr "[:upper:]" "[:lower:]" # เปลี่ยนอักษรตัวใหญ่ (uppercase) เป็นอักษรตัวเล็ก (lowercase) นอกจากนี้ยังสามารถนำทั้งสามแบบมาผสมผสานใช้งานร่วมกันได้ อักขระในเซตส่วนใหญ่เป็นตัวแทนของอักขระนั้น และกำหนด escape sequence ดังนี้ \nnn รหัสแทนอักขระเป็นเลขฐานแปด ระหว่าง 1 - 3 ตัว \a alert หรือ audible bell \b backspace \f form feed \n newline \r carriage return \t horizontal tab \v vertical tab อักขระแรก-อักขระสุดท้าย อักขระทุกตัวระหว่างอักขระตัวแรกถึงอักขระตัวสุดท้าย เรียงจากน้อยไปมาก [อีกขระ*] ใช้เฉพาะเซตที่สอง สำหรับขยายอักขระที่กำหนดให้มีความยาวเท่ากับอักขระในเซตแรก การใช้งานอักขระพิเศษ ต้องนำด้วย \ เช่น การเปลี่ยนวงเล็บปีกกาเป็นวงเล็บก้ามปู (bracket) tr "{}" "\[]" # สังเกตการใช้ \[ เพราะ [ เป็นอักขระพิเศษสำหรับ POSIX character class หมายเหตุ: ประเด็นสำคัญที่ต้องระลึกไว้เสมอคือ การเปลี่ยนแทนนี้เป็นการเปลี่ยนแทนตัวอักขระต่อตัวอักขระ ไม่ใช่การเปลี่ยนแทนคำ ดังนั้นคำสั่ง tr "cat" "dog" ไม่ใช่เป็นการเปลี่ยนแทนคำว่า "cat" ด้วยคำว่า "dog" เพราะหากเลือกตัวอย่างสำหรับทดลองไม่ดีจะทำให้คิดเช่นนั้น เช่น $ echo "my cat" | tr "cat" "dog" my dog อาจทำให้คิดว่าการเป็นการเปลี่ยนแทน cat ด้วย dog หากเพิ่มข้อความเป็น $ echo "my cat, my caret" | tr "cat" "dog" my dog, my doreg จะเห็นวิธการเปลี่ยนแทนได้ชัดเจนขึ้น และจะเห็นความสำคัญของการเลือกตัวอย่างข้อมูลสำหรับทดสอบการทำงานของคำสั่งและโปรแกรม การ Complement, ตัวเลือก -c ------------------------- เป็นการ complement เซตแรกโดยเทียบกับ Universe of discourse ซึ่งเป็นอักขระทั้งหมดในตารางรหัส ASCII เช่น tr -c "[:print:][:space:]" "?" เป็นการเปลี่ยนแทนอักขระที่ "ไม่ใช่" printable characters และ "ไม่ใช่" white space ด้วยเครื่องหมายคำถาม รายละเอียดของสมาชิกในเซต [:print:] และ [:space:] ดูจาก Lect07.txt การลบ (delete, -d) ------------------- ทำการลบอักขระที่กำหนดใน <เซตแรก> ซึ่งอาจเป็นอักขระตัวเดัยวหรือหลายตัวก็ได้ เช่นใช้เพื่อกรองอักขระพิเศษออกจาก script ก่อนทำการ run เพื่อไม่ให้มีผลรบกวนการทำงาน เช่น $ tr -d '=;:`"<>,./?!@#$%^&(){}[]' < infile ลบเครื่องหมายที่กำหนดคือ '=;:`"<>,./?!@#$%^&(){}[]' ออกจากแฟ้มชื่อ infile การใช้งานที่สำคัญอีกอย่างหนึ่งคือการเปลี่ยนรหัสขึ้นบรรทัดใหม่ (newline) ในแฟ้มของบริษัท Microsoft เป็นรหัสขึ้นบรรทัดใหม่ของ Unix เนื่องจากระบบปฏิบัติการของบริษัท Microsoft กำหนดให้ใช้คู่อักขระ CR-LF เป็น newline ในขณะที่ระบบปฏิบัติการ Unix ใช้ LF เป็น newline เมื่อทำการถ่ายโอนแฟ้มในเครือข่ายเช่น ftp หากกำหนดชนิดแฟ้มเป็น ascii ชั้น Presentation Layer ของเครือข่ายจะทำการแปลงให้โดยอัตโนมัติ หากกำหนดชนิดแฟ้มเป็น binary จะทำให้เมื่อใช้งานใน Unix มี CR เกินมาที่ท้ายบรรทัดซึ่งมีผลต่อการทำงานของโปรแกรมที่ทำงานในระดับไบต์ สามารถกำจัดอักขระ CR ส่วนเกินได้ดังนี้ $ tr -d "\015" < pc-file > unix-file # กำหนดอักขระที่ต้องการลบด้วยเลขฐานแปด หรือ $ tr -d "\r" < pc-file > unix-file # กำหนดด้วยรหัสควบคุม การบีบ (squeeze, -s) ------------------ การบีบ หรือการลดจำนวนอักขระที่ซ้ำกันให้เหลือเพียงตัวเดียว เช่น $ tr -s "\40" < infile # รหัส ascii ของวรรค = 40 (ฐานแปด) หรือ $ tr -s " " < infile ลดจำนวนวรรคจำนวนหลายวรรคที่อยู่ต่อเนื่องกัน ให้เหลือเพียงตัวเดียว หรืออาจบีบแล้วเปลี่ยนแทนด้วยอักขระอื่นก็ได้ เช่น $ tr -s "[:space:]" "[\:*]" < infile กำหนดให้บีบอักขระในกลุ่ม white space หลายตัวที่อยู่ติดกันให้เหลือเพียงตัวเดียว เนื่องจากมีการกำหนดอักขระในเซตที่สองด้วย เท่ากับเป็นการกำหนดให้ tr ทำการเปลี่ยนแทนอักขระในฌวตที่หนึ่งด้วยอักขระในเซตที่สองด้วย ตัวอย่างนี้จึงมีการแทนอักขระที่บีบแล้วแต่ละตัวด้วย colon (':') ซึ่งเป็นอักขระพิเศษจึงต้องกำกับด้วย \ เป็น '\:' ส่วนเครื่องหมาย * มีผลให้ tr เพิ่มจำนวน colon ให้มีความยาวเท่ากับอักขระในเซตแรก เนื่องจากการเปลี่ยนแทนอักขระที่กำหนดด้วยเซตต้องมีจำนวนอักขระในเซตแรก และเซตที่สองต้องมีจำนวนเท่ากัน $ tr -s "\n" < infile ลดจำนวนบรรทัดว่างหลายบรรทัดให้เหลือเพียงบรรทัดเดียว เช่นใช้ลดการเว้นบรรทัดในเอกสารชนิด double spacing ซึ่งกำหนดให้มีการพิมพ์เอกสารหนึ่งบรรทัด เว้นบรรทัดหนึ่งบรรทัด เพื่อความสะดวกในการตรวจและแก้ไขงาน การใช้งาน tr สำหรับ "บีบ" เป็นการทำงานที่มีประโยชน์มาก เพราะสามารถใช้ tr "จัด" ข้อมูลให้อยู่ในรูปแบบที่เหมาะสม สำหรับการทำงานของโปรแกรม cut ที่มีความสามารถในการแยกฟิลด์จำกัดเกินไป จะเห็นตัวอย่างได้จากกรณีศึกษาตอนท้ายบทความนี้ การใช้ตัวเลือกผสมกัน ---------------- โดยมากการใช้งานคำสั่ง tr มักจะมีการใช้ตัวเลือก -c (complement), -d (delete), และ -s (squeeze) option หลายตัวผสมกัน เช่น tr -cd "[:digit:]" เป็นการ "ลบ" (-d) อักขระที่ไม่ใช่ตัวเลข (complement ของ [:digit:], -c) หรือเป็นการ "แยกตัวเลข" ออกจากข้อมูลเข้า tr -cs "[:alpha:]" "[\n*]" # สังเกตการใช้ * เพื่อขยายจำนวน \n เป็นการสร้างรายการของคำทุกคำ (list of words) ของข้อมูลเข้า โดยการเปลี่ยนแทนอักขระทุกตัวที่ "ไม่ใช่" ตัวอักษรให้เป็น newline ('\n') ผลลัพธ์คือจะได้รายการของคำ บรรทัดละคำ และมีการเว้นบรรทัดจำนวนมาก (เท่ากับจำนวนตัวอักขระที่ไม่ใช่ตัวอักษร) และทำการลดการเว้นบรรทัดหลายบรรทัดต่อเนื่องกันให้เหลือเพียงบรรทัดเดียว โดยใช้ -s ผลลัพธ์คือรายการของคำ บรรทัดละคำ ซึ่งอาจนำไปใช้ในการจัดเรียงด้วย sort และกำจัดข้อมูลที่ซ้ำกันโดยใช้ option -u (unique) $ cat infile | tr -cs "[:alnum:]" "\n" | sort -u ในกรณีที่ต้องทราบคำที่มีความถี่ใช้งานสูง ทำได้โดยการใช้คำสั่ง uniq -c และทำการ sort ความถึ่จากมากไปน้อย $ cat infile | tr -cs "[:alnum:]" "\n" | sort | uniq -c | sort -rn คำสั่ง uniq ใช้แสดงบรรทัดที่ซ้ำกันที่อยู่ต่อเนื่องกันเฉพาะบรรทัดแรกเพียงบรรทัดเดียว option -c (count) จะนำหน้าบรรทัดด้วยจำนวนจำนวนบรรทัดที่ซ้ำกัน และเนื่องจาก uniq ใช้แสดงบรรทัดที่ซ้ำกันที่อยู่ต่อเนื่องกัน จึงต้องทำการ sort ข้อมูลเข้าของ uniq ก่อน ผลลัพธ์ของ uniq อยู่ในรูปแบบดังนี้ <จำนวน> <ข้อความในบรรทัด> การเรียงจึงต้องเรียงแบบจำนวน, -n และเรียงจากมากไปน้อย, -r กรณีศึกษา -------- 1. การเข้ารหัสลับ (Cryptography) รูปแบบทั่วไปของการเข้ารหัสเป็นดังนี้ plain text ---> encrypt() ---> cipher text # การเข้ารหัส cipher text ---> decrypt() ---> plain text # การถอดรหัส เมื่อ plain text เอกสารก่อนการเข้ารหัส cipher text เอกสารที่เข้ารหัสแล้ว encryption การเข้ารหัสลับ decryption การถอดรหัส การเข้ารหัสลับในยุคแรกๆ คือการเข้ารหัสตามแบบของ Julius Caesar, จักรพรรดิโรมันที่มีชีวิตในช่วง 100 ปีก่อนคริสตกาล โดยใช้วิธีเลิ่อนตัวอักษรไปข้างหน้า 3 ตำแหน่ง ตัวอักษรสามตัวสุดท้ายให้วนบรรจบกลับมายังอักษรสามตัวแรก วิธีการนี้อักษร A เปลี่ยนเป็น D, B เปลี่ยนเป็น E, ..., X เปลี่ยนเป็น A, Y เปลี่ยนเป็น B, Z เปลี่ยนเป็น C การเข้ารหัส เริ่มด้วยการแทนอักษรแต่ละตัวด้วยจำนวนเต็ม A = 0, B = 1, C = 2, ... , Z = 25 การเข้ารหัสและการถอดรหัสทำโดยใช้ Congruence, ฟังก์ชันการเข้ารหัส (encryption), f กำหนดด้วย f(p) = (p + 3) mod 26 เมื่อ p เป็นรหัสจำนวนเต็มแทนอักษร โดย 0 ≤ p ≤ 25, ทำให้ค่าของ f(p) อยู่ในเซต {0, 1, 2, ... , 25 } จากนั้นจึงแปลงจำนวนเต็มที่ได้ เป็นอักษร การถอดรหัส (decryption) ใช้วิธีการแบบเดียวกับการเข้ารหัส เพียงแต่ใช้ฟังก์ชัน f', ซึ่งเป็น inverse function ของ f ดังนี้ f'(p) = (p - 3) mod 26 การเข้ารหัสลับแบบนี้เป็นการเข้ารหัสแบบเปลี่ยนแทน (substitution) ชนิดเลื่อนตำแหน่ง เรียกว่า shift cipher การเข้ารหัสโดยเลื่อนไป 3 ตำแหน่งมีชื่อเรียกเฉพาะตามผู้คิดว่า Caesar cipher หรือเรียกว่ามีกุญแจรหัส (key) เป็น 3 และสามารถจัดรูปแบบการเข้ารหัสลับแบบนี้ได้เป็น f(p) = (p + k) mod 26 และ f'(p) = (p - k) mod 26 เมื่อ k เป็นจำนวนตำแหน่งที่เลื่อนไป การเข้ารหัสแบบนี้เป็นการเข้ารหัสที่ไม่ "แกร่ง" นัก สามารถถอดรหัสได้ง่าย ในปัจจุบันมีไว้สำหรับใช้เข้ารหัสคำเฉลยของเกมส์ปริศนา (puzzle) เข่น อักษรไขว้ และ ทายคำ เป็นต้น , การบอกจุดหักเห หรือจุดสำคัญของภาพยนต์ล่วงหน้า (spoiler), และเรื่องที่ไม่ต้องการให้ผู้คนทั่วไปเห็นได้ชัดเจน ตัวอย่างเช่นการเข้ารหัสลับแบบนี้ที่มีการใช้งานคือ ROT-13 (Rotate by 13 places) ที่มี k = 13 การเขียนโปรแกรมเข้าและถอดรหัส Caesar cipher ด้วยภาษาสำหรับทำโปรแกรม เช่น ภาษา C คือการเขียนฟังก์ชัน f และ f' และที่สำคัญต้องสร้างฟังก์ชัน mod ขึ้นใช้เอง เพราะเครื่องหมาย % ทำงานได้ถูกต้องเฉพาะจำนวนเต็มบวกเท่านั้น การเข้ารหัส Caesar cipher ทำได้ง่ายมาก หากมองว่าเป็นการเปลี่ยนแทนอักษรแถวบนด้วยอักษรแถวล่างที่มีตำแหน่งตรงกัน คือ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z D E F G H I J K L M N O P Q R S T U V W X Y Z A B C เขียนเป็นคำสั่ง tr สำหรับเข้ารหัสตัวอักษรทั้งตัวใหญ่และตัวเล็กได้ดังนี้ tr "A-Za-z" "D-ZABCd-zabc" การถอดรหัสทำได้โดยการกลับเซตอักขระสำหรับเปลี่ยนแทน tr ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 2. การทำลายโพรเซสด้วยชื่อคำสั่ง จาก pipeline สำหรับหยุดการทำงานของโพรเซส (ทำลายโพรเซส) ด้วยคำสั่ง kill ในการบรรยายที่ผ่านมา คือ $ ps -ef | egrep "top" | egrep -v "egrep" | awk '{print $2}' | xargs kill -9 ทำการเปลี่ยนแทนคำสั่ง awk ใน pipeline ด้วยคำสั่ง cut ▶ คำสั่ง cut - ตัด "ส่วน" ที่ระบุของแต่ละบรรทัดจากแฟ้มที่กำหนด, "ส่วน" ของบรรทัดกำหนดด้วยตำแหน่งอักขระ (-c), หรือ ฟิลด์ (-f) โดยกำหนดตัวแยกฟิลด์ไว้เป็น tab หากใช้ตัวอื่นต้องกำหนดด้วย -d เช่น แสดงการตัดปี ค.ศ. ซึ่งเป็นฟิลด์ที่ 6 โดยกำหนด delimiter เป็นวรรค (default เป็น tab) $ date | cut -d" " -f6 2018 ตัวอย่างผลลัพธ์จากคำสั่ง $ ps -ef | egrep "top" | grep -v "grep" เช่น jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 มีอักขระคั่นระหว่างฟิลด์เป็น "วรรค" และเมื่อกำหนดให้ตัดฟิลด์ที่สอง (หมายเลขโพรเซส) ดังนี้ $ ps -ef | egrep "top" | egrep -v "egrep" | cut -d" " -f2 จะดูเหมือนไม่ได้ผลลัพธ์ใดเลย แต่โดยที่จริงแล้วได้ผลลัพธ์เป็นเครื่องหมายวรรคหนึงตัวที่มองไม่เห็น เพราะ -d' ' แทนวรรคเพียงวรรคเดียว ทำให้หมายเลขโพรเซส (pid) เป็นฟิลด์ที่ 6 ทำให้ไม่สามารถใช้งานได้ หากใช้ตัวเลือก -c กำหนดตำแหน่งอักขระเริ่มต้นและสิ้นสุด ก็จะมีปัญหาอีกเพราะจำนวนหลักของ pid ไม่เท่ากัน ในกรณีเช่นนี้จำเป็นต้องใช้คำสั่ง tr เพื่อ "บีบ" วรรคหลายตัวให้เหลือเพียงตัวเดียว จะได้บรรทัดข้อมูล (หรือเรคอร์ด) ที่มีวรรคคั่นแต่ละฟิลด์คั่นเพียงตัวเดียว แล้วจึงตัดฟิลด์ด้วย cut การ "บีบ" หรือลดอักขระที่ซ้ำกันให้เหลือเพียงตัวเดียว ทำได้โดยกำหนดตัวเลือกของ tr เป็น -s (squeeze) มีรูปแบบการใช้งานดังนี้ tr -s อักขระที่ต้องการ "บีบ" # ในที่นี้คือวรรค, ' ' หรือในกรณีที่ต้องการเปลี่ยนเป็นอักขระอื่น เช่น บีบวรรคหลายตัวให้เหลือตัวเดียว แล้วเปลี่ยนเป็นลูกน้ำ (,) ทำดังนี้ tr -s " " "," ลำดับงานของ pipeline เป็นดังนี้ # เลือกเฉพาะบรรทัดที่มีคำว่า "top" $ ps -ef | egrep "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 # เลือกเฉพาะบรรทัดที่ไม่มีคำว่า "egrep" -- กำจัดบรรทัดอื่นที่ไม่เกี่ยวออกไป $ ps -ef | egrep "top" | egrep -v "egrep" jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 # บีบ "วรรค" ที่อาจมีหลายตัวที่อยู่ระหว่างฟิลด์ให้เหลือตัวเดียว $ ps -ef | egrep "top" | egrep -v "egrep" | tr -s " " jira 23422 23399 0 06:08 pts/0 00:00:00 top -u jira -d30 # ตัดมาเฉพาะฟิลด์ที่สอง (pid - หมายเลขโพรเซส) จากบรรทัด หรือเรคอร์ด ที่มีอักขระคั่นเป็นวรรค $ ps -ef | egrep "top" | egrep -v "egrep" | tr -s " " | cut -d" " -f2 23422 # ส่งหมายเลขโพรเซสที่ตัดได้เป็นอาร์กิวเมนต์ให้คำสั่ง kill $ ps -ef | egrep "top" | egrep -v "egrep" | tr -s " " | cut -d" " -f2 | xargs -9 kill คำสั่ง kill ใช้สำหรับส่งสัญญาณ (signal) หมายเลข 9 ซึ่งมีชื่อสัญญาณเป็น SIGKILL ไปยังโพรเซสที่ระบุ เมื่อโพรเซสได้รับสัญญาณนี้แล้วจะจบการทำงานของตัวเอง (Terminate) -- หรือเป็นสัญญาณให้ทำลายตัวเอง 3. การแยกตัวบ่งชี้ (identifier) จากโปรแกรมภาษา C หากผู้เขียนโปรแกรมภาษา C ต้องการดูว่าในแฟ้มโปรแกรมต้นฉบับมีการใช้ identifer ใดบ้าง - identifier ได้แก่ ชื่อตัวแปร, ชื่อฟังก์ชัน, ชื่อ label, tag สำหรับ union และ structure - ชื่อของตัวบ่งชี้ ตัวแรกเป็นตัวอักษรหรือขีดล่าง (underscore) ตัวต่อๆ ไปอาจเป็นตัวอักษร, ตัวเลข, หรือ ขีดล่างก็ได้ อักขระตั้งแต่ตัวที่สองเป็นต้นไปจะมีหรือไม่ก็ได้ เขียนเป็นนิพจน์ปรกติได้เป็น "[A-Za-z_][A-Za-z0-9_]*" - นิพจน์ปรกติที่กำหนดนอกจากจะตรงกับชื่อตัวบ่งชี้แล้ว ยังตรงกับชื่อคำสำคัญ (keyword) เช่น if, while, ... และยังตรงกับค่าคงที่สายอักขระที่มีในโปรแกรมด้วย แนวคิด - เปลี่ยนอักขระนอกเหนือจาก "A-Za-z0-9_" เป็นรหัสขึ้นบรรทัดใหม่ (newline, '\n') นอกเหนือจาก "A-Za-z0-9_" คือ Complement ของอักขระชุดนี้, -c "A-Za-z0-9_" เปลี่ยนแทนด้วย newline คำสั่ง: tr -c "A-Za-z0-9" "\n" - กำจัดบรรทัดว่างโดยเพิ่มตัวเลือก -s : tr -cs "A-Za-z0-9" "\n" - ผลลัพธ์ที่ได้มีทั้งชื่อตัวบ่งชี้ คำสำคัญ ค่าคงที่สายอักขระ และค่าคงที่ที่เป็นตัวเลข - เรียงลำดับผลลัพธ์ที่ได้เพื่อให้ง่ายต่อการค้นหา โดยใช้คำสั่ง sort, และหากมีคำซ้ำกันให้เลือกมาใช้งานเพียงคำเดียว กำหนดด้วยตัวเลือก -u (unique) ของคำสั่ง sort คำสั่ง $ cat source.c | tr -c '"a-zA-Z0-9_" "\n" | sort -u การปรับปรุง - เราสามารถกำจัดตัวเลขออกจากรายการได้ โดยใช้คำสั่ง grep เลือกเฉพาะบรรทัดที่ไม่ใช่ตัวเลขล้วน หรือ egrep -v "[0-9]+" $ cat source.c | tr -cs "A-Za-z0-9_" "\n" | sort -u | egrep -v "[0-9]+" - กำจัด keyword ของภาษา C ออกโดยใช้คำสั่ง comm เปรียบเทียบผลลัพธ์ที่ได้ กับรายการ keyword ในแฟ้ม และเลือกไว้เฉพาะ คำที่ไม่ใช่ keyword ก ในไดเรกทอรีที่จะทำงานต้องมีแฟ้มเก็บ keyword ของภาษา C อยู่แล้ว $ cat source.c | tr -cs "A-Za-z0-9_" "\n" | sort -u | egrep -v "[0-9]+" | comm -23 - keyword.txt ในกรณีที่โปรแกรมมี comment จำนวนมาก จะมีค่าคงที่สายอักขระในผลลัพธ์มาก ซึ่งทำให้ยากต่อการแยกแยะว่าชื่อใดเป็นตัวบ่งชี้ ชื่อใดเป็นค่าคงที่สายอักขระ ต้องทำการกำจัด comment และสายอักขระในโปรแกรมซึ่งเกินความสามารถของคำสั่ง tr หากต้องการค้นหาสายอักขระที่กับนิพจน์ปรกคิในแฟ้มและเปลี่ยนแทนด้วยคำที่กำหนด (ซึ่งอาจเป็นสายอักขระว่าง หรือ empty string) ทำได้โดยใช้คำสั่ง sed ซึ่งมีลักษณะการทำงานเหมือนโปรแกรม vi แต่มีการทำงานได้เฉพาะจากบรรทัดคำสั่งเท่านั้น sed (stream editor for filtering and transforming text) มีการใช้งานค่อนข้างซับซ้อน เป็นอีกคำสั่งหนึ่งที่มีความสามารถมาก ผู้สนใจสามารถเรียนรู้จากคู่มือของ sed หรือ web site ที่สอนการใช้ sed ซึ่งมีจำนวนมากทั้งภาษาไทยและภาษาอังกฤษ คำสำคัญของภาษา C ---------------------- คำสำคัญ หรือ keyword ของภาษา C มีทั้งหมด 32 คำดังนี้ :- auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while คำสั่ง comm -------------- comm - เปรียบเทียบข้อมูลที่จัดเรียงแล้วในแฟ้มสองแฟ้ม โดยเปรียบเทียบไปทีละบรรทัด (compare two sorted files line by line) รูปแบบการใช้งาน comm [ตัวเลือก] แฟ้มแรก แฟ้มที่สอง ในกรณีที่แฟ้มแรกเป็นตัวแทนของข้อมูลที่ผ่านเข้ามาจาก standard input เช่นใน pipeline ให้แทนชื่อแฟ้มด้วยเครื่องหมาย - ตัวเลือก หากไม่มีการกำหนด option จะแสดงผลการเปรียบเทียบเป็นสามคอลัมน์, คอลัมน์แรกเป็นบรรทัดที่พบเฉพาะในแฟ้มแรก คอลัมน์ที่สองเป็นบรรทัดที่พบเฉพาะในแฟ้มที่สอง และคอลัมน์ที่สามแสดงบรรทัดที่มีเหมือนกันในแฟ้มทั้งสอง ตัวเลือกมีไว้เพื่อกำหนดว่าจะ "ไม่" แสดงผลคอลัมน์ใด คือ -1 ไม่แสดงผลคอลัมน์ 1 (บรรทัดที่มีเฉพาะแฟ้มแรก) -2 ไม่แสดงผลคอลัมน์ 2 (บรรทัดที่มีเฉพาะแฟ้มที่สอง) -3 ไม่แสดงผลคอลัมน์ 3 (บรรทัดที่มีทั้งสองแฟ้ม) ----------------------------------------------------------------------------------------------------------------------------------- ของแถม -Unix Pipeline ------------------------ จากตัวอย่างการทำงานของ Unix pipeline ในแฟ้ม Lect06.txt และแฟ้มนี้คือ $ ps -ef | egrep "top" | egrep -v "egrep" | awk '{print $2}' | xargs kill -9 $ ps -ef | egrep "top" | egrep -v "egrep" | tr -s " " | cut -d" " -f2 | xargs -9 kill สามารถนำมาสร้างเป็น shell script สำหรับหยุดการทำงานของโพรเซสใดๆ จากชื่อได้ โดยแทนชื่อคำสั่ง top ด้วยตัวแปรที่จะรับชื่อของโปรแกรมที่ผู้ใช้กำหนดมาในบรรทัดคำสั่ง เช่น หาก script มีชื่อเป็น mykill และต้องการหยุดการทำงานของโปรแกรม top ทำได้ดังนี้ $ mykill top # หยุดการทำงานของโพรเซสที่เกิดจากคำสั่ง top คำสั่งในแฟ้ม mykill เป็นดังนี้ $ cat mykill ps -ef | egrep "$1" | egrep -v "egrep" | awk '{print $2}' | xargs kill -9 เมื่อ $1 เป็น command line argument ลำดับที่ 1 ที่ผู้ใช้ป้อนเข้ามาในบรรทัดคำสั่ง และต้องไม่ลืมว่า shell script จะทำงานได้ ผู้ใช้ต้องมีสิทธิในการ execute (x) shell script นี้ทำงานได้ถูกต้องเฉพาะเมื่อผู้ใช้นั้นมีการเรียกใช้โปรแกรม top เพียงคนเดียว หากมีผู้เรียกใช้หลายคนจะไม่สามารถทำงานได้ ถูกต้อง ตัวอย่างที่ชัดเจนกว่าคือการเรียกใช้ a.out ซึ่งในชั่วโมงปฏิบัติการที่ใช้ภาษา C ในเวลาใดเวลาหนึ่งอาจมีผู้เรียกใช้หลายคน ในกรณีเช่นนี้จำเป็นต้องกำหนดชื่อผู้ใช้ด้วย (ชื่อโปรแกรม AND ชื่อผู้ใช้) ซึ่งเป็นข้อมูลฟิลด์แรกของคำสั่ง ps -ef โดยการเพิ่มคำสั่ง grep เพื่อหาชื่อผู้ใช้เฉพาะที่ต้นบรรทัด หรือ grep "^ชื่อผู้ใช้" ระหว่างการทำงาน เชลล์จะเก็บชื่อผู้ใช้แต่ละคนไว้ในตัวแปร USER ซึ่งเป็นตัวแปรของ shell (ตัวแปรระบบ) สามารถนำมาใช้ในนิพจน์ปรกติของ grep ได้เป็น grep ^$USER หรือ grep "^USER" ในกรณีนี้ไม่สามารถใช้เครื่องหมายคำพูดเดี่ยวได้ เพราะจะทำให้ไม่สามารถเข้าถึงตัวแปรได้ แฟ้ม mykill ที่แก้ไขแล้วเป็นดังนี้ $ cat mykill ps -ef | egrep "$USER" | egrep "$1" | egrep -v "egrep" | awk '{print $2}' | xargs kill -9