msgbartop
Just another blog of mine
msgbarbottom

17 Jan 11 จับลอกการบ้านแบบพื้นๆ

เทอมนี้ผมได้รับมอบหมายให้สอนวิชา Introduction to Computers and Programming ซึ่งเป็นวิชาพื้นฐานสำหรับนักศึกษาชั้นปีที่ 1 ทุกโปรแกรม มีนักศึกษาลงทะเบียนจำนวนมาก อย่างเทอมนี้ก็มีเกือบ 300 คน เนื้อหาหลักก็คือสอนการเขียนโปรแกรมภาษา C กับอัลกอริทึมพื้นฐาน (อย่างเช่น searching และ sorting) แต่การเขียนโปรแกรมเป็นทักษะที่ต้องฝึกฝนบ่อยๆ จึงทำเข้าใจและทำได้ วิชาก็เลยมีทั้งบรรยาย ปฏิบัติการ และการบ้าน (ปีนี้ดีหน่อยที่สามารถหาคนมาช่วยตรวจการบ้านได้)

เมื่อมีการบ้านให้คะแนนแล้ว ผมก็อยากจะให้ทำการบ้านกันจริงๆ (อย่างน้อยก็ควรจะได้สัมผัสและฝึกฝนด้วยตนเอง) เลยจะต้องหาทางตรวจว่าลอกการบ้านกันบ้างหรือเปล่า แต่การบ้านแรกคำถามจะค่อนข้างง่ายมาก ไม่สามารถพลิกแพลงหรือเขียนโปรแกรมแบบที่แตกต่างกันมากนัก (ส่วนใหญ่จะฝึกการเขียน expression ตามสูตรที่กำหนดให้) ก็เลยตรวจการลอกข้อสอบแบบง่ายๆ ก่อน (การบ้านครั้งต่อไปค่อยขยับขยายความซับซ้อนในการตรวจจับให้เพิ่มขึ้น) คือ ตรวจแค่ว่าส่งไฟล์เดียวกันมาหรือเปล่า ในที่นี้แปลว่าเอาไฟล์มาจากเพื่อนแล้วก็ upload ส่งมาให้เลยโดยไม่แตะต้องใดๆ ทั้งสิ้น

เมื่อจะตรวจแบบพื้นๆ ผมก็แค่ใช้ hash function มาสร้าง message digest จากไฟล์ที่นักศึกษาส่งมา ถ้าไฟล์ไหนมี message digest เหมือนกัน ก็แปลว่าลอกกันมาทั้งไฟล์ จะไปได้หักคะแนนถูก การหาค่า message digest ของไฟล์การบ้านทั้งหมด ก็ใช้คำสั่ง find ของ unix

$ cd assignment1
$ find . -name "*" -not -type d -exec shasum '{}' \; | sort > digest_list.txt

คำสั่ง find ใช้หาชื่อไฟล์ตามเงื่อนไขที่กำหนด ในที่นี้คือเอาทุกไฟล์ ยกเว้นไดเรคทอรี เมื่อได้ไฟล์แล้ว เราก็สามารถสั่งให้รันโปรแกรม shasum โดย ‘{}’ จะถูกแทนด้วยชื่อไฟล์ที่ค้นเจอแต่ละไฟล์ จากนั้นก็เอาไปเรียงลำดับเพื่อให้ไฟล์ที่มี digest เหมือนกันอยู่ติดกัน เก็บลงไฟล์​ digest_list.txt ที่เหลือก็แค่เขียนโปรแกรมจับกลุ่มว่าไฟล์ที่เหมือนกันมีใครบ้าง จัดเป็นกลุ่มก็เรียบร้อย

Tags: , ,

07 Aug 08 Screen, NoHUP, SIGHUP

ในระบบเชลล์ของยูนิกซ์เวลาที่เราต้องการจะสั่งให้โปรแกรมทำงานแบบแบ็คกราวน์ จะทำโดยเรียกโปรแกรมนั้นพร้อมกับระบุเครื่องหมาย &
ต่อท้าย เช่น

