Tag: shell script

  • Windows CRLF to Unix LF Issues in Cygwin Shell Script

    เมื่อเรารัน shell script ของโปรแกรม Cygwin for Windows ซึ่งมีการเขียนคำสั่งไปตัดเอาข้อความผ่านคำสั่ง (Command Line) ของ Windows มาใส่ในตัวแปรของ shell script

    เช่น ในตัวอย่างนี้คือคำสั่ง ipconfig เมื่อได้ข้อความที่ต้องการมาเราจะได้ \r แถมมาให้ด้วยต่อท้าย เพราะ Windows style line ending จะมี CRLF (\r\n)  ในขณะที่ Linux style line ending จะมี LF (\n) เท่านั้น

    น่าแปลกใจมากว่า เราเคยรัน shell script นี้ใน Windows 7 ใช้งานได้ แต่พอเป็น Windows 10 build 1709 มันรันไม่ได้

     

    ปัญหา

    เมื่อเปิด Cygwin Terminal ขึ้นมา จะได้เป็น bash shell

    ในไฟล์ test.sh ดังตัวอย่างข้างล่างนี้ เมื่อสั่งรัน จะพบว่าพบข้อผิดพลาด ตัวแปร ZONEX จะไม่มีค่า ซึ่งจริง ๆ จะต้องได้คำว่า zone1
    $ cat test.sh

    #!/bin/bash
    
    DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs)
    
    MAC=$(ipconfig /all | grep -A4 -i "^Ethernet Adapter Ethernet" | tail -1 | cut -d\: -f2 | tr - : | xargs)
    
    ZONEX=$(curl -s http://${DHCPSERVER}/dhcpd.txt | grep -i ${MAC} | awk '{print $2}' | cut -d'_' -f1)
    
    echo "DHCP SERVER is ${DHCPSERVER}"
    echo "MAC is ${MAC}"
    echo "Zone is ${ZONEX}"

    สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash test.sh

    $ bash test.sh
    DHCP SERVER is 192.168.6.150
    MAC is 50:7B:9D:30:2E:4B
    Zone is

    ผมก็ตรวจสอบด้วยวิธีการ debug คือ เพิ่ม -x ดังตัวอย่างนี้

    $ bash -x test.sh
     ++ ipconfig /all
     ++ grep -i 'DHCP Server'
     ++ cut -d: -f2
     ++ xargs
     + DHCPSERVER=$'192.168.6.150\r'
     ++ ipconfig /all
     ++ grep -A4 -i '^Ethernet Adapter Ethernet'
     ++ tail -1
     ++ cut -d: -f2
     ++ tr - :
     ++ xargs
     + MAC=$'50:7B:9D:30:2E:4B\r'
     ++ curl -s $'http://192.168.6.150\r/dhcpd.txt'
     ++ grep -i $'50:7B:9D:30:2E:4B\r'
     ++ awk '{print $2}'
     ++ cut -d_ -f1
     + ZONEX=
     ' echo 'DHCP SERVER is 192.168.6.150
     DHCP SERVER is 192.168.6.150
     ' echo 'MAC is 50:7B:9D:30:2E:4B
     MAC is 50:7B:9D:30:2E:4B
     + echo 'Zone is '
     Zone is

    จึงพบว่า ตัวแปร DHCPSERVER และ ตัวแปร MAC จะมี “/r” แถมมาให้ด้วย ซึ่งเป็นส่วนเกินที่ทำให้คำสั่งถัดไปทำงานผิดพลาดทำให้ได้ค่า ZONEX เป็น ว่างเปล่า

     

    วิธีแก้ไข เราต้องใส่ option ” -o igncr ” หลังคำสั่ง bash เพื่อให้ตัด “/r” ออกไป

    สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash -o igncr test.sh

    ผลลัพธ์คราวนี้ถูกต้องแล้ว

    $ bash -o igncr test.sh
    DHCP SERVER is 192.168.6.150
    MAC is 50:7B:9D:30:2E:4B
    Zone is zone1

     

    วิธีแก้อีกวิธีคือ หากไม่ใส่ option ที่ว่านี้ เราก็ต้องไปแก้ไขบรรทัดคำสั่ง เพื่อเติม sed ‘s/\r$//’ ต่อท้าย เป็นการตัด /r ออกไปก่อนนำค่าที่ได้ไปใส่ในตัวแปร DHCPSERVER
    เช่น

    DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs | sed 's/\r$//')

    ซึ่งก็ทำได้เช่นกัน แต่ต้องแก้ไขไฟล์ shell script ซึ่งก็แล้วแต่ชอบวิธีไหน

     

    Reference
    https://stackoverflow.com/users/1010997/user1010997
    https://stackoverflow.com/questions/11616835/r-command-not-found-bashrc-bash-profile
    https://stackoverflow.com/questions/18608380/r-command-not-found

  • date นั้นสำคัญไฉน

    ที่ Shell prompt พิมพ์คำสั่ง man date

    ได้อะไรมาไม่รู้เยอะแยะ…

    man date
    man date

    จากคู่มือจะเอารูปแบบวันที่ 12-09-2017 ตัวเลือกที่เกี่ยวข้องได้แก่ %d %D %e %F %g %G %m %y %Y เป็นต้น ลองส่งคำสั่ง

    date +"%d-%m-%Y"

    ได้ผลลัพธ์

    12-09-2017

    ตรงตามที่ต้องการ มาเขียนสคริปต์กันหน่อย อยากได้เมื่อวานทำไง วันนี้เล่น tcsh shell สร้างแฟ้ม date.tcsh ด้วย editor ที่ชื่นชอบมีข้อความว่า

    #!/bin/tcsh -f
    set tday=`date +"%d"`
    set tmonth=`date +"%m"`
    set tyear=`date +"%Y"`
    echo "Today is ${tday}-${tmonth}-${tyear}."
    set yday=`expr ${tday} - 1`
    echo "Yesterday was ${yday}-${tmonth}-${tyear}."

    ทดสอบสคริปต์ด้วยคำสั่ง

    tcsh date.tcsh

    ไม่อยากพิมพ์ tcsh ทุกครั้งเพิ่ม execution bit ด้วยคำสั่ง

    chmod +x date.tcsh

    เรียกใช้ได้โดยพิมพ์

    ./date.tcsh (อ่านว่า จุด-ทับ-เดต-จุด-ที-ซี-เอส-เอช)

    ผลลัพธ์ที่ได้

    Today is 12-09-2017.
    Yesterday was 11-09-2017.

    อยากได้เมื่อวานทำไมมันยากอย่างนี้ ฮา… ซึ่งเมื่อกลับไปอ่านคู่มือ (man date) ให้ดี..อีกครั้งจะพบว่ามีตัวเลือก

    -d, –date=STRING
    display time described by STRING, not ‘now’

    และเมื่่อเลื่อนลงมาล่างสุดจะพบว่า

    DATE STRING
    The –date=STRING is a mostly free format human readable date string such as “Sun, 29 Feb 2004 16:21:42 -0800” or
    “2004-02-29 16:21:42” or even “next Thursday”. A date string may contain items indicating calendar date, time of day,
    time zone, day of week, relative time, relative date, and numbers. An empty string indicates the beginning of the day.
    The date string format is more complex than is easily documented here but is fully described in the info documentation.

    โอ้ววว มันเขียนไว้หมดแล้ว…

    เขียนใหม่ได้ว่า
    date -d yesterday

    ได้ผลลัพธ์

    Mon Sep 11 21:43:51 +07 2017

    เปลี่ยนให้ผลลัพธ์ออกมาในรูปแบบที่ต้องการได้ด้วยคำสั่ง

    date -d yesterday +"%d-%m-%Y"

    ก็จะได้ผลลัพธ์ว่า

    11-09-2017

    แก้สคริปต์ date.tcsh

    #!/bin/tcsh -f
    set tday=`date -d today +"%d-%m-%Y"`
    set yday=`date -d yesterday +"%d-%m-%Y"`
    echo "Today is ${tday}."
    echo "Yesterday was ${yday}."

    เจ็บมาเท่าไหร่แล้วกับคำว่าไม่อ่านเอกสาร….

    ยังใส่ข้อความอื่นๆ แทน string ได้เช่น
    date -d 'tomorrow'
    date -d '-1 days ago'
    date -d '200 days'
    date -d '1000 weeks'
    date -d '30 months'
    date -d '300 years'

    เรื่องนี้สอนให้รู้ว่า บางทีเอกสารก็มีให้หมดแล้ว ไม่ต้องทำเองก็ด้ายยยยย….

    จบขอให้สนุก

    man date
    ค้นเพิ่มเติม https://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html

  • Script สำหรับ หาวันที่ของไฟล์ล่าสุดใน directory

    Q: ถ้าจะเขียน คำสั่ง หรือ script บน linux เพื่อหา วันที่ของ file ล่าสุดใน folder ของ user จะเขียนยังไงดี มีไอเดียไหมครับ

    A: สมมติ folder ของ user คือ /home/user/Documents

    $ ls -t /home/user/Documents
    

    จะได้ไฟล์เรียงตามลำดับวันที่/เวลาของไฟล์ โดยไฟล์ล่าสุดจะโผล่มาเป็นไฟล์แรก

    ถ้าเราต้องการไฟล์ล่าสุดแค่ไฟล์เดียว ก็สามารถใช้คำสั่ง head -1 เพื่อตัดให้เหลือไฟล์เดียวได้ ก็จะได้คำสั่งเป็น

    $ ls -t /home/user/Documents | head -1
    

    ทีนี้ จากชื่อไฟล์ที่ได้ ถ้าต้องการวันที่ เราก็สามารถใช้คำสั่ง date โดยใช้ option -r สำหรับให้มันแสดงวันที่ของไฟล์ใดๆ ตย. เช่น ต้องการรู้วันที่ ของไฟล์ /etc/passwd ก็ใช้คำสั่ง

    $ date -r /etc/passwd
    

    เอาสองอย่างนี้มาใช้งานร่วมกันได้ตามนี้ครับ

    $ date -r `ls -t /home/user/Documents | head -1`
    

    ทีนี้ ถ้าต้องการให้ format ของวันที่ออกมาตามที่เราต้องการ อย่างเช่น ให้ format เป็น yyyy-mm-dd HH:MM:SS
    ก็เพิ่ม option ให้กับคำสั่ง date ประมาณนี้ “+%Y-%m-%d %H:%M:%S”

    รวมกันทั้งหมดเป็น

    $ date -r `ls -t /home/user/Documents | head -1` "+%Y-%m-%d %H:%M:%S"
    

    Q: ถ้าจะลงลึกไปหลาย level ต้องทำอะไรเพิ่มครับ
    A: หมายถึงต้องการไฟล์ล่าสุด ไฟล์เดียว จากใน directory นั้นและ sub directory ย่อยทั้งหมดใช่ใหมครับ?
    Q: ใช่ครับ
    A: งั้น คงต้องพึ่งพาคำสั่ง find ครับ เพื่อที่ list เอาเฉพาะไฟล์ทั้งหมดออกมาก่อน เพราะคำสั่ง ls ธรรมดามันจะไล่ไปตาม directory ทีละ directory

    เริ่มจาก

    $ find /home/user/Documents
    

    มันจะ list ทุกอย่างทั้งไฟล์ และ ไดเรคตอรี่และในไดเรคตอรี่ย่อยออกมา

    เราต้องการเฉพาะไฟล์ ระบุ option -type f
    เราต้องการให้มันแสดงวันที่ของไฟล์ออกมาด้วย อันนี้ต้องพึ่งคำสั่ง ls โดยใช้ option ของ ls เป็น –full-time

    $ find /home/user/Documents -type f -exec ls --full-time {} \;
    

    โดย {} เป็นการระบุว่าให้ find ใช้คำสั่ง ls –full-time กับ output ของ find ส่วน \; เป็นตัวระบุว่า จบ option ของ คำสั่ง find แค่นี้

    output ที่ได้ จะเป็นไฟล์ “ทั้งหมด” โดยที่ในแต่ละบรรทัดจะมี ข้อมูลอย่างอื่นของไฟล์นั้นออกมาด้วย เช่น permission, owner, group, size ซึ่งเราไม่สนใจ เราตัดเอาข้อมูลที่อยู่ข้างหน้าเหล่านั้นออกไปได้ โดยใช้คำสั่ง cut โดยในกรณีนี้ใช้ space ‘ ‘ เป็นตัวแบ่ง field และ เอาข้อมูลตั้งแต่ column ที่ 6 เป็นต้นไป

    $ find /home/user/Documents -type f -exec ls --full-time {} \; | cut -f6- -d' '
    

    คราวนี้เราก็ได้ไฟล์ทั้งหมดออกมาโดยนำหน้าชื่อไฟล์ด้วยวันที่/เวลา ซึ่งเราสามารถส่งเข้าไป sort โดยให้เรียงจากหลังมาหน้า
    sort -r และ เอา output ทั้งหมดมาตัดเอาเฉพาะบรรทัดแรกโดยใช้ คำสั่ง head -1 เหมือนเดิม

    $ find /home/user/Documents -type f -exec ls --full-time {} \; cut -f6- -d' ' | sort -r | head -1
    

    ซึ่ง output ที่ได้จากคำสั่ง head อันนี้จะมี วัน/เวลาของไฟล์ นำหน้า ตามด้วยชื่อไฟล์ ซึ่งอาจจะเอาไปใช้งานได้เลย
    หรือ ถ้าต้องการเฉพาะชื่อไฟล์ เพื่อจะเอาไปทำอะไรอย่างอื่นต่อ ก็ต้องส่งไปให้คำสั่ง cut เพื่อตัด field ข้อมูลที่อยู่ด้านหน้าออก

    ขอบคุณคำถามจาก Garnet Komane ครับ

    ปล. สุดท้ายแล้วจากคำสั่งข้างบนที่ว่า แทนที่จะพิมพ์เอาบน command line แล้วไปเปลี่ยน path ที่จะให้ค้นหา เขียนมันใหม่เป็น shell script เลยจะเรียกใช้งานได้ง่ายกว่า ซึ่งจะได้ shell script ประมาณนี้ครับ

    #!/bin/bash
    
    LOCATION="$1"
    
    [ -z "$LOCATION" ] && { echo "Usage: $0 LOCATION"; exit; }
    
    FILE=$(
    find $LOCATION -type f -exec ls --full-time {} \;  |\
    cut -f6- -d' '                                     |\
    sort -r                                            |\
    head -1                                            |\
    cut -f4- -d' '
    )
    
    STAMP=$(date -r "$FILE" "+%Y-%m-%d %H:%M:%S")
    
    echo "$STAMP $FILE"
    

    หรือ ดาวน์โหลด script ได้จาก ที่นี่

  • Dialog cannot open tty-output

    dialog บน Oracle Enterprise Linux และ Ubuntu ไม่เหมือนกัน (ทำไมล่ะ…ไม่ทราบครับ)

    สร้างสคริปต์ชื่อ file.sh มีข้อความว่า

    #!/bin/bash
    FILE=$(dialog --ascii-lines --title "Delete a file" --stdout \
    --title "Please choose a file to delete" --fselect /tmp/ 14 48)
    echo $FILE

    บน Ubuntu รันได้ผลลัพธ์
    Ubuntu's Dialogแต่บน Oracle Enterprise Linux
    Oracle Enterprise LInux' Dialog-_-

    ต้องเปลี่ยนสคริปต์มีสองแบบ
    แบบแรก
    #!/bin/sh
    dialog --ascii-lines --title "Delete a file" --stdout \
    --title "Please choose a file to delete" --fselect /tmp/ 14 48 2>/tmp/file.tmp
    echo $(cat /tmp/file.tmp)

    แบบที่สอง
    #!/bin/bash
    FILE=$(dialog --ascii-lines --title "Delete a file" --stdout \
    --title "Please choose a file to delete" --fselect /tmp/ 14 48 2>&1>/dev/tty)
    echo $FILE

    ผลลัพธ์ของทั้งสองแบบให้ผลเหมือนกันคือ
    Oracle Enterprise Linux's Dialog2จบ..วันนี้ห้วนไปหน่อย ขอให้สนุกครับ

  • Shell Script : Grouping and Summation

    มี Log ขนาดใหญ่ แล้ว ต้องการจะวิเคราะห์ข้อมูลของนาทีที่ผ่านมา เลือกเฉพาะรูปแบบที่ต้องการด้วยคำสั่ง

    grep 'Apr 28 10:59' /var/log/mail.log | grep 'postfix/qmgr' |grep 'nrcpt=' |grep -v 'from=<>'

    ได้ผลมาประมาณนี้

    01

    ต้องการเอาข้อมูล X และ Y จากรูปแบบนี้ from=<X> และ nrcpt=Y ใช้ความรู้จาก Shell Script: Extract exact pattern from string ใช้คำสั่งต่อไปนี้

    grep 'Apr 28 10:59' /var/log/mail.log | grep 'postfix/qmgr' |grep 'nrcpt=' |grep -v 'from=<>'|sed -n 's/.*\sfrom=<\(.*\)>.*\snrcpt=\(.*\)\s(.*/\1:\2/p'

    ได้ผลมาประมาณนี้

    02

    ต้องการจับกลุ่มตาม คอลัมน์ที่ 1 แล้วหาผลรวมของคอลัมน์ที่ 2 ในแต่ละกลุ่ม ใช้คำสั่ง

    grep 'Apr 28 10:59' /var/log/mail.log | grep 'postfix/qmgr' |grep 'nrcpt=' |grep -v 'from=<>'|sed -n 's/.*\sfrom=<\(.*\)>.*\snrcpt=\(.*\)\s(.*/\1:\2/p' | awk 'BEGIN{FS=OFS=":"}{a[$1]+=$2}END{ for (i in a) print i,a[i]}'

    ได้ผลดังนี้

    03

    Reference:

    http://unix.stackexchange.com/questions/169215/group-by-and-sum-in-shell-script-without-awk

  • Shell Script : Extract exact pattern from string

    มี string ดังนี้ ในตัวแปร line

    line=’Apr 18 06:04:57 webmail squirrelmail: Message sent via webmail: by username.s (psu.ac.th) at 41.203.69.5 on 04/17/2015 23:04:57: Message-ID: 98f9739438686e127bcb8547fea7ed82.squirrel@webmail.psu.ac.th Total 9 recipients Message-ID: 98f9739438686e127bcb8547fea7ed82.squirrel@webmail.psu.ac.th’

    ต้องการค่าที่อยู่ระหว่าง Total … recipients

    ใช้คำสั่งนี้

    total=$(echo $line | sed -n ‘s/.*Total \([[:digit:]]*\) recipients.*/\1/p’)

    ตัวแปร $total จะมีค่า 9 ตามที่ต้องการ

    อธิบาย
    คำสั่ง
    sed -n ‘s/PATTERN/&/p’
    จะแสดงข้อความที่ตรงกับ Pattern เท่านั้น ไม่แสดงข้อความอย่างอื่่น

    Reference:
    http://www.grymoire.com/Unix/Sed.html#uh-15

  • การตรวจสอบสถานะการให้บริการ Web Server ด้วย WGET และสร้าง Shell Script เพื่อตรวจสอบอัตโนมัติ

    ขอนำเสนอวิธีการตรวจสอบสถานะการให้บริการด้วยคำสั่ง wget และการสร้าง Shell Script เพื่อให้ทำงานได้อย่างอัตโนมัติ

    การตรวจสอบสถานะการให้บริการด้วย WGET

    วิธีติดตั้ง wget สำหรับ ubuntu 14.04

    สามารถติดตั้งโดยใช้คำสั่ง (โดยปกติจะลงมาให้อยู่แล้ว)

    # sudo apt-get install -y wget

    วิธีติดตั้ง wget สำหรับ windows

    สามารถ Download ได้ที่

    http://downloads.sourceforge.net/project/gnuwin32/wget/1.11.4-1/wget-1.11.4-1-setup.exe

    สามารถเปิด command prompt รันได้ที่ path นี้ (ยกตัวอย่างจาก windows 64 bit)

    C:\Users\xxx>cd C:\Program Files (x86)\GnuWin32\bin
    
    C:\Program Files (x86)\GnuWin32\bin>wget
    SYSTEM_WGETRC = c:/progra~1/wget/etc/wgetrc
    syswgetrc = C:\Program Files (x86)\GnuWin32/etc/wgetrc
    wget: missing URL
    Usage: wget [OPTION]... [URL]...
    
    Try `wget --help' for more options.

    ตัวอย่างคำสั่งที่ใช้สำหรับโหลด html URL ที่ต้องการตรวจสอบ

    wget -nv --timeout 2 --connect-timeout=10 -t 1 http://webserver1.testlab -O /tmp/webserver1-tmp
    
    -nv : ใช้สำหรับปิดการแสดงผล แต่ยังแสดงผล error และข้อมูลเบื้องต้น
    --timeout [sec] : ใช้สำหรับกำหนดค่า timeout ในการเชื่อมต่อไปยัง web server
    --connect-timeout [sec] : ใช้กำหนดเวลาในกรณีที่การเชื่อมต่อไม่สิ้นสุดในเวลาที่กำหนด
    -t [sec] : จำนวนครั้งในการ retry connection
    -O : บันทึกผลลัพธ์ไปยังไฟล์

    ในกรณีที่ต้องการโหลด html URL ประเภท https ให้ทำการข้ามการตรวจสอบ certificate ด้วยตัวอย่างคำสั่งดังนี้

    wget -nv --timeout 2 --connect-timeout=10 -t 1 --no-check-certificate https://webserver1.testlab -O /tmp/webserver1-https-tmp

    หลังจากได้ไฟล์ผลลัพธ์ของหน้า html website เป้าหมายแล้ว สามารถใช้โปรแกรม grep เพื่อตรวจสอบว่าปรากฎข้อความที่แสดงว่าหน้า web ทำงานปกติหรือไม่ เช่น

    grep WEBSERVER /tmp/webserver1-tmp

    หลังจากนั้นเราสามารถประยุกต์เพื่อเขียน Shell Script ง่าย ๆ ที่สามารถตรวจสอบ Web Server เพื่อดูว่า Web ปกติหรือไม่ในขั้นตอนถัดไป

    วิธีการเขียน Shell Script เพื่อตรวจสอบการใช้งานแบบอัตโนมัติ

    Shell Script สำหรับตรวจสอบ Web Server

    – ทำการสร้างไฟล์ /home/[user]/checkweb.sh

    nano /home/testlab/checkweb.sh
    

    – สร้าง script ข้อความดังนี้

    *ตัวอย่างรูปแบบ sh shell

    #!/bin/sh
    url1='webserver1.testlab'
    url2='webserver2.testlab'
    searchtxt='WEBSERVER'
    #URL PATH
    path='/'
    while(true);
    do
      cp /dev/null /tmp/server-status
      for i in $(seq 1 2)
      do
         eval wget -q --timeout 2 --connect-timeout=10 -t 1 http://\${url${i}}$path -O /tmp/\${url${i}}-tmp
         eval echo "Get URL : http://\${url${i}}$path"
         status=$(eval grep $searchtxt /tmp/\${url${i}}-tmp | wc -l)
         if [ $status -ge "1" ]; then
            eval echo \${url${i}}" : STATUS = UP"
            eval echo \${url${i}}" UP" >> /tmp/server-status
         else
            eval echo \${url${i}}" : STATUS = DOWN"
            eval echo \${url${i}}" DOWN" >> /tmp/server-status
         fi
      done
      echo "Sleep 15 secs....."
      sleep 15
      /usr/bin/clear
    done
    

    *ตัวอย่างรูปแบบ bash shell

    #!/bin/bash
    url[1]='webserver1.testlab'
    url[2]='webserver2.testlab'
    searchtxt='WEBSERVER'
    #URL PATH
    path='/'
    while(true);
    do
      cp /dev/null /tmp/server-status
      for i in {1..2}
      do
         wget -q --timeout 2 --connect-timeout=10 -t 1 http://${url[$i]}$path -O /tmp/${url[$i]}-tmp
         echo "Get URL : http://${url[$i]}$path"
         status=$(grep $searchtxt /tmp/${url[$i]}-tmp | wc -l)
         if [ $status -ge "1" ]; then
            echo ${url[$i]}" : STATUS = UP"
            echo ${url[$i]}" UP" >> /tmp/server-status
         else
            echo ${url[$i]}" : STATUS = DOWN"
            echo ${url[$i]}" DOWN" >> /tmp/server-status
         fi
      done
      echo "Sleep 15 secs....."
      sleep 15
      /usr/bin/clear
    done
    

    – ทำการเปลี่ยน permission ให้สามารถ execute ได้

    chmod 755 checkweb.sh

    – ทดสอบ execute script จะได้ข้อความดังนี้

    testlab@lbs:~$ ./checkweb.sh 
    Get URL : http://webserver1.testlab/
    webserver1.testlab : STATUS = UP
    Get URL : http://webserver2.testlab/
    webserver2.testlab : STATUS = UP
    Sleep 15 secs.....
    

    สามารถนำ script ดังกล่าวไปปรับเปลี่ยนเพื่อใช้ในการรันเป็น daemon ได้ตามสะดวกครับ

  • เทคนิคการเขียน Shell Script #1

    เมื่อต้องการเขียน Shell Script เพื่อรับ Argument และ Option เช่น เขียน myscript.sh ซึ่งแบ่งเป็น 3 แบบ
    1) $ sh myscript1.sh myfirstname mylastname 16
    2) $ sh myscript2.sh -f myfirstname -l mylastname -a 16
    3) $ sh myscript3.sh –firstname myfirstname –lastname mylastname –age 16

    [บทความนี้ ใช้งานบน Ubuntu 12.04 Server และ ใช้ Bash Shell]

    1) วิธีแรก คือ การรับตัว Argument เรียงตามลำดับ โดยใช้ เครื่องหมาย $ ตามด้วยลำดับของตัวแปร เช่น $1, $2, $3 ดัง Script myscript1.sh มีรายละเอียด ต่อไปนี้

    firstname=$1
    lastname=$2
    age=$3
    echo "Firstname=$firstname"
    echo "lastname=$lastname"
    echo "age=$age"

    วิธีนี้ ข้อดีคือ สร้างง่าย แต่ ข้อเสียคือ ต้องใส่ Argument ตามลำดับเท่านั้น

    2) วิธีที่สอง คือ มีการใช้ Option แบบชื่อสั้น เช่น -f, -l ,-a โดยต้องใช้คำสั่ง getopts ดังตัวอย่าง Script myscript2.sh

    getopts "f:l:a:" opt
    while [ "$opt" != "?" ]
    do
       case $opt in
        f)
         echo "Firstname: $OPTARG"
         ;;
        l)
         echo "Lastname: $OPTARG"
         ;;
        a)
         echo "Age: $OPTARG"
         ;;
        esac
     getopts "f:l:a:" opt
    done

    อธิบายเพิ่มเติม

    • getopts "f:l:a:" opt
      คำสั่งนี้ บอกว่า getopts จะรับ Option คือ f, l และ a และ เมื่อรับมาแล้ว จะใส่ในตัวแปร opt
      ส่วนใน “f:l:a:” นั้น เป็นการกำหนด Short Option Name โดยที่ เป็นอักษรตัวเดียว และ ค่า Option ใด ไม่มี เครื่องหมาย “:” แสดงว่า ไม่ต้องการใส่ค่า
      Option ใด มี     เครื่องหมาย “:” แสดงว่า ต้องการมีการใส่ค่า
      Option ใด ไม่มี เครื่องหมาย “::” แสดงว่า จะใส่ค่า หรือไม่ใส่ค่า ก็ได้
    • while [ "$opt" != "?" ] … done
      วนลูป ทุก Options จนเจอค่า “?” จึงหยุดทำงาน
    • case $opt in … esac
      เป็นการ Switch Case ตัว Option ที่เข้ามา ระหว่าง f, l และ a ให้ทำงานตามสั่ง
    • $OPTARG
      เป็นค่าที่ให้มา ผ่าน Option นั้นๆ เช่น หากมีการสั่งงานด้วยคำสั่ง

      sh myscript2.sh -f Somchai -l Jaidee -a 16
      เมื่อลูป ค่า $opt เป็น l , ก็จะได้ค่า $OPTARG เป็น Somchai เป็นต้น

    วิธีนี้ มีข้อดีคือ ง่ายต่อการพัฒนา สามารถ สลับตำแหน่งของ Option ได้ดีกว่าวิธีแรก แต่มีข้อเสียคือ ชื่อย่อ ของ Option นั้นสั้น อาจจะทำให้ยากต่อการสื่อสาร

    3) วิธีที่สาม คือ มีการใช้งาน Option แบบชื่อยาว เช่น –firstname, –lastname, –age
    ตัวอย่าง

    if ! options=$(getopt -o f:l:a: -l firstname:,lastname:,age: -- "$@")
    then
     exit 1
    fi
    
    set -- $options
    
    while [ $# -gt 0 ]
    do
     case $1 in
       -f|--firstname) echo "Firstname: $2" ; shift 2;;
       -l|--lastname) echo "Lastname : $2"; shift 2 ;;
       -a|--age) echo "Age: $2" ; shift 2 ;;
       --) shift ; break;;
     esac
    done
    

    ทั้งนี้ สิ่งที่แตกต่างระหว่างวิธีที่ 2) และ 3) คือ การใช้งาน “getopts” นั้น จะไม่สามารถใส่ Long Options ได้ ต้องใช้ “getopt” จึงจะใช้งานได้ นอกนั้น ก็คล้ายๆกันครับ

  • วิธีตรวจสอบเว็บไซต์ที่โดน Hack #11

    ตั้งแต่ วิธีตรวจสอบเว็บไซต์ที่โดน Hack #1 เป็นต้นมา เป็นการแสดงให้เห็นถึง ปัญหา, การตรวจสอบ, การค้นหา หลังจากเกิดปัญหาแล้วทั้งสิ้น ก็จะเห็นได้ว่า ยุ่งยาก และเป็นเรื่องยากมาก ที่จะค้นหา Backdoor ให้หมด และการจะกำจัดให้หมดนั้นเป็นภาระอย่างมาก

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

    การสำรองข้อมูล หรือการ Backup มี 2 แบบ

    1. Full Backup: สำรองทุกไฟล์และไดเรกทอรี่
    2. Incremental Backup: สำรอง “เฉพาะ” ไฟล์และไดเรกทอรี่ ที่มีการเพิ่ม หรือเปลี่ยนแปลง เท่านั้น

    เครื่องมือในการ Backup มีหลายอย่าง ในบทความนี้ ขอใช้ tar เพราะสามารถใช้งานได้ง่าย
    โดยยกตัวอย่าง เป็นการ Backup /var/www/joomla15

    1. Full Backup ทำได้โดยการสร้างไฟล์ fullbackup.sh และมีข้อมูลดังนี้

    d=$(date "+%Y%m%d%H%M%S")
    cp /dev/null joomla15.snar
    tar -zcvf joomla15-full-$d.tar.gz -g joomla15.snar /var/www/joomla15

    2. Incremental Backup ทำได้โดยการสร้างไฟล์ incrementalbackup.sh และมีข้อมูลดังนี้

    d=$(date "+%Y%m%d%H%M%S")
    tar -zcvf joomla15-inc-$d.tar.gz -g joomla15.snar /var/www/joomla15

    โดยคำสั่ง tar มีคำสั่งเพิ่มเติม เล็กน้อย คือ -g ตามตัว joomla15.snar ซึ่ง จะเก็บสถานะของการ Backup ล่าสุดเอาไว้ ทำให้สามารถทราบได้ว่า มีต้อง Backup ไฟล์ใดบ้าง, ส่วน ชื่อไฟล์ .tar.gz ก็จะนำหน้าด้วย วันเวลานาที ของการทำ backup ไว้

    เมื่อทำคำสั่ง

    sh fullbackup.sh

    จะได้ไฟล์นี้

    joomla15-full-20140105004433.tar.gz

    ต่อมา สมมุติ มีการโจมตี Joomla ด้วยช่องโหว่ของ JCE แบบนี้

    jce01

    เมื่อใช้คำสั่ง

    find /var/www/ -name "*.php" -type f | grep 'images/stories'

    ได้ผลดังนี้

    /var/www/joomla15/images/stories/0day.php

    และ สมมุติ Hacker ใช้งาน Backdoor 0day.php ดังกล่าวทาง URL

    http://localhost/joomla15//images/stories/0day.php

    และ แก้ไขไฟล์

    /var/www/joomla15/CREDITS.php

    ดังนี้

    jce02

    สรุปคือ Hacker สามารถ สร้างและเปลี่ยนแปลงไฟล์

    /var/www/joomla15/images/stories/0day.php
    /var/www/joomla15/CREDITS.php

    เมื่อใช้คำสั่ง

    sh incrementalbackup.sh

    จะได้ไฟล์

    joomla15-inc-20140105021906.tar.gz

    วิธีตรวจสอบ ไฟล์ที่เพิ่มเข้ามา และมีการเปลี่ยนแปลง ใช้คำสั่ง diff โดยเอารายการของไฟล็ใน .tar.gz มาเปรียบเทียบกัน ด้วยคำสั่ง

    diff <(tar -ztvf joomla15-full-20140105004433.tar.gz) <(tar -ztvf joomla15-inc-20140105021906.tar.gz) |grep '>'

    ผลที่ได้คือ จะทราบว่ามีไฟล์ ต่อไปนี้ เพิ่ม/เปลี่ยนแปลง

    > -rw-r--r-- www-data/www-data 15571 2014-01-05 02:10 var/www/joomla15/CREDITS.php
    > -rw-r--r-- www-data/www-data 14315 2014-01-05 01:55 var/www/joomla15/images/stories/0day.php

    หาก ต้องการไฟล์ต้นฉบับของ CREDITS.php ก็ใช้คำสั่ง

    tar -zxvf joomla15-full-20140105004433.tar.gz var/www/joomla15/CREDITS.php

    ก็สามารถกู้ไฟล์เดิมกลับมาได้ครับ

    ขอให้โชคดี