Tag: shell script

  • Check previous running script using process id

    วันนี้ในกลุ่ม sysadmin บน facebook คุยกันเรื่องของ ubuntu mirror ของ PSU แล้วมีประเด็นของการใช้ script สำหรับการ mirror

    ตัว script ที่ว่านี้ จะเป็น shell script ธรรมดา ที่จะไปเรียกใช้โปรแกรม rsync ที่จะทำหน้าที่ mirror ข้อมูลจาก primary ftp/rsync site ของ ubuntu มาเก็บไว้ที่เครื่อง server ของ PSU ตัว script จะถูกเรียกใช้โดย cron กำหนดไว้ใน crontab และเรียกใช้ 4-6 ครั้งใน 1 วัน (หรือ run ทุกๆ 6 หรือ 4 ชั่วโมง) ระยะเวลาที่ใช้ในการ run script ไม่แน่นอนว่าจะใช้เวลาเท่าไหร่ ขึ้นอยู่กับปริมาณของข้อมูลที่จะต้อง mirror จากต้นทางมายังปลายทาง

    ถึงแม้ว่าการใช้ rsync จะช่วยให้ transfer เฉพาะไฟล์ ที่มีการเปลี่ยนแปลงไป หลังจากการ mirror ครั้งสุดท้ายมา แต่ในบางครั้งจำนวนของข้อมูลที่เปลี่ยนแปลงไป ก็อาจจะมากกว่าปกติ เช่นในช่วงเวลาที่มีการเปลี่ยน release ซึ่งทำให้มีไฟล์ใหม่ๆ เพิ่มขึ้นเป็นจำนวนมาก ทำให้ จากช่วงปกติ ซึ่งอาจจะใช้เวลาเพียง 1-2 ชม. ในการ mirror และ mirror ทุกๆ 4 ชม. ก็เพียงพอ ในช่วงเวลาของการปรับเปลี่ยน release ถ้าจะ mirror ให้เสร็จ ก็อาจจะใช้เวลาถึง 12 ชม. เป็นต้น

    จะเกิดอะไรขึ้น ถ้าในการ mirror ครั้งที่แล้ว ยังไม่เสร็จสิ้นสมบูรณ์ แล้วก็ถึงเวลาของการ mirror รอบถัดไป?

    ถ้าไม่มีการตรวจสอบใดๆเลย กระบวนการของการ mirror ในครั้งถัดมาก็จะเริ่มทำงาน ในขณะที่รอบแรกยังไม่เสร็จ และ ถ้ามีข้อมูลที่ถูกเปลี่ยนแปลงมากจริงๆ ถึงรอบที่ 4-5 ของการ mirror แล้ว … การ mirror ครั้งแรกก็ยังไม่เสร็จเรียบร้อยดี … และพอถึงขั้นนี้แล้ว ระบบจะมีภาระงานสะสมมากขึ้นเรื่อยๆ และระบบเริ่มช้าลง และ จำนวน process ของการ mirror ที่คั้งค้างอยู่ก็จะเพิ่มขึ้นเรื่อยๆ เพราะ process หลังๆ ก็จะไปหน่วงการทำงานของ process แรกๆให้ทำงานช้าลงไปด้วย

    ครั้งแรกที่ผมเจอปัญหาในลักษณะนี้ process ของการ mirror ที่ run ค้างอยู่ทำให้ load ของระบบสูงกว่า 100 โชคยังดีที่ load ที่สูงขึ้นเกิดจาก i/o ของการเขียน disk ซึ่งยังทำให้สามารถ secure shell เข้าไปได้ สามารถ run คำสั่ง ps auxw เพื่อตรวจสอบได้ ถึงแม้จะช้าอยู่มาก แต่ก็ทำให้ทราบว่าปัญหาเกิดจากอะไร และเอาข้อมูลนั้นมาแก้ไขปัญหาในภายหลังได้

    สำหรับปัญหาแบบนี้ วิธีการแก้ไข ก็ไม่ได้ยากอะไร การทำงานของ mirror process ในครั้งหลังที่ถูก start ขึ้นมาด้วย cron ไม่ควรที่จะทำงานต่อ ถ้า process แรกที่ทำงานอยู่ ยังทำงานไม่เสร็จ ควรที่จะปล่อยให้ process แรกทำงานให้เสร็จเท่านั้นเอง

    ในแง่ของ shell script ก็สามารถทำได้ โดยการใช้ lock file ก่อนที่จะเริ่มต้นทำงาน ก็ตรวจสอบดูก่อนว่า มี lock file อยู่หรือเปล่า ถ้ามี ก็แสดงว่ายังมี process เดิมทำงานอยู่ ไม่ต้องทำอะไร ให้ terminate ไป ถ้าไม่มี lock file ก่อนที่จะเริ่มต้นทำงาน ก็สร้าง lock file ขึ้นมา เพื่อบอกว่า กำลังทำงานนี้อยู่ หลังจากทำงานเสร็จแล้ว ก็ลบ lock file นั้นทิ้ง เพื่อบอกว่า process ทำงานเสร็จแล้ว

    เขียนเป็น script คร่าวๆ ได้ประมาณนี้ครับ

    LOCK="/tmp/script-name.lock"
    if [ -f "$LOCK" ]; then
        # lock file exist
        echo "Previous process is still running..."
        exit  -1 # Terminate script here
    fi
    
    # No other process, we do our job as usual
    
    ...
    
    # end of our duty, do cleaning up, remove lock file
    
    rm -f $LOCK
    
    exit 0  # Terminate normally

    วิธีการนี้ เป็นวิธีการที่ใช้อยู่แล้ว สำหรับ mirror script ของ ubuntu แต่ล่าสุดนี้ มีปัญหาเกิดขึ้นก็คือ process ของการ mirror ที่ควรจะ run เป็นระยะๆ กลับหยุดทำงานไปหลายวัน

    ปัญหาที่เกิดขึ้นก็คือ script ที่ cron เรียกให้ทำงานนั้น ไม่ได้ทำงานเสร็จ ตามปกติของมัน ซึ่งจะมีการลบ lock file ทิ้งไป ซึ่ง สาเหตุอาจจะเกิดขึ้นได้จากหลายๆกรณี ในส่วนที่ผมเคยเจอ ก็คือ “ไฟดับ” ระบบที่ผมดูแลอยู่ในตอนนั้นถึงจะมี UPS backup แต่ไม่มีส่วนของการ monitor UPS และควบคุมให้ shutdown เครื่องแบบอัตโนมัติ ถ้าเกิดไฟดับนานเกินไป และ battery ของ UPS ไม่สามารถจ่ายไฟฟ้าให้นานพอจนกระทั่งไฟฟ้ากลับมาเป็นปกติได้ ตัว script ก็ตายไปกลางคัน พร้อมๆกับเครื่อง และ lock file นั้นก็ไม่ได้ถูกลบไป ทำให้ script ที่ run โดย cron ในรอบถัดไปไม่สามารถทำงานในส่วนของการ mirror ได้ เพราะ lock file ยังอยู่ (ตัว lock file ไม่ได้เก็บไว้ใน /tmp หรือ /var/lock) ส่วนกรณีอื่นๆ ก็จะเป็นกรณีที่ script เกิดตายไปกระทันหันโดยสาเหตุอื่นๆ เช่น out of memory หรือ process ถูก kill โดยกรณีอื่นๆ และ lock file ก็ถูกทิ้งค้างเอาไว้

    ตัว script ที่ผมใช้ในสมัยหลัง ก็เลยมีส่วนของการตรวจสอบเพิ่มขึ้นมาอีกอย่างหนึ่ง ก็คือ นอกจากจะ สร้าง lock file แล้ว ก็จะเก็บ process id ของ script เอาไว้ใน lock file นั้นด้วย ในการทำงานของ script ส่วนของการตรวจสอบว่า process ของ script ที่ทำงานอยู่ก่อนหน้านี้ ยังทำงานอยู่หรือเปล่า นอกจากตรวจสอบว่ามี lock file แล้ว ก็จะตรวจสอบว่า process ของ lock file นั้น ยังมีอยู่ในระบบหรือเปล่า

    ส่วนของการเก็บ process id ของ script นั้น ใช้วิธีการ

        echo "$$" > $LOCK

    ได้เลยตัว special shell variable ‘$$’ จะเป็นหมายเลข process id ของ shell ที่ใช้ในการ run script ตัวนั้น

    ส่วนการตรวจสอบว่า process นั้นยังทำงานอยู่หรือเปล่า ก็ใช้ process id ที่เก็บอยู่ใน lock file เอามาตรวจสอบ ซึ่งเราอาจจะตรวจสอบ โดยการใช้คำสั่ง

        ps ax | grep $previous_pid | grep -v grep

    ดูก็ได้ แต่จริงแล้ว ก็มีวิธีง่ายกว่านั้น ก็คือ สำหรับ Linux แล้ว จะมี virtual directory ที่ชื่อว่า /proc และ ทุกๆ process จะถูกสร้างเป็น sub directory ใน /proc โดยใช้ชื่อเป็นหมายเลขของ process id ดังนั้น แทนที่เราจะตรวจสอบโดยการใช้คำสั่ง ps ซึ่งจะต้องส่ง ourput ไปให้คำสั่ง grep อีก 2 รอบ ก็สามารถตรวจสอบเพียงแค่ว่ามี directory นั้นอยู่หรือเปล่าได้เลย โดยการใช้

        if [ -d /proc/$previous_pid ]; then
        ...
        fi

    ตัวอย่าง script ที่ใช้งานวิธีการนี้

    #!/bin/sh
    TASKNAME="this"
    LOCK="/tmp/${TASKNAME}.LCK"
    LOG="/tmp/${TASKNAME}.log"
    
    lock() {
        if [ -f $LOCK ]; then
           D=`date`
           task_pid=`cat $LOCK`
           if [ -d "/proc/$task_pid" ]; then
              # it's possible that this_pid is different task, but
              # it is very unlikely.
              echo "$D : Previous process (pid: $task_pid) is running"
              exit
           else
              # Lock is not clean up properly, assume
              echo "$D : clean up previous lock file (pid: $task_pid)"
           fi
        fi
        echo $$ > $LOCK
    }
    
    unlock() {
        rm -f $LOCK
    }
    
    do_myjob() {
        START=`date`
        sleep 10    # This is the real 'task' of this script
        STOP=`date`
        echo "Process start at: $START" >> $LOG
        echo "Process stop at : $STOP"  >> $LOG
    }
    
    lock
    do_myjob
    unlock

    ใน script ตัวอย่างข้างต้น สมมติ save ให้อยู่ในชื่อว่า t และกำหนด permission ให้สามารถ execute ได้ โดยใช้

    $ editor t
    $ chmod +x t

    เราสามารถ ตรวจสอบการทำงานของ script ดูได้ โดย ทดลอง run

    $ ./t

    ซึ่งมันจะรอเวลา 10 วินาที ตาม “sleep 10” ในฟังก์ชัน do_myjob() ก่อนที่จะกลับมาที่ shell prompt อีกครั้งนึง และในไฟล์ /tmp/this.log จะแสดง เวลาเริ่มต้นทำงาน และ ทำงานเสร็จ
    ถ้า run script ด้วยคำสั่ง

    $ ./t & ./t

    ตัว script ตัวแรกจะทำงานตามปกติ แต่ตัวที่สอง จะแสดงข้อความว่า “…Previous process (pid: xxxxx) is running” และ เราจะไม่สามารถ run script ครั้งที่สองได้ จนกว่า process ที่ run ค้างอยู่ทำงานเสร็จแล้ว และ ถ้าตรวจสอบใน log file ก็จะเห็นว่า การทำงานของ script จะเป็น [start, stop], [start, stop] ไปเรื่อยๆ ไม่มีการซ้อนเหลือมกัน

    แต่ใน script ตัวนี้ ถ้าหาไป comment บรรทัด lock; do_myjob; unlock เป็น

    # lock
    do_myjob
    # unlock

    แล้วทดลอง run script

    $  ./t & ./t

    ก็จะเห็นว่า script สามารถ run ซ้อนกันได้ และ ใน log file ก็จะเห็นเป็น star, stat, stop, stop ซ้อนเหลื่อมกัน ซึ่งเป็นกรณีที่เราไม่ต้องการให้เกิดขึ้น

    script ตัวนี้ อาจจะไม่ได้สมบูรณ์ 100% ในการใช้การตรวจสอบว่า มี process เดิมยังทำงานค้างอยู่หรือเปล่า เพราะมีความเห็นไปได้ว่า process id ที่มีอยู่และตรวจสอบจาก /proc ได้นั้น ไม่ได้เป็น process ของ script ตัวนี้ ที่ทำงานไปในครั้งที่แล้ว และยังทำงานไม่เสร็จ แต่เป็น process อื่นๆ ซึ่งไม่เกี่ยวข้องกับ script ตัวนี้เลยก็เป็นไปได้ เพราะ process id จะมีการเอากลับมาใช้ใหม่ หลังจากใช้ไปจนครบแล้ว แต่ โอกาสเช่นนั้น จะเกิดขึ้นได้น้อยมาก (process id เป็น process id เดียวกับ script ที่ terminate ไปแล้วแบบผิดปกติ และ process ที่ไม่เกี่ยวข้องทำงานอยู่ในช่วงเวลานี้พอดี) การตรวจสอบเพ่ิมเติม จะทำให้ script มีความซับซ้อนมากขึ้น โดยใช่เหตุ ก็เลยทิ้งไว้เท่านี้ครับ ผู้ที่สนใจ อาจจะตรวจสอบโดยใช้ ข้อมูลอื่นๆ ใน /proc/$process_id เช่น cmdline ดูได้ ทิ้งไว้ให้ไปลองทำเป็นการบ้านดูครับ 🙂

  • Script สำหรับการ reset mysql root’s password

    สำหรับ admin ของ Linux Server โดยทั่วไปแล้ว มักจะหลีกไม่พ้นที่จะต้อง ติดตั้ง database server สักครั้งนึง เพราะว่า สำหรับ application โดยส่วนใหญ่แล้ว โดยเฉพาะ web application ในปัจจุบัน มักจะต้องการใช้ database server เป็น backend สำหรับการเก็บข้อมูล

    และบน Linux ส่วนใหญ่ก็จะหนีไม่พ้นการใช้งาน MySQL เป็น Server … เพราะเป็นตัวที่มีคนใช้กันมากที่สุด และรองรับโดยหลายๆ framework ของ Web App ทั้งหลาย

    แน่นอน ตอนที่ติดตั้งครั้งแรก ตัวซอฟต์แวร์ที่ใช้ในการติดตั้ง ก็ถาม root password ของ MySQL สำหรับการ กำหนดสิทธิในการใช้งาน

    สำหรับคนที่ใช้งาน database server มานานระดับหนึ่ง ก็จะรู้ว่า password ที่จะใช้สำหรับ root ของ MySQL นั้น ไม่จำเป็นจะต้องเป็นตัวเดียวกับ root password ของระบบ … และ ไม่ควรที่จะเป็นตัวเดียวกัน … สำหรับคนที่ไม่รู้ในเรื่องนี้ ก็มักจะต้อง “เรียนรู้” ด้วยความยากลำบากในทีหลัง … ผมรู้สิน่า ผม “จ่าย” ค่าเล่าเรียนเรื่องนี้ด้วยราคาที่ “แพง” พอสมควรเชียวล่ะ

    หลังจากที่รู้แล้วว่า มัน “จะต้อง” ไม่ใช่ password เดียวกันกับ password ที่สำคัญของระบบ ปัญหาที่จะตามมาก็คือว่า เราก็อาจจะตั้ง password ของ root ของ MySQL แบบง่ายๆ เช่น “1234”, “abcd” อะไรทำนองนี้ เพราะว่า ในแง่ของการให้บริการ MySQL server ก็มักจะให้บริการกับ “client” ท่อยู่บนเครื่องเดียวกัน ไม่ได้ให้บริการผ่าน network ไปให้เครื่องอื่นๆ หรือ ถ้าจำเป้นจะต้องให้บริการ ก้ให้บริการกับ server  ที่อยู่ใน cluster เดียวกัน จะมีเฉพาะ admin ด้วยกันที่จะรู้ว่ามี mysql ให้บริการอยู่บนเครื่องนี้

    แน่นอน ความคิดเช่นนี้ ก็มักจะนำบทเรียน “ราคาแพง” บทที่สอง เกี่ยวกับ root password ของ mysql มาให้ อีกเหมือนกัน และมันมักจะพ่วงมากับ “phpmyadmin” ที่จะทำให้ ตัว mysql server ซึ่งเคยเข้าใจว่า “ไม่สามารถเข้าถึงจาก server อื่นๆได้” กลับเข้าถึงได้ง่ายๆ ผ่าน “web” และเมื่อเจอกับ bot ที่โจมตีแบบอัตโนมัติ หายนะ … ก็มาเยือนได้ง่ายๆ เหมือนกัน

    นั่นก็จะเป็นบทเรียนราคาแพงอีกบทนึงเช่นกัน

    ข้อสรุปของผม หลังจากได้บทเรียนอย่างนั้นมาก็คือ password ของ root ไม่ว่าจะเป็นของระบบเอง หรือ จะเป็นของ database “จะต้อง” เป็นคนละตัวกัน  _และ_ จะต้องมีระดับของความปลอดภัยสูงใกล้ๆกัน

    ได้ password ที่ secure มา … ก็ดี … แต่ปัญหาของ password ที่ secure ก็คือ จำยาก … สำหรับ root’s password ของระบบ ที่ยังมีการใช้งานค่อนข้างบ่อย ก็จะมีโอกาสที่จะลืมได้น้อยกว่า … วิธีการที่จะทำให้ ไม่ลืม และยังมี password ที่ secure ก็มีแน่ๆ แต่นั่นไม่ใช่สิ่งที่ผมอยากจะพูดถึงในบันทึกนี้ (อันที่จริงก็เขียนเอาไว้ที่อื่นแล้วด้วย)

    ที่จะมาแก้ปัญหาในบันทึกนี้ ก็คือ password ของ root ของ MySQL … โอเค … ก็รู้แล้วล่ะว่าจะต้อง set ให้มัน secure … ก็ set ไปแล้ว แต่ก็ลืมไปแล้วด้วย (ฮา) … งั้นทำไงดีล่ะ?

    ครับ ถ้าลืม root password ไม่ว่าจะเป็นของระบบเอง หรือ จะเป็นของ MySQL server ถ้าจะให้สามารถ “ยึดอำนาจ” ของ root กลับมาได้ ก็คงต้องใช้วิธีการ reset password กัน

    วิธีการ reset password ของ root ระบบที่ใช้ Linux ที่ไม่ได้การ encrypt ระบบไฟล์เอาไว้ และ ไม่ได้ lock password ที่ระดับของ bios ก็ไม่ได้ยุ่งยากอะไรมาก … แต่ก็ไม่ได้เป็นเรื่องที่ตั้งใจจะเขียนในบันทึกฉบับนี้อีกเหมือนกัน และคิดว่า admin แทบทุกท่านก็น่าจะรู้วิธีการกันอยู่ดีพอสมควรแล้ว

    ที่ตั้งใจจะพูดถึงก็คือ การ reset password ของ root ของ MySQL Server

    ซึ่งอันที่จริง ก็ไม่ได้เป็นความลับอะไร ถ้าลอง search หาด้วย google ก็จะเจอในระยะไม่เกิน 2-3 click ของ mouse

    แต่โดยตัวผมเอง ที่จำเป็นจะต้อง เกี่ยวข้องกับ MySQL Server บนหลายเครื่อง และเกิดเหตุการณ์จำเป็นให้ต้อง reset password ของ MySQL อยู่ 2-3 รอบ ในช่วงไม่กี่เดือนที่ผ่านมา ก็ชักจะรู้สึกหงุดหงิอยู่นิดหน่อย ตรงที่ว่า วิธีการที่มี มันจะต้องทำแบบ manual และ จะต้องเปิด terminal ขึ้นมามากกว่า 2 terminal หรือ 2 tab เพื่อที่จะจัดการให้เสร็จเรียบร้อย เพราะจะต้อง run โปรแกรม server บน terminal นึง และ run โปรแกรม client อีก terminal นึง ขนานกันไป

    ทำอยู่ หลายรอบ … รอบสุดท้ายที่ทำ ก็ชักเบื่อ แล้วก็ถามตัวเองว่า จะเขียนมันเป็น script แล้ว run ครั้งเดียวเสร็จ ไม่ได้เชียวหรือ?

    ลองกลับไป ล้วง,แคะ,แกะ,เกา shell script และตัว mysqld กับ mysql client ใหม่อีกรอบ ก็ทำให้ได้ script ตัวนี้มาครับ

    #!/bin/sh
    
    PASSWD="$1"
    [ -z "$PASSWD" ] && echo "Usage: $0 newpassword" && exit
    
    [ `id -u` != "0" ] && echo "Must be root!" && exit
    
    # Is mysql installed?
    [ ! -x /usr/bin/mysql ]    && \
        echo "[?] Is mysql-server & mysql client installed?" && \
        exit
    
    RUNNING=0
    # is mysql server running?
    if [ "`ps auxw | grep [/]usr/sbin/mysqld`" ]; then
        echo "[*] mysqld is running, try to stop it..."
        RUNNING=1
        # yes, then shutdown it
        service mysql stop
        for i in `seq 1 10`; do
            [ "$i" = "10" ] && {
                echo "[?] Hmm? can't stop mysqld server";
                exit;
            }
            [ -z "`ps auxw | grep [/]usr/sbin/mysqld`" ] && {
                echo "[*] mysqld stopped!";
                break;
            }
            echo -n "."
            sleep 1
        done
        echo ""
    fi
    
    # Start mysqld not using grant tables
    echo "[*] Start mysqld without using grant tables"
    /usr/sbin/mysqld --skip-grant-tables 2>/dev/null &
    MPID=$!
    
    echo "[*] Wait for mysqld to be ready ..."
    sleep 5
    
    echo "[*] Now, set new password..."
    cat <<EOT | /usr/bin/mysql
    UPDATE mysql.user SET Password=PASSWORD("$PASSWD") WHERE User='root';
    FLUSH PRIVILEGES;
    EOT
    
    echo "[*] Done! now stop the mysqld again ..."
    kill -QUIT $MPID
    sleep 3
    
    [ "$RUNNING" = "1" ] && {
        echo "[*] Restart mysqld using normal running method..";
        service mysql start;
    }

    วิธีการ ใช้งาน  ก็ save โปรแกรมลงในไฟล์ สมมติว่าชื่อ reset-mysql-root-password

    $ chmod +x bin/reset-mysql-root-password
    $ bin/reset-mysql-root-password NewSecretPasswd2013

    ครับ เรียกใช้โปรแกรมตามด้วย password ที่ต้องการจะให้ set ใหม่แค่นั้นเองครับ

    ตัว script เขียนขึ้นมาเพื่อให้ใช้งานได้กับ Debian และ Ubuntu

    ผมใช้ delay เพื่อรอให้การ start/stop ของ mysqld เสร็จเรียบร้อยก่อนที่จะทำงานต่อไป ซึ่งก็ขึ้นอยู่กับว่า load ของเครื่อง และความเร็วของเครื่องที่ใช้อยู่เป้นอย่างไร ถ้ามีปัญหา ก็อาจจะต้องปรับเพิ่มค่า delay ที่มีอยู่ใน script ให้มากขึ้นกว่าเดิมอีกหน่อยครับ

    จริงๆแล้วผมควรที่จะอธิบายเพิ่มว่า เทคนิคที่ใช้ใน script มีอะไรบ้าง แต่ก็คาดว่า หลายๆคนที่เตยเจอปัญหาแบบเดียวกัน ก็น่าจะเคยอ่านจาก document ของ MySQL เองแล้ว ก็น่าจะไม่จำเป็นมาก ที่ผมต้องการจะเสนอในที่นี ก็คือ tool เป้น shell script ที่จะช่วยให้ทำงานให้ง่ายขึ้น แทนที่จะต้องใช้วิธีการแบบ manual น่ะครับ

    แต่ยังไงถ้ามีข้อสงสัย ก็ comment / ถามมาก็แล้วกันนะครับ ผมจะกลับมาตอบ ถ้ามีคำถามครับ 🙂

  • เปลี่ยน uptime timestamp ในคำสั่ง dmesg ให้เป็น date/time timestamp

    เคยใหมครับ เมื่อใช้คำสั่ง dmesg เพื่อที่จะดู kernel message แล้วสงสัยว่า เวลาที่แสดงในเครื่องหมาย square bracket ที่อยู่ด้านหน้าของ message น่ะมันเป็นเวลาเท่าไหร่กันแน่?

    อาจจะไม่บ่อยนัก แต่ผมก็เคยมีปัญหานั้น

    ถ้าใช้คำสั่ง dmesg บน command line เราอาจจะเห็น message ประมาณนี้

     [   10.738140] udev[228]: starting version 164
     [   12.840798] ACPI: PCI Interrupt Link [LNKD] enabled at IRQ 9
     [   12.842168] PCI: setting IRQ 9 as level-triggered
     [   12.843002] pci 0000:00:04.0: PCI INT A -> Link[LNKD] -> GSI 9 (level, low) -> IRQ 9
     [   12.856454] input: PC Speaker as /devices/platform/pcspkr/input/input2
     [   12.911121] vboxguest: major 0, IRQ 9, I/O port d020, MMIO at 00000000f0400000 (size 0x400000)
     [   12.912531] vboxguest: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   13.165633] piix4_smbus 0000:00:07.0: SMBus base address uninitialized - upgrade BIOS or use force_addr=0xaddr
     [   13.233473] parport_pc 00:05: reported by Plug and Play ACPI
     [   13.314410] ACPI: AC Adapter [AC] (on-line)
     [   13.318154] input: Power Button as /devices/LNXSYSTM:00/LNXPWRBN:00/input/input3
     [   13.345820] ACPI: Power Button [PWRF]
     [   13.376871] input: Sleep Button as /devices/LNXSYSTM:00/LNXSLPBN:00/input/input4
     [   13.420217] ACPI: Sleep Button [SLPF]
     [   13.455000] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8042/serio1/input/input5
     [   15.017345] Intel ICH 0000:00:05.0: PCI INT A -> Link[LNKA] -> GSI 5 (level, low) -> IRQ 5
     [   15.019308] Intel ICH 0000:00:05.0: setting latency timer to 64
     [   15.344130] intel8x0_measure_ac97_clock: measured 54439 usecs (10177 samples)
     [   15.346177] intel8x0: measured clock 186943 rejected
     [   15.704139] intel8x0_measure_ac97_clock: measured 55930 usecs (10470 samples)
     [   15.705569] intel8x0: measured clock 187198 rejected
     [   16.064125] intel8x0_measure_ac97_clock: measured 55919 usecs (10451 samples)
     [   16.065415] intel8x0: measured clock 186895 rejected
     [   16.066703] intel8x0: clocking to 48000
     [   17.234971] EXT3 FS on dm-0, internal journal
     [   17.967548] loop: module loaded
     [   23.754984] Adding 8388600k swap on /dev/mapper/lv-nilanont--swap.  Priority:-1 extents:1 across:8388600k
     [   30.580221] kjournald starting.  Commit interval 5 seconds
     [   30.581590] EXT3 FS on dm-2, internal journal
     [   30.582599] EXT3-fs: mounted filesystem with ordered data mode.
     [   31.094231] FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
     [   35.777014] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
     [   39.640499] fuse init (API version 7.13)
     [   40.373844] binfmt_misc: Unknown symbol bprm_change_interp
     [   40.469771] binfmt_misc: Unknown symbol bprm_change_interp
     [   41.587335] vboxsf: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   52.668229] lp: driver loaded but no devices found
     [   52.742090] ppdev: user-space parallel port driver
     [   64.384194] device eth0 entered promiscuous mode
     [   66.379973] device eth0 left promiscuous mode
     [   73.139965] tun: Universal TUN/TAP device driver, 1.6
     [   73.141787] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>

    ตัวเลขที่อยู่ภายใน [ ] เช่น “[   73.141787]” ในบรรทัดสุดท้าย 73.141787 หมายความว่า ข้อความของ kernel ในบรรทัดนั้น

    tun: (C) 1999-2004 ...

    พิมพ์ออกมาหลังจากเครื่องเริ่มต้นทำงานไปแล้วประมาณ 17 วินาทีกับอีกนิดหน่อย 141787 ที่อยู่ข้างหลัง จะแสดงเวลาที่เป็นหน่วยย่อยในระดับ micro-second

    ซึ่งการแสดงผลแบบนี้ จะไม่มีปัญหาซักเท่าไหร่ สำหรับเครื่องที่เพิ่งเปิดขึ้นมาใหม่ๆ เช่นเครื่อง desktop หรือ notebook ทั่วไป แต่สำหรับเครื่องที่ทำงานเป็น Server ซึ่งเปิดทิ้งเอาไว้เป็นชั่วนาตาปี, โขง, ชี, มูล, ปิง, วัง, ยม, น่าน นั้น บางทีเวลาเราใช้คำสั่ง dmesg ออกมา อาจจะเห็นเป็น message แบบนี้

     ...
     [201089.204812] br0: topology change detected, propagating
     [201089.204815] br0: port 1(eth0) entering forwarding state
     [201089.204838] br0: port 1(eth0) entering forwarding state
     [237531.522878] br0: port 1(eth0) entering forwarding state
     [237531.671857] r8169 0000:02:00.0: eth0: link down
     [237531.671888] r8169 0000:02:00.0: eth0: link down
     ...

    จะเห็นว่าบรรทัด “br0: port 1(eth0) enter forwarding state” ในสองบรรทัดแรก กับบรรทัดที่สาม จะเกิดห่างกันพอสมควร ระยะเวลาห่างกันเท่าไหร่ เป็นเวลากี่วินาที ก็เอาตัวเลขทั้งสองตัวมาหักลบกันได้ ยุ่งยากนิดหน่อย แต่ก็ไม่มากจนเกินไป

    แต่ปัญหาที่เกิดขึ้นก็คือ หลายครั้ง ผมอยากจะรู้ว่า “มันเกิดขึ้นเมื่อไหร่?” มากกว่า ที่อยากจะรู้ว่า “มันเกิดขึ้นหลังจากครั้งที่แล้วนานเท่าไหร่?”

    อืม แล้วถ้าเป็นอย่างนั้นผมจะหาคำตอบได้ยังไง?
    เนื่องจากเวลาที่คำสั่ง dmesg แสดงออกมา เป็นเวลานับเป็นจำนวนวินาที นับตั้งแต่เครื่องเริ่มต้นทำงาน เพราะฉะนั้น ถ้าเรา “รู้” ว่าเราเริ่มเปิดเครื่องเมื่อไหร่ เราก็สามารถ ใช้เวลานั้นเป็นเวลาตั้งต้น แล้วบวกเวลาด้วยจำนวนวินาทีที่ผ่านไป แล้วค่อยแปลงเวลานั่นกลับมาเป็นวันเดือนปีอีกครั้ง เราก็จะรู้เวลาที่เกิดเหตุการณ์นั้นขึ้นใช่ใหมครับ

    ฟังดูไม่ยาก … รึเปล่า?

    เวลาที่เครื่องเริ่มต้นทำงานเราจะเอามันมาจากใหนได้บ้างล่ะนี่?

    ถ้าเครื่องของเราเพิ่ง reboot มาเมื่อไม่นานเท่าไหร่ เราอาจจะใช้คำสั่ง last ในการหาเวลา boot ของเครื่องได้โดยประมาณแบบนี้

     $ last | grep reboot
     reboot   system boot  3.2.0-4-amd64    Tue Mar 19 18:14 - 02:25 (6+08:10)

    อืม … เครื่องของผมเพิ่ง reboot ไปเมื่อวันที่ 19 มีค. เวลา 18:14 และ เวลาขณะที่เขียนบันทึกฉบับนี้ เวลาผ่านไปแล้ว 6 วัน 8 ชม. กับอีก 10 นาที … แต่ว่า วิธีการนี้จะมีปัญหากับเครื่อง boot มาแล้วตั้งแต่เดือนที่แล้ว หรือ นานกว่านั้น เพราะ /var/log/wtmp จะเก็บข้อมูลเฉพาะเดือนนี้ และ โดยทั่วไปแล้วจะมีข้อมูลเก็บย้อนหลังอีกแค่ 1 เดือนเท่านั้น (สำหรับ server ที่ผมดูแลอยู่ ข้อมูลของ wtmp จะระบุใน logrotate config ให้เก็บย้อนหลังไปอย่างน้อย 12 เดือน) … ไม่ว่าอย่างไร วิธีการนี้จะมีปัญหาถ้าต้องการที่จะได้ข้อมูลที่ถูกต้องแน่นอนทุกครั้ง

    งั้นมีวิธีใหนอีกบ้าง? คำสั่ง uptime ล่ะ

     $ uptime
     02:31:56 up 6 days, 8:17, 23 users, load average: 1.00, 0.98, 0.95

    แต่ … เอ วิธีการนี้เราจะได้มาแค่ ระยะเวลาที่เครื่องทำงานอยู่เท่านั้นนี่นา ไม่ได้บอกว่าเครื่อง ‘เริ่ม’ ทำงานเมื่อไหร่ … ใช่ใหม?

    ใช่แล้วครับ! แต่เรามีคำสั่งที่จะช่วยอีก 1 คำสั่งคือคำสั่ง date

     $ date;uptime
     Tue Mar 26 02:33:05 ICT 2013
     02:33:05 up 6 days, 8:18, 23 users, load average: 0.85, 0.95, 0.94

    เอาล่ะ คราวนี้ เราได้เวลา ‘ขณะนี้’ ของเครื่องออกมาแล้ว จากคำสั่ง date และเราได้ ‘ระยะเวลาที่เครื่องทำงาน’ มาจากคำสั่ง uptime เพราะฉะนั้น ถ้าผมนับย้อนหลังจากเวลา ‘ขณะนี้’ ไปเท่ากับเวลา ‘uptime’ ผมก็จะได้เวลาที่เครืองเริ่มต้นทำงานขึ้นมา … ใช่ใหมครับ

    start_time = "Tue Mar 26 02:33:05 ICT 2013" - "6 days, 8:18"

    เอ่อ … จะง่ายไปใหมเนี่ย เอามันมาลบได้อย่างนั้นน่ะ? ถ้าทำอย่างได้จริง ชีวิตของ sysadmin ก็คงง่ายขึ้นครับ แต่ชีวิตจริงของ admin มันไม่ง่ายดายขนาดนั้น

    แต่ถ้าจะให้ผมแปลง string ของวันที่กลับมาเป็นตัวเลข และแปลง string ของระยะเวลา กลับมาเป็นตัวเลข โดยใช้ shell script เองก็คงไม่ไหวเหมือนกันแหละครับ 🙂

    บน unix เรามี tools ซึ่งช่วยในการจัดการเรื่อง วัน/เวลาอยู่แล้ว คำสั่งนั้นก็คือ คำสั่ง date เพื่อนเก่าของเราน่ะเอง

    ถ้าเราใช้คำสั่งนี้แบบธรรมดา เราก็จะได้ string ของ ข้อความที่เป็นแบบที่เรียกว่า human readable date string ออกมาตามตัวอย่าง  เราสามารถระบุให้คำสั่ง date แสดงข้อความออกมาเป็นรูปแบบอื่นๆได้อีกด้วย เช่น แสดงเวลานับเป็นวินาทีเริ่มจาก เวลาอ้างอิงของระบบปฏิบัติการแบบ unix ที่เรียกกันโดยทั่วไปว่า epoch ซึ่งเวลาที่ว่านั่นก็คือ 1970-01-01 00:00:00 … ถ้าจะถาม ว่าทำไมเวลาอ้างอิง หรือ epoch ถึงต้องเป็นวัน/เวลานี้ด้วย  คำตอบคงจะต้องเท้าความถึงประวัติศาสตร์ของ Unix ค่อนข้างไกล … ซึ่งคงจะออกนอกเรื่องไปอีกพอสมควร เพราะฉะนั้นขอละไว้เพียงแค่นี้ก่อนก็แล้วกันครับ ผู้ที่สนใจลองหาอ่านเองโดยใช้ google ช่วยได้ครับ

    คำสั่ง date สามารถแสดงผลออกมาได้หลากหลายรูปแบบมาก รายละเอียดสามารถอ่านได้จาก man page ของคำสั่ง date นะครับ แต่สำหรับกรณีของการแสดงเวลาเป็นวินาทีนับจากเวลาอ้างอิง ก็สามารถใช้คำสั่ง

    date +%s

    รูปแบบ +%… จะเป็นการกำหนดรูปแบบการแสดงผล นะครับ ตัวอย่างเช่น บนเครื่องของผมขณะที่เขียนบันทึกอยู่ขณะนี้

     $ date;date +%s
     Tue Mar 26 02:50:49 ICT 2013
     1364241049

    เวลา 1364241049 วินาทีนับจาก epoch ก็เป็นเวลาเดียวกันกับ Tue Mar 26 02:50:49 ICT 2013 เพียงแต่แสดงในรูปแบบที่ต่างกัน

    เอาล่ะเราได้เวลาที่เป็น เวลาปัจจุบัน แสดงออกมาเป็นวินาทีแล้ว … แล้วทีนี้ ค่าเวลา uptime ล่ะ เราจะแปลง “6 days, 8:18” หรือ 6 วัน 8 ชม. 18 นาที มาเป็นตัวเลขวินาทียังไง … ดูแล้วก็น่าจะไม่ยากมากเท่าไหร่ … แต่ก็ไม่ง่าย …(ฮา)

    จริงๆแล้ว ก็มีวิธีง่ายๆอยู่ครับ ค่าของ uptime เราสามารถอ่านมาจากไฟล์ /proc/uptime ได้ โดยไม่ต้องไปแปลงจาก string ของ “N days, HH:MM” โดยตรง

     $ cat /proc/uptime
     549627.40 1106209.61

    ตัวเลขตัวแรกเป็น uptime ส่วนตัวที่สองเป็น cpu idle time

    เอ๊ะ.. ทำไม idle time ถึงมากกว่า uptime ล่ะ? … คือว่าเครื่องที่ผมใช้งานอยู่มันมี core ของ processor อยู่จำนวน 4 core ใน 1 cpu น่ะครับ ค่า idle time นับของ cpu ทุกตัวรวมกัน ตัวเลขก็เลยออกมาสูงกว่า cpu uptime

    เอาล่ะ เราสามารถ หาวัน/เวลา ที่เครื่องเริ่มต้นทำงานได้ โดยการใช้คำสั่งของ shell ได้คร่าวๆประมาณนี้ครับ

     NOW=`date +%s`
     UPTIME=`cat /proc/uptime | cut -f1 -d.`
     STARTTIME=`expr $NOW - $UPTIME`
     echo $STARTTIME

    เวลาของ UPTIME เราใช้เฉพาะหน่วยวินาทีโดยการใช้ จุดเป็นตัวคั่น แล้วตัดส่วนที่เหลือทิ้งไปทั้งหมด (รวมทั้ง idle time ด้วย) ส่วนการคำนวณ ก็ใช้คำสั่ง expr เป็นตัวช่วย

    แต่เวลาที่ได้เป็นตัวเลข จำนวนเป็นวินาทีนี่!! ดูไปก็ยังไม่รู้ว่ามันเป็นวัน/เวลาใหนกันแน่ … จะต้องแปลงตัวเลขกลับยังไงนี่

    ตรงนี้ไม่ยากครับ ใช้คำสั่ง date อีกนั่นแหละ เราสามารถใช้ option -d หรือ –date= สำหรับการระบุเวลาที่ต้องการเป็นเวลาอื่น แทนที่จะเป็นเวลา “ปัจจุบัน” ได้ โดยที่ ถ้าเราจะระบุเวลาเป็น จำนวนวินาทีนับจากเวลาอ้างอิง — epoch ก็ให้ใส่เครื่องหมาย @ หน้าตัวเลขนั้นครับ ซึ่ง สามารถลองได้ประมาณนี้

     $ date -d @0
     Thu Jan  1 07:00:00 ICT 1970
     $ date -d @1363691682
     Tue Mar 19 18:14:42 ICT 2013

    ถ้าใช้ @0 นั่นก็คือระบุเวลาอ้างอิงเลย ก็จะได้เป็นวันที่ 1 มค. 1970 เวลา 00:00:00 UTC — เวลาที่แสดงเป็น 07:00:00 ICT ซึ่งเป็นเวลาของประเทศไทย (=GMT+7)  และ เวลาที่เครื่องคอมพิวเตอร์ที่ผมใช้ reboot ครั้งสุดท้ายเมื่อ วันที่ 19 มีค. เวลา 18:14 น.

    ส่วนที่เหลือก็คือ รับข้อมูลแต่ละบรรทัดจาก คำสั่ง dmesg เข้ามาแล้วก็แปลงข้อมูลของ time stamp ที่อยู่ในรูปแบบ uptime ซึ่งอยู่ระหว่างเครื่องหมาย [] ข้างหน้าออกมา ส่วนนี้ สามารถใช้คำสั่ง read ช่วย เช่น dmesg บรรทัดสุดท้ายได้ข้อมูลออกมาเป็น

     $ dmesg | tail -1
     [288163.997319] br0: port 1(eth0) entering forwarding state

    คำสั่ง read จะอ่านค่ามาเก็บไว้ในตัวแปร โดยจะแบ่งโดยใช้ space

     $ dmesg | tail -1 | (read STAMP REST; echo "STAMP=$STAMP REST=$REST")
     STAMP=[288163.997319] REST=br0: port 1(eth0) entering forwarding state

    ในกรณีนี้ เราจะได้ค่าเวลา ซึ่งมี เครื่องหมาย [] คร่อม อยู่ในตัวแปร shell ชื่อ STAMP และ ส่วนที่เหลือทั้งหมด จะอยู่ในตัวแปร shell ที่ชื่อว่า REST

    ในส่วนของ เวลา อาจจะอยู่ในรูปแบบ  [ 0.00000] ก็ได้ ซึ่งจะมีช่องว่างอยู่หลัง ‘[‘ และก่อนตัวเลข ซึ่งสามารถกำจัดทิ้งได้ ด้วยคำสั่ง sed และ เครื่องหมาย ‘]’ ด้านหลังตัวเลขก็จะสามารถตัดทิ้งได้ด้วย sed เช่นเดียวกัน หรือ อาจจะใช้คำสั่ง tr  ด้วย option -d ก็ได้

    ตัวเลขที่ได้ ตัดเอามาเฉพาะส่วนที่เป็นวินาที แล้วเอามาบวกกับค่า STARTTIME โดยใช้คำสั่ง expr เป็นตัวช่วยเหมือนเดิม

    เอาล่ะ ใช้สิ่งที่ได้เรียนรู้จากข้างต้น เราก็จะสามารถเขียนเป็น shell script ออกมาได้ประมาณนี้

    #!/bin/sh
    DSTR1="+%b %d %H:%M:%S"                    # Syslog format Month Date Hour:Min:Sec
    DSTR2="+%Y-%m-%d %H:%M:%S"                 # ISO format
    NOW=`date +%s`                             # current timestamp in second
    UPTIME=`cat /proc/uptime | cut -f1 -d.`    # current uptime
    START=`expr $NOW - $UPTIME                 # start time of this machine
    cat - | sed 's/^\[ *//' |\
    while read STAMP REST; do
        SEC=`echo $STAMP | tr -d '[]' | cut -f1 -d.`
        TIME=`expr $START + $SEC`
        DATE=`date -d @$TIME "$DSTR1"`
        echo "$DATE $REST"
    done

    ส่วนที่เพิ่มขึ้นมาก็คือ การเลือกแสดงผล ออกมาเป้นรูปแบบเดียวกับ date/time stamp ที่ใช้ใน log file (DSTR1) หรือ จะใช้แบบ ISO format (DSTR2)

    วิธีการใช้งาน script ตัวนี้ ซึ่งสมมติให้มีชื่อว่า dmesg-datestamp ก็ใช้แบบนี้ครับ

    $ dmesg | ./dmesg-datestamp

    ซึ่งในกรณีที่มี ข้อความใน dmesg จำนวนมาก ก็อาจจะใช้เวลาค่อนข้างนานเพราะในแต่ละบรรทัด ของ dmesg จะต้องมีการคำนวณ เวลาและแปลงเป็นวันที่ใหม่ โดยการใช้คำสั่ง expr และ date ตามลำดับ ซึ่งสามารถลดเวลาที่ใช้ในการแสดงผล โดยเลือกเอาเฉพาะ dmesg ในบรรทัดที่เราสนใจได้ เช่น

    $ dmesg | grep eth0 | ./dmesg-datestamp

    หรืออาจจะ redirection ลงไปเก็บไว้ในไฟล์ แล้วอ่านจากไฟล์ แทนที่จะอ่านจากคำสั่ง dmesg ทุกครั้ง

    สำหรับคนที่ ทนอ่านมาจนถึงบรรทัดนี้ แล้วกำลังจะบอกผมว่า … จริงๆแล้ว ไม่ต้องทำถึงขนาดนั้นก็ได้ครับ คำสั่ง /bin/dmesg มี option -T ซึ่ง สามารถแปลง time stamp ให้อยู่ในรูปแบบ human readable ให้อยู่แล้วโดยไม่จำเป็นจะต้องพึ่งพา shell script ใดๆเลย

    แหะๆ ครับ Hit with Brick

    ผมก็ทราบแล้วครับ … แต่ว่านั่นมันหลังจากผมเขียน script เสร็จแล้ว (ฮา)

    บน Debian Linux ถ้าใช้ wheezy ก็จะสามารถใช้

    $ dmesg -T

    ได้เลย แต่บน Debian squeeze ซึ่งเป็น stable release ของ Debian ในตอนนี้ คำสั่ง dmesg ยังไม่สามารถใช้งาน option -T ได้ แต่ปัญหานี้ก็คงมีอีกไม่นานเท่าไหร่ เพราะ wheezy กำลังจะกลายเป็น stable release ในของ Debian ภายในไม่ช้า …

    แต่ … จะทำยังไงได้ล่ะ ก็เขียนไปแล้วนี่ …

    ส่วนใครที่อ่านมาจนถึงบรรทัดนี้แล้ว … ก็อ่านไปแล้วนี่ ช่วยอะไรไม่ได้เนอะ …

    ขอจบเอาดื้อๆแค่นี้แหละคร้าบ …

  • shell script for download virtualbox packages Part 2

    หลังจาก เกริ่นนำ เสียยืดยาว แต่ ยังไม่เข้าสู่เนื้อหาใน ภาคแรก
    ก็ขอเอามาต่อภาคสองที่นี่ …
    ความเดิมตอนที่แล้ว โจทย์มีอยู่ว่า

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    โดยไฟล์ทั้งหมดจะเก็บอยู่ใน http://download.virtualbox.org/virtualbox/$version
    ส่วนของตัวเลข $version นั้นเรามีวิธีการที่จะใช้ script ในการอ่านค่านั้นมาโดยอัตโนมัติเอาไว้แล้ว ใน ตอนแรกของบทความ ชุดนี้ (เรียกซะเป็นชุด แต่จริงๆแล้วก็มีแค่ 2 เท่านั้นแหละครับ)

    ไฟล์ที่ต้องการจริงๆนั้นมีอะไรบ้าง?
    1. Windows
    http://download.virtualbox.org/virtualbox/4.2.8/VirtualBox-4.2.8-83876-Win.exe
    2. Ubuntu
    2.1 Ubuntu 12.04 มี 2 file สำหรับ 2 architecture
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    เอาเท่านี้ก่อน … จะเห็นว่า URL ที่จะใช้ในการเข้าถึงไฟล์นั้น จะซับซ้อนพอดู
    ส่วนแรกที่เหมือนกัน ก็คือ URL ที่ระบุไปถึง directory และ version คือ

    http://download.virtualbox.org/virtualbox/4.2.8/

    จะเหมือนกัน แต่หลังจากนั้น ส่วนของไฟล์จะได้เป็น

    VirtualBox-4.2.8-83876-Win.exe
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    และไฟล์ของ Ubuntu 12.10

    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    นั่นแน่ะ ตัวเลข release เฉพาะของ Ubuntu 12.10 เองก็ยังต่างกัน ระหว่าง amd64 (83876) กับ i386 (83877) อีกต่างหาก ถ้าจะระบุชื่อเองโดยหาข้อมูล version/sub version/release บวกกับ architecture บวกกับการตั้งชื่อที่แตกต่างกัน (ตัวเล็ก/ใหญ่ ของชื่อ VirtualBox ระหว่าง Windows กับ Ubuntu) ก็คงจะได้ script ที่ซับซ้อนน่าดู

    Crying

    แต่จริงๆแล้วมีวิธีการที่ง่ายกว่านั้น

    ก่อนอื่นเรามา list ไฟล์ที่มีอยู่ออกมาดูกันก่อน เราสามารถใช้คำสั่ง

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/

    ก็จะเห็น ข้อมูลของหน้า download อยู่ใน format ของ html มีขยะอะไรที่เราไม่ต้องการเยอะแยะไปหมด เราต้องการเฉพาะบรรทัดที่อ้างถึงไฟล์ที่เราสามารถเอามาใช้ download ได้ ลองสังเกตดู เราก็จะเห็นว่า บรรทัดเหล่านั้นจะเริ่มบรรทัดด้วย ‘<A HREF=’ … งั้นเอาละ เราก็กรองมาเฉพาะบรรทัดเหล่านั้น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF'

    สวยงามขึ้น … แต่เราต้องการเฉพาะชื่อไฟล์ ไม่ได้ต้องการ html format ทั้งบรรทัด ในกรณีนี้ ส่วนที่เราต้องการอยู่ระหว่างเครื่องหมาย double qoute (“) คู่แรก ซึ่งเราใช้คำสั่ง cut ช่วยแยกสิ่งที่เราต้องการออกมาได้ โดยเพิ่มคำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\"

    ผลลัพธ์คือ

    /virtualbox/
    MD5SUMS
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack
    SDKRef.pdf
    SHA256SUMS
    UserManual.pdf
    VBoxGuestAdditions_4.2.8.iso
    VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm
    VirtualBox-4.2.8-83876-Linux_amd64.run
    VirtualBox-4.2.8-83876-Linux_x86.run
    VirtualBox-4.2.8-83876-OSX.dmg
    VirtualBox-4.2.8-83876-SunOS.tar.gz
    VirtualBox-4.2.8-83876-Win.exe
    VirtualBox-4.2.8.tar.bz2
    VirtualBoxSDK-4.2.8-83876.zip
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    สวยงามยิ่งกว่าเดิม … มี directory ที่เราไม่ต้องการโผล่มา 1 คือ “/virtualbox/” ซึ่งสามารถใช้ grep -v เพื่อทิ้งไปได้ แต่สำหรับกรณี ไม่จำเป็น

    จากไฟล์ที่ list ออกมา ถ้าพิจารณาดูหน่อยนึง เราก็จะเห็นว่า งานที่เราจำเป็นจะต้องทำ ไม่ต้องไปสร้าง ชื่อของไฟล์ให้ยุ่งยากซับซ้อนเกินไปก็ได้ เพราะใน list ที่เรามีอยู่ เราสามารถเลือก “grep” ส่วนที่เราต้องการได้เลย เช่น ไฟล์ package สำหรับ ubuntu 12.04 ก็สามารถเลือก grep โดยใช้ string “precise” ได้เลยเช่น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep "precise"

    ก็จะได้ผลลัพธ์เป็น

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    ถ้าต้องการ ubuntu 12.10 ด้วยล่ะ ก็เพิ่ม option -E สำหรับคำสั่ง grep และเปลี่ยน string ที่จะใช้ grep นิดหน่อยเป็น “precise|quantal” ก็จะได้คำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep -E "precise|quantal"

    และได้ผลลัพธ์เป็น

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    อะฮ่า … งดงามเป็นอย่างยิ่ง โดยใช้ trick เดียวกันเราก็จะได้คำสั่ง สำหรับ “เลือก”ไฟล์ที่เราต้องการทั้งหมด ได้เป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF=' | cut -f2 -d\" | grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"

    โอเค คราวนี้เราก็พร้อมที่จะดัดแปลงมันเป็น shell script สำหรับการ download VirtualBox package ที่เราต้องการแล้วละครับ โดยส่ง list รายการของไฟล์ทั้งหมดไปเก็บไว้ในตัวแปร แล้วก็ใช้การวน loop download มาทีละไฟล์ประมาณนี้ครับ

    #!/bin/sh
    
    VERS="4.2.8"
    VBOXDL="http://download.virtualbox.org/virtualbox/$VERS"
    LIST=`    wget -q -O- $VBOXDL                        |\
        grep '^<A HREF='                        |\
        cut -f2 -d\"                            |\
        grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"    |\
        grep -v "Extension_Pack-${VERS}-"                `
    
    for file in $LIST; do
        wget ${VBOXDL}/${file}
    done

    บรรทัด grep -v “Extension_Pack-${VERS}-” ที่เพิ่มขึ้นมาก็เพื่อตัดให้เหลือไฟล์ extension pack ที่ต้อง download เพียงไฟล์เดียว เพราะทั้งสองไฟล์ถึงจะมีชื่อไฟล์ต่างกัน แต่จริงๆแล้วก็เป็นไฟล์เดียวกันครับ

    งานส่วนที่เหลือก็คือ รวมเอา script ในตอนแรก ที่ใช้ในการหา $VERS แบบอัตโนมัติ มารวมอยู่ใน script เดียวกันนี้ เพื่อว่าจะได้ไม่จำเป็นจะต้องมาเปลี่ยนหมายเลข version ใหม่ทุกครั้ง ก่อนที่จะ download
    หรือถ้าจะให้ดียิ่งไปกว่านั้น ก็คือ ให้ script download ไฟล์ทั้งหมดมาโดยอัตโนมัติ และ ส่ง email แจ้งว่า ได้ download ไฟล์ทั้งหมดมาเรียบร้อยแล้ว

    อันนี้ ทิ้งไว้เป็นโจทย์ ให้ลองหัดทำกันดูเก็แล้วกันนะครับ

    ถ้ามีปัญหาแก้ไม่ได้ ก็ขอให้ comment มาครับ เผื่อว่า ผมจะได้ idea สำหรับเขียน blog ใหม่เพิ่ม เพราะว่า ถ้าปล่อยให้คิดเอง ก็คิดไม่ออกเหมือนกัน (ฮา)

    สุดท้ายนี้ ขอขอบคุณเจ้าของ email ต้นเรื่องนะครับ ที่ทำให้ผมเขียน blog เพิ่มขึ้นได้อีก 2 บทความ ถ้าไม่ได้คำถามดีๆ ผมก็ไม่มีอะไรให้เขียนจริงๆครับ ขอบคุณอีกครั้งครับ

  • shell script for download virtualbox packages

    หลังจากบรรยายเรื่อง Shell script ไปในสัปดาห์ที่แล้ว ด้วยหัวข้อเรื่องที่มีอยู่ค่อนข้างเยอะกว่าที่คิดไว้ และใช้เวลาบรรยายไปอย่างไม่ค่อยมีประสิทธิภาพสักเท่าไหร่ ทำให้เนื้อเรื่องบางส่วน ต้องทิ้งเอาไว้ ไม่ได้พูดต่อให้จบ ปล่อยคนที่เอา slide มาอ่านงงไปก็แล้วกันว่า มันคือเรื่องอะไรกัน

    เรื่อง slide ก็ … เหอะ … เดี๋ยวมีโอกาส ค่อยกลับไปปรับปรุงมันอีกที ผมยังพอมี idea อยู่บ้างว่าจะแก้ไขมันยังไง แต่เรื่องหนึ่งซึ่งจริงๆแล้ว มีปัญหาตั้งแต่ก่อนเริ่มบรรยายแล้วว่า จะยกตัวอย่างโจทย์ปัญหาอย่างไรดี ที่จะทำให้เห็น วิธีการที่ผมใช้ในการ “เขียน” shell script ตัวนึงออกมาได้ ตัวอย่างที่ยกให้ดูในตอนแรกของ การบรรยาย เป็นแค่ตัวอย่างหลอกๆ ที่เขียนไปเพื่อให้เห็น รูปแบบ ของการใช้ shell script แต่จะว่าไป ที่ผมใช้งานอยู่ปัจจุบัน ก็ไม่ได้ตัวอย่างที่ว่านั่น จะใช้ตัวอย่างอื่นที่มี ตัวอย่างเช่น psuautosigned ก็คงต้องอธิบายในเรื่องอื่นกันยาว ก่อนที่จะวกกลับมาที่เรื่องของ shell script ได้

    จนกระทั่งวันนี้ ได้รับ email ฉบับนี้มา

    > อาจารย์ครับ
    >    มีเรืองปรึกษครับ
    >
    > สิ่งที่ต้องการคือ wget file หลายๆ  file ตามข้างล่างนี้
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/VirtualBox-${version1}-83876-Win.exe
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83877~Ubuntu~quantal_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~quantal_amd64.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_amd64.deb
    > wget http://download.virtualbox.org/virtualbox/${version1}/MD5SUMS
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/Oracle_VM_VirtualBox_Extension_Pack-${version1}-83876.vbox-extpack
    > wget http://download.virtualbox.org/virtualbox/${version1}/UserManual.pdf
    >
    > ผมเขียน  script แบบนี้
    > version1=4.2.8
    > url1=http://download.virtualbox.org/virtualbox/${version1}
    > for i in Win  quantal  precise Oracle MD5 User
    >     do
    >        wget  ${url1}/*{i}*
    >    done
    > ไม่ work ครับ
    > ขอคำแนะนำด้วยครับ

    ผมตอบกลับไปว่า

    |        ใช้ *{i}* หรือ *${i}* ไม่ได้ครับ เพราะ * ที่ใช้บน command line
    |        ปกติ จะเป็นตัวอักษรที่ตีความโดย shell ที่เราใช้งานอยู่ ซึ่งโดยทั่วไป
    |        มันก็จะพยายาม match กับ ชื่อไฟล์ ใน directory บนเครื่องของเราครับ
    |
    |        ทีนี้ ตอนท่ีส่งให้กับ wget ถ้า shell ไม่สามารถ expand ตัว * ออกมาเป็น
    |        ไฟล์ได้ มันก็จะส่ง * ไปยังเครื่องปลายทาง ซึ่งก็จะเปรียบเทียบแบบ literal
    |        คือเป็นตัวอักษร * โดยตรงโดยไม่ได้พยายาม match กับไฟล์ที่มีอยู่ใน
    |        directory นั้นๆ
    |
    |        วิธีการหนึ่งที่อาจจะใช้สำหรับการ download ทั้งหมดมาได้ ก็คือใช้ option -r
    |        แต่ว่านั่นก็จะได้ไฟล์ ที่เราไม่ต้องการมาอีกหลายๆไฟล์
    |
    |       สำหรับกรณีนี้ วิธีการที่ สามารถใช้ในการแก้ปัญหาได้ก็คือ อ่าน list ของไฟล์
    |       ที่มีอยู๋ทั้งหมดมาก่อน แล้วค่อยมาเลือกชื่อไฟล์ที่เราต้องการ เอามาเก็บไว้ใน
    |       list ที่เราต้องการ download อีกรอบนึง ครับ
    |
    |       อันที่จริงแล้ว นี่เป็นคำถามที่ดีมาที่จะใช้ยกตัวอย่างในเรื่องของการใช้งาน
    |       shell script เลยทีเดียวครับ
    |
    |       ผมกำลังหา หัวข้อเรื่องที่เหมาะสมสำหรับเขียนเป้น blog ลงใน psu sysadmin
    |       พอดีเลยครับ ผมขอเอาคำตอบไปตอบในโน้นนะครับ

    จริงๆแล้วแอบร้องยูเรก้าอยู่ในใจ เพราะนี่แหละสิ่งที่ผมต้องการ ไม่ต้องรอคำอนุญาตจากเจ้าของ email ผมก็รีบเอามา post ที่นี่ทันทีถ้าไม่มีคำถามดีๆ ผมก็ไม่มีไอเดีย อะไรที่จะเอามาเขียนเป็น blog เหมือนกันครับ ของคุณเจ้าของ email ที่ส่งมาถามเป็นอย่างมากเลยครับขอเริ่มจากอธิบายโจทย์ใหม่อีกสักรอบก่อนนะครับโจทย์ คือต้องการที่จะ download package ของ virtualbox ใหม่ล่าสุดที่มีอยู่บน site ของ virtualbox มาเก็บไว้ที่ local เพื่อให้การติดตั้ง package บนเครื่องคอมพิวเตอร์ในเครือข่าย ซึ่งมีอยู่หลายเครื่อง จะได้ไม่ต้องไป download มาสำหรับแต่ละเครื่องให้เปลือง bandwidthถ้าหากว่า เครื่องคอมพิวเตอร์ที่ใช้ เป็น Linux และติดตั้ง Ubuntu Linux หรือ Debian Linux และต้องการ
    VirtualBox ตัวที่ทันสมัย แต่ไม่ถึงกับต้องการตัว “ล่าสุด” จริงๆ ปัญหานี้ก็สามารถแก้ได้ง่ายๆ โดยติดตั้ง
    Virtualbox บนเครื่อง โดยการใช้คำสั่ง

    sudo apt-get install virtualbox

    ซึ่งบนเครื่อง Debian Wheezy ที่ผมใช้งานอยู่ปัจจุบัน ก็จะมี Virtualbox 4.1.8 ให้ใช้ติดตั้งได้เลย โดยไม่ต้อง download มาจาก website ของ virtualbox

    แต่ถ้าต้องการ virtualbox version ล่าสุด ซึ่ง version stable ล่าสุด ซึ่งประกาศ release อยู่บน website ของ virtualbox (ณ วันนี้ 6 มีค. 2556) คือ 4.2.8 ซึ่งก็ … อะ แฮ่ม … ต่างกันไม่มากเท่าไหร่

    แต่ถ้าต้องการ version ล่าสุด หรือ package สำหรับ Windows หรือ Mac OSX ด้ว ก็คงต้องใช้วิธีการ download มาจาก website ของ virtualbox โดยตรงจาก https://www.virtualbox.org/wiki/Downloads ครับ

    กลับมาที่ โจทย์ ต่อ

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    ไฟล์ของ virtualbox ซึ่งสร้างเป็น link ไว้ ในหน้า download จะเก็บอยู่บน http://download.virtualbox.org/virtualbox/4.2.8/ สำหรับ version 4.2.8

    ซึ่งถ้าเรา list มาดูจะได้ ประมาณนี้

    [DIR]  Parent Directory
    [   ]  MD5SUMS                                                          01-Mar-2013 02:11     3K    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack     27-Feb-2013 12:56    10M    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack           27-Feb-2013 12:56    10M    
    [   ]  SDKRef.pdf                                                       27-Feb-2013 12:55     2M    
    [   ]  SHA256SUMS                                                       01-Mar-2013 02:12     5K    
    [   ]  UserManual.pdf                                                   27-Feb-2013 12:55     5M    
    [BIN]  VBoxGuestAdditions_4.2.8.iso                                     27-Feb-2013 13:00    54M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm                        27-Feb-2013 12:45    82M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm                        27-Feb-2013 12:45    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm                      27-Feb-2013 13:01    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm                        27-Feb-2013 12:45    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm                      27-Feb-2013 13:01    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm                   27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm                 27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm                  27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm                27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm                  27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm              27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm                   27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm                 27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm                   27-Feb-2013 12:46    73M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm                 27-Feb-2013 12:47    73M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_amd64.run                           27-Feb-2013 12:55    79M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_x86.run                             27-Feb-2013 12:55    79M    
    [   ]  VirtualBox-4.2.8-83876-OSX.dmg                                   27-Feb-2013 12:55   103M    
    [   ]  VirtualBox-4.2.8-83876-SunOS.tar.gz                              27-Feb-2013 12:55   113M    
    [BIN]  VirtualBox-4.2.8-83876-Win.exe                                   27-Feb-2013 12:55    93M    
    [   ]  VirtualBox-4.2.8.tar.bz2                                         28-Feb-2013 02:22    72M    
    [   ]  VirtualBoxSDK-4.2.8-83876.zip                                    27-Feb-2013 13:00     9M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb              27-Feb-2013 09:00    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb               27-Feb-2013 08:49    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb               27-Feb-2013 10:20    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb                27-Feb-2013 10:05    61M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb                27-Feb-2013 12:12    82M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb                 27-Feb-2013 11:59    79M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb                27-Feb-2013 09:42    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb                 27-Feb-2013 09:33    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb                27-Feb-2013 08:38    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb                 27-Feb-2013 08:26    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb              27-Feb-2013 11:46    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb               27-Feb-2013 11:30    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb              27-Feb-2013 09:24    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb               27-Feb-2013 09:10    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb              27-Feb-2013 08:56    59M    
    [   ]  virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb               01-Mar-2013 01:45    60M

    ถ้าต้องการ download มาทั้งหมด ก็ทำได้โดยการใช้คำสั่ง

     wget -r -np -nH --cut-dirs=2 http://download.virtualbox.org/virtualbox/4.2.8/

    แต่ก็จะเห็นว่าเราจะได้ไฟล์จำนวนมากที่เราไม่ต้องการมาด้วย เราต้องการไฟล์จริงๆเพียงไม่กี่ไฟล์ เพราะฉะนั้น ก็ต้องทำงานเพ่ิมขึ้นอีกหน่อยนึง

    แต่ก่อนที่จะเริ่ม จากการ download ไฟล์จาก directory ที่เราต้องการ ซึ่งเรารู้ตำแหน่งที่แน่นอนอยู่แล้ว จากการสำรวจแบบ manual มาตั้งข้อสมมติฐานก่อนว่า ถ้าเมื่อไหร่ virtualbox มีการ release version ใหม่ออกมา เราก็ควรที่จะรู้ได้โดยอัตโนมัติ หรืออย่างน้อย เราก็ไม่น่าจะจำเป็นที่จะต้องมาเขียน script ใหม่ … คำถามคือ เราจะรู้ได้ใหม? และ ถ้าได้ เราจะรู้ได้อย่างไร

    คำตอบแรกคือ ได้ คำตอบถัดมามี อยู่หลายทางเลือกครับ แต่ถ้าเอาแบบไม่ซับซ้อนมากนัก วิธีการที่ผมใช้แบบ manual อยู่ก็คือ เข้าไปดูที่ https://www.virtualbox.org/wiki/Downloads ซึ่งในหน้าที่ก็จะ list ตัว package ของ virtualbox ที่จะให้ download สำหรับ OS ต่างๆ และจะมี ระบุ version เอาไว้ด้วย

    ในบรรทัดที่ใช้สำหรับระบุ URL ให้ download ของ Linux ก็จะเขียนไว้ว่า

    VirtualBox 4.2.8 for Linux hosts

    ตัวเลข 4.2.8 คือ version ซึ่งจะเปลี่ยนไปเรื่อยๆ เมื่อ Virtualbox มีการ update เป็น version ใหม่
    เราสามารถ เอาตัวเลขนี้ มาได้ โดยการใช้ความร่วมมือระหว่าง โปรแกรม 2 ตัว คือ wget กับ grep โดยที่คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads

    เราจะได้ ข้อมูลของ page ดังกล่าวอยู่ในรูป html ปรากฏขึ้นบนหน้าจอ … เราต้องการเฉพาะบรรทัดที่ระบุ URL สำหรับ Linux ก็สามารถใช้ grep สำหรับ ตัดมา เฉพาะบรรทัดนั้นได้ โดยการใช้คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads | grep 'for Linux hosts'

    ซึ่งจะได้ผลลัพธ์ออกมาเป็น

    </li><li><strong><a href="/wiki/Linux_Downloads">VirtualBox 4.2.8 for Linux hosts</a></strong>

    และ โดยการใช้คำสั่ง sed เพื่อที่จะตัดข้อความข้างหน้า ตั้งแต่ <li><s… ไปจนถึง VirtualBox ทิ้งไป ก็สามารถเพิ่มคำสั่งเข้าไปเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
    grep 'for Linux hosts' |\
    sed -e 's/^.*VirtualBox //'

    ก็จะได้ผลลัพธ์ออกมาเป็น

    4.2.8 for Linux hosts</a></strong>

    คราวนี้ เพิ่มส่วนของการกำจัดข้อความด้านหลังตัวเลข version โดยใช้ expression ที่สองของ sed ก็จะได้คำสั่งเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
     grep 'for Linux hosts' |\
     sed -e 's/^.*VirtualBox //' -e 's/ for Linux hosts.*$//'

    และได้ผลลัพธ์ออกมาเป็น

    4.2.8

    สวยงามไม่มีที่ติ Smile
    เอาน่า … เชื่อผมหน่อยน่า บอกว่าสวยก็สวยสิ!

    หลังจากพยายามมาสักพักเราก็ได้ตัวเลข version มา … ซึ่งจะเสียเวลากับมันมากไปหรือเปล่า? …
    เอาน่า โดยตัวเลขที่มีอยู่นี้ เราสามารถเขียน script สำหรับการตรวจสอบว่า Virtualbox มีการ update version ใหม่หรือเปล่า โดยการเอา code ข้างต้น มาปรับให้เป็น script ได้ประมาณนี้

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    getVBoxVersion
    echo "Last VirtaulBox Version = $VERS"

    เขียนส่วนของ code ให้ execute ใน backtick เพื่อส่งค่ามาให้กับตัวแปร shell VERS ซึ่งเราจะเอาไปใช้ต่อไป ใช้ตัวแปร VBOXWIKIDL สำหรับการระบุ URL ของ download page เพื่อให้ code อ่านง่ายขึ้น และเขียนในรูปแบบของ shell function เพื่อที่เราจะเอาฟังก์ชันนี้ไปใช้ในงานอื่นต่อไปในอนาคต

    กำหนด ชื่อให้ script เป็น vboxvers แล้วกำหนดให้มัน execute ได้โดยการใช้คำสั่ง

    chmod +x vboxvers

    และทดสอบ

    ./vboxvers

    เราก็จะได้ script สำหรับแสดง version ล่าสุดของ VirtualBox ที่มีให้ download จาก website ของ VirtualBox … แต่ถ้าให้ดีขึ้นไปกว่านี้ ตัว script นี้ควรจะทำงานโดยอัตโนมัติ โดยการตรวจสอบวันละครั้ง และถ้ามี version ที่ใหม่กว่า ก็ค่อยส่ง email มาแจ้งเรา

    ซึ่งเราสามารถทำได้โดย การเพิ่ม code สำหรับการเก็บข้อมูล version ล่าสุดที่เคยตรวจสอบเอาไว้ในไฟล์ และเปรียบเทียบ ตัวเลขนั้นกับ version ใหม่ที่มี สมมติให้ไฟล์นั้นชื่อ .vboxvers.txt เก็บไว้ใน HOME ของเราเอง

    FILE="$HOME/.vboxvers.txt"

    ถ้ามีไฟล์นั้นอยู่ เราก็อ่านค่า version ล่าสุดที่เคยตรวจสอบมาจากไฟล์นั้น แต่ถ้าไม่มีไฟล์ ก็กำหนดค่า version เริ่มต้นให้เป็น “0.0.0” ซะ

    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi

    หลังจากนั้น เราก็จะอ่านค่า version ล่าสุดจาก web ของ VirtualBox มาโดยใช้ ฟังก์ชัน getVBoxVersion ที่เขียนเอาไว้แล้ว

    เอาค่าที่ได้มาเปรียบเทียบกัน ถ้าไม่เท่ากัน ก็ส่ง email ให้กับ address ที่กำหนดเอาไว้

    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ซึ่งก็จะได้ script สุดท้ายมีหน้าตาประมาณนี้ครับ

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    FILE="$HOME/.vboxvers.txt"
    EMAIL="me@example.com" # Change to your email address!
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    # Check whether we have version file
    # if not use initial 0.0.0 version
    # if there is, then get the last version from that file
    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi
    
    # Then get lastest version from VirtualBox web
    getVBoxVersion
    
    # Then check and report
    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ว้า … ยังไม่ไปถึงใหนเลย … แต่ชักจะยาวเกินไปแล้ว ขอตัดจบแค่นี้ก่อนครับ เดี๋ยวจะกลับมาต่อเรื่องจะ download package ไฟล์ที่เราต้องการจาก download.virtualbox.org ได้ยังไง ใน blog ถัดไปครับ Exciting (กรรมวิธีในการเพิ่มจำนวน blog ทั้งที่มีเนื้อหาที่จะเล่าเท่าเดิม … (ฮา) …)