$ firefox &

แต่เราจะพบปัญหาเวลาที่ต้องการให้โปรแกรมที่สั่งให้ทำงานแบบแบ็คกราวน์นั้นทำงานต่อไปแม้ว่าเราจะออกจากระบบไปแล้ว เนื่องจากเวลาเราออกจากระบบ (logout) จะมีการส่งสัญญาณ SIGHUP (hangup) ไปยังโปรเซสทั้งหมดของเราที่กำลังทำงานอยู่ ซึ่งพฤติกรรมโดยปกติของโปรเซสเมื่อได้รับสัญญาณก็คือ หยุดการทำงานแบบไม่ปกติ (abnormal termination) จึงทำให้โปรแกรมที่เราหมายมั่นปั้นมือว่าจะให้ทำงานไปตลอดคืนนี้ หยุดทำงานไปโดยไม่รู้ตัว (กว่าจะรู้อีกทีก็อาจจะเช้าแล้ว ต้องเสียเวลารออีก)

วิธีการแก้ปัญหานี้มีหลายวิธี วิธีที่ยุ่งยากหน่อยก็คือแก้โปรแกรมโดยเรียกใช้ system call เพื่อดัก SIGHUP ไว้ โปรแกรมของเราจะได้ไม่ทำตามพฤติกรรมปกติซึ่งก็ดูจะยุ่งยากไปหน่อย และไม่สามารถใช้กับโปรแกรมที่ไม่เปิดเผยโค้ดได้ อีกวิธีหนึ่งก็คือการใช้โปรแกรม nohup ซึ่งจะทำตัวเป็นเหมือนเชลล์ห่อโปรแกรมของเราไว้อีกชั้นหนึ่ง โดยจะดักสัญญาณ SIGHUP ไว้ ทำให้โปรแกรมทำงานต่อไปหลังจากออกจากระบบ เช่น

$ nohup myprogram &

แต่ข้อเสียของโปรแกรม nohup ก็คือแม้ว่าเราจะกลับเข้ามาในระบบใหม่ เราก็จะไม่สามารถติดต่อหรือดูผลการทำงานของโปรแกรมได้ ผลลัพธ์ทั้งหมดจะต้องเก็บลงไฟล์ไว้ก่อนเท่านั้น ทำให้ไม่สะดวกเท่าที่ควร

screen เป็นโปรแกรมบนยูนิกซ์อีกโปรแกรมหนึ่งที่ช่วยอำนวยความสะดวกในการรันโปรแกรมแบบนี้

$ screen myprogram

screen จะทำงานคล้าย nohup เพียงแต่ว่าเมื่อเริ่มทำงาน screen จะทำให้ดูเหมือนว่าโปรแกรมของเราทำงานเป็นแบบฟอร์กราวน์ตามปกติทั่วไป เราสามารถสั่งให้ทำงานเบื้องหลังได้ โดยกด CTRL-A CTRL-D ต่อกัน โปรแกรมก็จะไม่เชื่อมต่อกับเทอร์มินัล แต่จะทำงานในแบบแบ็คกราวน์ แต่เราสามารถกำหนดให้ screen เรียกโปรแกรมของเราที่ทำงานอยู่ให้กลับมาอยู่เบื้องหน้าอีกครั้งได้ แม้ว่าเราจะออกจากระบบไป และกลับมาใหม่แล้วก็ตาม

$ screen -r

ในกรณีที่เรียกใช้ screen พร้อมกันหลายๆ โปรเซส ก็อาจจะต้องระบุรหัสโปรเซสที่ต้องการเรียกกลับมาบนเทอร์มินัล โดยอาจจะใช้ออปชัน -ls เพื่อแสดงโปรเซสของ screen ทั้งหมดก็ได้ นอกจากนี้ screen ยังมีออปชันให้เกิดสิ่งที่เป็นผลลัพธ์ทั้งหมดลงไฟล์ไว้ให้ด้วยก็ได้ ทำให้สะดวกในการเรียกดูผลลัพธ์ภายหลัง ผมเองก็ต้องพึ่งโปรแกรม screen เยอะเลยทีเดียวกว่าจะเรียนจบมาได้

