เวลาผมให้นักศึกษาทำการบ้านเขียนโปรแกรม ผมมักจะให้ส่งการบ้านทางเมล เพราะคิดว่าสะดวกที่จะตรวจทางเมลมากกว่า จะทดสอบด้วยการรันโปรแกรมที่ส่งมาก็ได้ รวมทั้งยังสามารถใช้เครื่องมือต่างๆ ในการตรวจจับความคล้ายของโปรแกรมในกรณีที่ลอกกันมาได้อีกด้วย แต่ประสบการณ์ที่ผ่านมา จะพบปัญหาต่างๆ ที่ทำให้ผมไม่อยากตรวจการบ้าน ก็คือ เมลที่แต่ละคนส่งมา จะมีรูปแบบหลากหลาย ไม่ค่อยตรงกับที่กำหนดให้ เช่น ไม่ตั้งชื่อไฟล์ตามที่กำหนด บางคนก็ใช้โปรแกรมบีบอัดแบบแปลกๆ มาให้ บางทีบอกให้ส่งเป็น plain text ก็จะมีคนเขียนใน MS Word แล้วเปลี่ยน extension เป็น .txt จนอ่านไม่ออก หรือกำหนดให้ส่งเป็น .jar แต่ไม่รู้ยังไงใช้ WinRAR ทำเป็น .rar แล้วเปลี่ยน extension เป็น .jar เฉยเลย สุดท้ายผมจะต้องมานั่งไล่เช็คไล่เก็บไฟล์ทีละฉบับ บางคนยังส่งมาหลายที แถมยังมีการใช้แอดเดรสเดียวกัน ส่งการบ้านหลายชุดแทนเพื่อนอีก ทำให้สับสนได้ง่ายมาก และเนื่องจากผมจะต้องสอนนักศึกษาไม่น้อยกว่า 70-80 คน จึงเสียเวลาไปกับเรื่องนี้เยอะมาก จนพาลไม่ค่อยจะอยากตรวจสักเท่าไหร่
เมื่อวานเลยนั่งคิดว่าจะทำยังไงกับการบ้านในเทอมที่จะถึงนี้ดี จะทำเป็นเว็บให้อัพโหลดไฟล์ ก็ขี้เกียจเขียนเว็บ สุดท้ายจึงตัดสินใจจะใช้เมลเหมือนเดิม แล้วไปเขียนสคริปต์ด้วย fetchmail กับ procmail หรือไม่ก็ maildrop เพื่อคัดแยกเมลที่ส่งมา ไปไว้ในไดเรคทอรีที่กำหนด แยก attachment ออกจากเมล โดยอาจจะใช้ Perl หรือไม่ก็โปรแกรม ripMime พร้อมทั้งตอบเมลกลับแบบอัตโนมัติว่าได้รับการบ้านแล้ว (ในกรณีที่ส่งมาถูกตามที่กำหนด) สุดท้ายหวังว่าจะช่วยให้จัดการกับการบ้านทางเมลได้สบายขึ้น แต่วิธีการทั้งหมดก็จะยังไม่สามารถจัดการกับกรณีที่ส่งไฟล์มาผิดประเภทได้ แต่ไม่เป็นไรเพราะมีคนแนะนำวิธีจัดการมาให้แล้ว ก็คือไม่ต้องตรวจให้ 0 ไปเลย คนแนะนำรับรองว่าแค่ทำเพียงครั้งเดียวก็จะส่งถูกต้องทุกคน จริงๆ แล้วยังไม่ได้เขียนสคริปต์ทั้งหมดหรอก แต่จะเอามาเล่าเรื่อยๆ วันนี้เล่าถึง fetchmail ก่อนล่ะกัน
โปรแกรม fetchmail เป็นโปรแกรมสำหรับดึงเมลจากเซิร์ฟเวอร์ (จะเป็นแบบ POP3 หรือ IMAP ก็ได้) มาไว้ที่เครื่องของเรา การทำงานของมัน ก็จะคล้ายๆ โปรแกรมอ่านเมลทั่วไป เพียงแต่ว่าเป็นแบบ command line ที่ให้โหลดเป็นครั้งๆ ไป และอาจจะกำหนดให้ทำงานเป็น daemon คอยเช็คและโหลดอยู่ตลอดเวลาได้ด้วย การใช้งานก็ง่ายมาก แค่กำหนดข้อมูลเซิร์ฟเวอร์ที่จะไปโหลดไว้ในไฟล์ .fetchmailrc เช่น
set daemon 300 poll mail.mydomain.com protocol pop3 user "myname" password "mypassword" mimedecode mda "/usr/bin/maildrop" no keep fetchall
จะเป็นกำหนดให้อ่านเมลทั้งหมดจากเซิร์ฟเวอร์ด้วยโปรโตคอล pop3 ตามที่กำหนด จากนั้นส่งเมลที่ได้ทีละฉบับไปยังโปรแกรม maildrop ซึ่งเราจะสามารถกำหนด regular expression สำหรับคัดแยกเมลได้ ทีนี้จะเขียนวิธีการคัดแยกทำยังไงเอาไว้ต่อคราวหน้าล่ะกัน
Tags: assignment, fetchmail, linux, teaching, unix
ในระบบเชลล์ของยูนิกซ์เวลาที่เราต้องการจะสั่งให้โปรแกรมทำงานแบบแบ็คกราวน์ จะทำโดยเรียกโปรแกรมนั้นพร้อมกับระบุเครื่องหมาย &
ต่อท้าย เช่น
$ firefox &แต่เราจะพบปัญหาเวลาที่ต้องการให้โปรแกรมที่สั่งให้ทำงานแบบแบ็คกราวน์นั้นทำงานต่อไปแม้ว่าเราจะออกจากระบบไปแล้ว เนื่องจากเวลาเราออกจากระบบ (logout) จะมีการส่งสัญญาณ SIGHUP (hangup) ไปยังโปรเซสทั้งหมดของเราที่กำลังทำงานอยู่ ซึ่งพฤติกรรมโดยปกติของโปรเซสเมื่อได้รับสัญญาณก็คือ หยุดการทำงานแบบไม่ปกติ (abnormal termination) จึงทำให้โปรแกรมที่เราหมายมั่นปั้นมือว่าจะให้ทำงานไปตลอดคืนนี้ หยุดทำงานไปโดยไม่รู้ตัว (กว่าจะรู้อีกทีก็อาจจะเช้าแล้ว ต้องเสียเวลารออีก)
วิธีการแก้ปัญหานี้มีหลายวิธี วิธีที่ยุ่งยากหน่อยก็คือแก้โปรแกรมโดยเรียกใช้ system call เพื่อดัก SIGHUP ไว้ โปรแกรมของเราจะได้ไม่ทำตามพฤติกรรมปกติซึ่งก็ดูจะยุ่งยากไปหน่อย และไม่สามารถใช้กับโปรแกรมที่ไม่เปิดเผยโค้ดได้ อีกวิธีหนึ่งก็คือการใช้โปรแกรม nohup ซึ่งจะทำตัวเป็นเหมือนเชลล์ห่อโปรแกรมของเราไว้อีกชั้นหนึ่ง โดยจะดักสัญญาณ SIGHUP ไว้ ทำให้โปรแกรมทำงานต่อไปหลังจากออกจากระบบ เช่น
$ nohup myprogram &
แต่ข้อเสียของโปรแกรม nohup ก็คือแม้ว่าเราจะกลับเข้ามาในระบบใหม่ เราก็จะไม่สามารถติดต่อหรือดูผลการทำงานของโปรแกรมได้ ผลลัพธ์ทั้งหมดจะต้องเก็บลงไฟล์ไว้ก่อนเท่านั้น ทำให้ไม่สะดวกเท่าที่ควร
screen เป็นโปรแกรมบนยูนิกซ์อีกโปรแกรมหนึ่งที่ช่วยอำนวยความสะดวกในการรันโปรแกรมแบบนี้
$ screen myprogramscreen จะทำงานคล้าย nohup เพียงแต่ว่าเมื่อเริ่มทำงาน screen จะทำให้ดูเหมือนว่าโปรแกรมของเราทำงานเป็นแบบฟอร์กราวน์ตามปกติทั่วไป เราสามารถสั่งให้ทำงานเบื้องหลังได้ โดยกด CTRL-A CTRL-D ต่อกัน โปรแกรมก็จะไม่เชื่อมต่อกับเทอร์มินัล แต่จะทำงานในแบบแบ็คกราวน์ แต่เราสามารถกำหนดให้ screen เรียกโปรแกรมของเราที่ทำงานอยู่ให้กลับมาอยู่เบื้องหน้าอีกครั้งได้ แม้ว่าเราจะออกจากระบบไป และกลับมาใหม่แล้วก็ตาม
$ screen -r
ในกรณีที่เรียกใช้ screen พร้อมกันหลายๆ โปรเซส ก็อาจจะต้องระบุรหัสโปรเซสที่ต้องการเรียกกลับมาบนเทอร์มินัล โดยอาจจะใช้ออปชัน -ls เพื่อแสดงโปรเซสของ screen ทั้งหมดก็ได้ นอกจากนี้ screen ยังมีออปชันให้เกิดสิ่งที่เป็นผลลัพธ์ทั้งหมดลงไฟล์ไว้ให้ด้วยก็ได้ ทำให้สะดวกในการเรียกดูผลลัพธ์ภายหลัง ผมเองก็ต้องพึ่งโปรแกรม screen เยอะเลยทีเดียวกว่าจะเรียนจบมาได้
รายละเอียดเพิ่มเติม: http://www.gnu.org/software/screen/
Tags: linux, nohup, screen, sighup, terminal, unix
ตอนนี้พยายามเขียนเรื่องให้มีสาระมากขึ้น มากกว่าแค่แปะลิงก์ แล้วก็กล่าวถึงนิดๆ เพราะลองดูสถิติจาก Google Analytics แล้ว รู้สึกว่ามีคนหลงมาที่นี่เพราะคำสำคัญต่างๆ ที่ใส่ไว้ เห็นมีคนตามมาอ่านเรื่อง fpdf ที่บ่นๆ ไว้ ไม่ค่อยมีสาระเท่าไหร่ คนอ่านคงจะเซ็งว่าไอ้บ้านี่เขียนอะไรไม่มีประโยชน์เลย
awk เป็นเครื่องมือที่ช่วยจัดการไฟล์ข้อความ โดยมองว่าข้อมูลในไฟล์แบ่งเป็นเรคอร์ดและฟิลด์ สามารถระบุได้ว่าจะให้ใช้เครื่องหมายอะไรตัวแบ่งระหว่างฟิลด์หรือเรคอร์ด ลองดูตัวอย่างง่ายๆ กันก่อน ถ้าผมมีไฟล์ข้อมูลคะแนนนักเรียนอยู่ แบ่งเรคอร์ดตามบรรทัด และแบ่งฟิลด์ด้วยแท็บ
4822111111 10 4822222222 13 4833333333 14 4844444444 9
ถ้าจะแปลงข้อมูลในไฟล์นี้เป็นตารางในแบบ html ก็ทำได้โดยอ่านไฟล์มาทีละบรรทัด แล้วเพิ่มแท็ก tr และ td ลงไป
BEGIN { FS="\t" print "<table>" } { print "<tr><td>"$1"</td><td>"$2"</td></tr>" } END { print "</table>" }
เมื่อเขียนเสร็จ ก็ลองเอามาใช้งานได้โดย
$ awk -f [awk-file] < [data-file]
จะได้ผลเป็นตารางในตามข้างล่าง
<table border="0"> <tr> <td>4822111111</td> <td>10</td> </tr> <tr> <td>4822222222</td> <td>13</td> </tr> <tr> <td>4833333333</td> <td>14</td> </tr> <tr> <td>4844444444</td> <td>9</td> </tr> </table>
ในโปรแกรม awk ข้างต้น แบ่งออกเป็น 3 ส่วน แต่ละส่วนแบ่งด้วยวงเล็บปีกกา ส่วนแรกมีคำว่า BEGIN กำหนดอยู่หมายความว่าให้ทำครั้งเดียวตอนเริ่มต้นอ่านไฟล์ ในที่นี้กำหนดให้ใช้แท็บเป็นตัวแบ่งฟิลด์ แล้วก็พิมพ์ “<table>” ออกมา
ส่วนที่สองไม่ได้กำหนดอะไรไว้ก่อนเครื่องหมายวงเล็บปีกกาเปิด ก็จะทำงานทุกครั้งเมื่อขึ้นเรคอร์ดใหม่ จึงเป็นการพิมพ์ข้อมูลทีละแถวในตาราง โดย $1 ใช้แทนข้อมูลในฟิลด์ที่ 1 และ $2 จะแทนฟิลด์ที่ 2 ไปเรื่อยๆ
ส่วนที่สามเริ่มต้นด้วย END จะทำงานเพียงครั้งเดียวเมื่ออ่านไฟล์ทั้งหมดเสร็จแล้ว ดังนั้นจึงแค่พิมพ์ “</table>” ออกมาในตอนท้าย
ด้วยลักษณะที่ awk ทำงานตามเรคอร์ดและฟิลด์ ทำให้การเขียนโปรแกรมเพื่อจัดการกับข้อมูลต่างๆ ทำได้สะดวกขึ้น ไม่จำเป็นต้องเขียนลูปเพื่อวนรอบ แค่เขียนเป็นกฎไว้เท่านั้นก็พอ นอกจากนี้ยังสามารถระบุให้ทำงานกับเรคอร์ดที่แตกต่างกันได้ด้วย โดยใช้ Regular Expression กำหนดรูปแบบของเรคอร์ดนั้น เช่น
/^4822/ { print "48" }
กฎข้างต้นจะทำให้พิมพ์คำว่า “48″ เมื่อเรคอร์ดขึ้นต้นด้วย “4822″ ตามที่กำหนดไว้ใน RE เท่านั้น
จริงๆ แล้วโปรแกรมข้างต้นผมเขียนขึ้นมาใช้งานจริง เวลาจะเอาคะแนนของนักเรียนไปประกาศบนเว็บ หลายคนอาจจะสงสัยว่าทำไมไม่ใช้โปรแกรมพวกสเปรดชีท ทำไมต้องมาใช้ awk ให้เสียเวลา คำตอบก็คือ ผมไม่เคยสามารถทำให้โปรแกรมสเปรดชีทต่างๆ สร้างตารางในรูปแบบ html ที่สะอาดๆ ได้เลย ลองมาหลายโปรแกรมแล้ว ทุกๆ อันจะต้องแทรกโน้นนี่มาให้ตลอด แล้วผมก็เป็นพวกโรคจิต ถ้าเห็นโค้ดที่ไม่สะอาดรกรุงรัง ก็จะต้องพยายามหาทางทำให้สะอาด เนื่องจากอยากให้ตารางมันแสดงไปตามรูปแบบที่เรากำหนดไว้แล้ว สุดท้ายเลยตัดสินใจใช้ awk ดีกว่าสร้างโค้ดได้สะอาดสมใจ
แต่ว่าโปรแกรมข้างต้นก็ยังมีข้อจำกัดอยู่ที่กำหนดจำนวนฟิลด์ไว้ตายตัว ทำให้ไม่ยืดหยุ่นเท่าไหร่ ถ้ามีคะแนนหลายๆ ค่า แต่เนื่องจาก awk นั้นมี control structure อยู่หลายแบบ จึงสามารถเขียนให้ยืดหยุ่นขึ้นได้อีกเป็น
BEGIN { FS=" " ORS="" print "<table class=\"attendance\" border=\"0\">\n" } { print "<tr>" for(i=1; i<=NF; i++) print "<td>"$i"</td>" print "</tr>\n" } END { print "</table>\n" }
NF เป็นตัวแปรระบุจำนวนฟิลด์ ซึ่งทำให้เราสามารถใช้ลูปเพื่อพิมพ์ข้อมูลจากทุกๆ ฟิลด์ได้
Tags: awk, linux, text processing, unix