รายละเอียดเพิ่มเติม: http://www.gnu.org/software/screen/

Tags: , , , , ,

29 Jul 08 SSH Tunneling

ผมมักจะมีปัญหาเสมอในการใช้งานโปรแกรมต่างๆ ผ่านเน็ตเวิร์ค เนื่องจากที่ทำงานผมติดตั้งไฟร์วอลล์ที่อนุญาตให้ใช้งานหลักๆ ได้แค่ “เว็บ” กับ “ssh” อย่างเช่น ผมไม่สามารถอัพเดตข้อมูลแพคเกจของ MacPorts ได้ เพราะว่ามันใช้วิธี rsync ผ่านพอร์ต 783 ที่ไม่ได้รับอนุญาต ครั้นจะไปขอให้เขาเปิดพอร์ตเพิ่มก็ดูกระไรอยู่ ส่วนใหญ่ผมจึงมักจะอัพเดตข้อมูลแพคเกจที่บ้านก่อนมาทำงาน

จริงๆ แล้ว ถ้าหากเรามีเซิร์ฟเวอร์ที่ใช้งาน ssh ได้อยู่ภายนอก และเซิร์ฟเวอร์เครื่องนั้นไม่มีไฟร์วอลล์ เราจะสามารถใช้วิธี ssh tunneling เพื่อติดต่อจากเครื่องของเราผ่านเซิร์ฟเวอร์ออกไปโลกภายนอกได้

$ ssh xxx.myserver.net -L 1783:rsync.somewhere.com:783

จะเป็นการติดต่อผ่าน ssh ไปยังเครื่อง xxx.myserver.net ตัวเลือก -L ใช้กำหนด ssh tunneling โดยโปรแกรม ssh จะจับข้อมูลที่ส่งไปยังพอร์ท 1783 ของเครื่องเราทั้งหมด ส่งต่อผ่านไปยังเครื่อง xxx.myserver.net และส่งต่อไปยังพอร์ต 783 ของเครื่อง rsync.somewhere.com ทำให้เราสามารถใช้งานเครื่อง rsync.somewhere.com ที่อยู่ข้างนอกได้ โดย ssh ทำตัวเสมือนเป็นอุโมงค์ให้ข้อมูลของเราลอดผ่านไฟร์วอลล์ออกไป

ในทำนองเดียวกัน ผมก็มักจะใช้ ssh tunneling เพื่อต่อเข้ามาใช้งานเน็ตเวิร์คภายในหน่วยงาน เวลาผมอยู่ข้างนอก อย่างเช่น ถ้าผมอยากจะโหลดเปเปอร์จากฐานข้อมูลที่สถาบันเป็นสมาชิก ซึ่งปรกติเขาจะตรวจสอบบุคคลด้วยไอพีแอดเดรส เวลาผมอยู่บ้านผมก็จะแค่ ssh เข้ามาที่เซิร์ฟเวอร์ภายในสถาบันก่อนแล้ว โดยกำหนดให้ forward ข้อมูลไปยังเครื่อง proxy ที่สามารถใช้งานได้ จากนั้นผมก็แค่เซ็ต proxy บนเครื่องผมให้เป็น localhost และใช้พอร์ตที่กำหนด ก็จะดูเหมือนว่าผมใช้งานจากภายในหน่วยงาน ผมสามารถเข้าไปใช้งานฐานข้อมูลได้ตามปกติ

$ ssh xxx.myinternal.net -L 8080:proxy.myinternal.net:8080

โปรแกรม ssh ส่วนใหญ่จะสนับสนุน tunneling หรือบางทีเรียกว่า port forwarding อย่างเช่น บน Windows คนที่ใช้ putty ก็จะมีตัวเลือกให้กำหนดพอร์ตต้นทางและปลายทางได้ ดังรูป

Tags: ,

08 Jul 08 expect

ขอพูดถึงคำสั่ง unix ต่ออีกอันหนึ่งละกัน เนื่องจากเมื่อวานพยายามเขียน script เพื่อสร้าง account แบบเยอะๆ โดยใช้ข้อมูลในไฟล์รายชื่อนักเรียน เพราะว่ามีนักเรียนอยู่ 120 กว่าคน ถ้าให้สร้างด้วยมืออาจจะหมดแรงก่อนได้ ปัญหาเกิดขึ้นตอนกำหนด password ที่แม้ว่าจะมีโปรแกรม pwgen สำหรับสร้าง password ให้โดยอัตโนมัติแล้วก็ตาม แต่โปรแกรม adduser ที่ใช้บน Ubuntu ไม่ยอมให้กำหนด password ไว้เป็น option จึงต้องหาวิธีอื่นๆ ซึ่งก็มีหลายวิธี เช่น เปลี่ยนไปใช้ useradd (ชื่อคล้ายกัน สับสนดีแท้) แทนเนื่องจากกำหนดรายละเอียดต่างๆ ได้เยอะ แต่ก็มีปัญหาที่จะต้องตามไปกำหนดเงื่อนไขต่างๆ ยุ่งยากพอสมควรในขณะที่ adduser มีอีกคนกำหนดค่าต่างๆ ไว้ให้แล้ว อีกวิธีหนึ่งก็จะต้องไปแก้ไฟล์ shadow เพื่อแก้ข้อมูลเอาเอง ซึ่งไม่อยากทำเท่าไหร่ กลัวผิดแล้วมีปัญหา

สุดท้ายมีคนแถวนี้แนะนำให้ใช้โปรแกรม expect ซึ่งสามารถจำลองการทำงานของโปรแกรมที่ต้องมีการตอบโต้กับผู้ใช้ ให้เราสามารถส่งค่าได้เหมือนกับสั่งงาน โดย expect นี้จะใช้ regular expression เพื่อเทียบข้อความที่โปรแกรมแสดงออกมา แล้วจึงสามารถใช้คำสั่ง send เพื่อส่งข้อความหรืออินพุตกลับไปได้ เช่น

#!/usr/bin/expect -f
 
spawn passwd [lindex $argv 0]
set password [lindex $argv 1]
expect "password:"
send "$password\r"
expect "password:"
send "$password\r"
expect eof

จากโปรแกรม คำสั่ง expect เป็นการกำหนดให้รอจนกระทั่งมีข้อความที่กำหนดแสดงขึ้นมา ส่วนคำสั่ง send ก็จะเป็นส่งข้อมูลซึ่งก็คือ password ของเราไปให้โปรแกรม ที่เรียกมาทำงานโดยใช้คำสั่ง spawn ทั้งหมดนี้ไม่ได้เขียนเองหรอก ดูจากที่อ้างอิงได้

อ้างอิง: http://floppsie.comp.glam.ac.uk/Glamorgan/gaius/scripting/5.html

Tags: ,

05 Jul 08 AWK

ตอนนี้พยายามเขียนเรื่องให้มีสาระมากขึ้น มากกว่าแค่แปะลิงก์ แล้วก็กล่าวถึงนิดๆ เพราะลองดูสถิติจาก 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] &lt; [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&lt;=NF; i++)
    print "<td>"$i"</td>"
  print "</tr>\n"
}
 
END {
  print "</table>\n"
}

NF เป็นตัวแปรระบุจำนวนฟิลด์ ซึ่งทำให้เราสามารถใช้ลูปเพื่อพิมพ์ข้อมูลจากทุกๆ ฟิลด์ได้

Tags: , , ,