Pages

7/20/2012

Fixing Blackbuntu Command Line Wrapping Problem

I have been experiencing this problem for quite sometime now. It is when I type a long command (> 20 chars ), the whole line then disappears.  I have to re-type the whole line again, or pressing home then space to make it appears again.


I searched and found this article http://www.ibm.com/developerworks/linux/library/l-tip-prompt/ . And I came to the conclusion that this is a problem about coloring in bash and line wrapping.

Root cause is, coloring codes are not enclosed in bracket \[...\] which means Bash(or Xterm, I'm not sure) have to count the coloring code characters as characters on the line and does line-wrapping using the count. And this messes up the whole line.

Solution:
  1. find every file that setting the color for bash prompt. Possible files are:
    1. ~/.bashrc                <<< I found my prompt setting in here
    2. /etc/profile
    3. /etc/profile.d/*.sh
    4. /etc/bash.bashrc
  2. To check if your prompt setting in either of the files, look for the word "PS1"
  3. If there is any color code such as  
    ### Prompt 
    ((UID)) && User="\e[1;32m" || User="\e[1;31m"
    PS1="$User\u\e[0;34m@\h\e[0;37m\w$User\\$\e[m "
    

    Enclose every color codes with \[...\], so they become
    ### Prompt 
    ((UID)) && User="\[\e[1;32m\]" || User="\[\e[1;31m\]"
    PS1="$User\u\[\e[0;34m\]@\h\[\e[0;37m\]\w$User\\$\[\e[m\] "
    
  4. Save the file and you're done.
Next time you open your bash prompt again, it'll be problem-free  :)



7/13/2012

Basic Bash Shell Scripting for Network Programming

เขียน shell script บน Bash shell ให้ติดต่อกับ Network ได้


Unix, Linux เป็นระบบปฏิบัติการที่ทุกๆอย่างจะอยู่ในรูปไฟล์ทุกอย่าง
หมายความว่า นอกเหนือจากการเข้าไปถึงไฟล์ต่างๆธรรมดาแล้ว  เรายังสามารถเข้าถึงข้อมูลของระบบได้ในรูปแบบของไฟล์ได้ด้วย  เช่น
/proc/cpuinfo    #จะได้สถานะของ CPU ที่รันอยู่ตอนนี้ เช่น มีกี่ core, รันที่กี่ MHz
/proc/meminfo    #จะได้สถานะของ RAM ตอนนี้ว่า มีทั้งหมดเท่าไหร่ ใช้ไปแล้วเท่าไหร่ เหลือเท่าไหร่
/dev/sda  หรือ /dev/hda  #เป็นไฟล์ที่เป็นตัวแทนของ hard disk ตัวแรกของเครื่องขณะนั้น (sda=SATA hard disk, hda=PATA hard disk)

รวมถึงการติดต่อกับ network ก็สามารถทำการติดต่อได้ในรูปแบบนี้
/dev/tcp/host/port    #สำหรับการติดต่อด้วย TCP
/dev/udp/host/port    #สำหรับการติดต่อด้วย UDP
ตัวอย่าง เช่น
/dev/tcp/www.google.com/80
/dev/tcp/127.0.0.1/telnet      #ชื่อ port จะไปแม๊ปกับข้อมูลในไฟล์ /etc/services
/dev/udp/8.8.8.8/53

การติดต่อกับ network แบบนี้ Linux ก็จะมองว่ามันเป็นไฟล์หมดเลย ดังนั้นจึงต้องใช้วิธีการจัดการแบบไฟล์ด้วย

ดังนั้น... มารื้อฟื้นการจัดการไฟล์บน unix กันก่อนละกัน

File Descriptor
การจัดการไฟล์ของ linux จะต้องมีการเปิดไฟล์ และตั้งหมายเลขให้กับแต่ละไฟล์ที่เปิดขึ้นมาก่อน
ยกตัวอย่างเช่น
exec 9<>/etc/passwd
จะเป็นการเปิดไฟล์ /etc/passwd โดยให้ใช้หมายเลข 9 แทนไฟล์ๆนี้  และเป็นการเปิดไฟล์แบบสองทางคือ เปิดเพื่ออ่าน และ เปิดเพื่อเขียนลงไป โดยใช้ไฟล์หมายเลขเดียวกัน
(ปล. ถ้าจะเปิดไฟล์แค่ทางเดียว ให้เปลี่ยนเครื่องหมาย "<>" เป็น:  "<" สำหรับเปิดไฟล์เพื่ออ่านอย่างเดียว หรือ ">" สำหรับเปิดไฟล์เพื่อเขียนลงอย่างเดียว)

การตั้งหมายเลขไฟล์ใหม่ จะใช้หมายเลขอะไรก็ได้ที่เป็นเลขจำนวนเต็มบวก ยกเว้นสามตัวนี้ซึ่งเป็น file descriptor ที่ linux ตั้งมาให้เป็นมาตรฐานอยู่แล้วคือ
   0    หมายถึง standard input  เช่นถ้าเป็นบน console: จะหมายถึงการกด keyboard
   1    หมายถึง standard output  เช่นถ้าเป็นบน console:  จะหมายถึงการแสดงผลออกทางหน้าจอ
   2    หมายถึง standard error  เช่น ไว้แสดงผล error ที่เกิดจากคำสั่งต่างๆ โดยปกติบน console จะหมายถึงสิ่งเดียวกับ 1  (ซึ่งก็คือแสดงออกทางหน้าจอ)

การเล่นกับ File Descriptor

ก่อนอื่นต้องว่าด้วยการทำ redirection ก่อน
Redirection ก็คือ การถ่ายโอนข้อมูลจากคำสั่งนึงๆ หรือ file descriptor อันนึงให้กับตัวอื่นๆ
สิ่งที่น่าจะเคยทำกันแล้วก็คือ
เมื่อต้องการให้ แสดงผลของไฟล์(จากเดิมที่แสดงออกหน้าจอ) ให้แสดงออกลงไฟล์ คือ
ls > /tmp/abc.txt

การใช้ output redirection (เครื่องหมาย ">" )
คือหมายถึงการเอาข้อมูลที่จากเดิมจะแสดงออกทางหน้าจอ(file descriptor หมายเลข 1) ให้ออกลงไฟล์ที่ระบุไว้ด้านหลังเครื่องหมาย >

ดังนั้น คำสั่งข้างบนก็จะเหมือนกับการใช้
ls 1> /tmp/abc.txt

หมายถึงว่า เราสามารถระบุ file descriptor ให้ชัดเจนก่อนเครื่องหมาย ">" (ห้ามมีวรรคระหว่างหมายเลข file descriptor กับ > ) ได้ว่าเราต้องการจะเอาข้อมูลจาก file descriptor นี้ไป redirect ลงไฟล์

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

แต่ว่า ในบางกรณีเราก็ต้องการที่จะทำการเปิดไฟล์เอง ปิดไฟล์เอง และต้องการให้ redirection ทำการโอนข้อมูลเข้ามาในไฟล์ที่เปิดไว้แล้ว(มี file descriptor แล้ว) จะทำยังไง?

ตรงนี้จะเป็นการ redirect ข้อมูลลง file descriptor จะต้องใช้เครื่องหมาย >& แทน
คือถ้ามี & ตามหลังเครื่องหมาย redirection ใดๆ จะแปลว่า จะเป็นการกระทำกับ file descriptor
ยกตัวอย่างเช่น
ls 1>/tmp/abc.txt 2>&1

คำสั่งนี้จะหมายความว่า หลังจากคำสั่ง ls ได้ค่าออกมาแล้ว ให้ standard output(หมายเลขไฟล์=1)เขียนข้อมูลลงในไฟล์ /tmp/abc.txt  หลังจากนั้นจึงให้ standard error (หมายเลขไฟล์=2) ส่งข้อมูลลง standard output (หมายเลขไฟล์=1) ซึ่งตอนนี้ถูกตั้งไว้ให้ชี้ไปที่ไฟล์ /tmp/abc.txt  ดังนั้นจะได้ผลลัพธ์คือ ทั้ง error และผลลัพธ์ปกติ จะถูกเขียนอยู่ในไฟล์ /tmp/abc.txt ทั้งหมด

พอทำ redirection ของฝั่ง output ได้แล้ว(คือสัญลักษณ์ >, >&) มาดูด้านการ input ด้วย redirection บ้าง

การส่งข้อมูลเข้า มีอยู่ด้วยกันสองทางคือ การใช้เครื่องหมาย < และ การใช้ | (อ่านว่า ไปป์ pipe)
การใช้ input redirection (เครื่องหมาย "<")
ก็จะคล้ายกันกับข้างบน ก็คือ จะต้องตามหลังด้วยไฟล์ (ไม่ใช่หมายเลขไฟล์) แล้วระบบการ redirect นี้จะทำการเปิดไฟล์ให้เอง,อ่านไฟล์,ส่งข้อมูลให้กับโปรแกรม,และปิดไฟล์ให้โดยอัตโนมัติ ยกตัวอย่างเช่น
cat < /tmp/abc.txt

ก็จะเอาข้อมูลในไฟล์ /tmp/abc.txt ออกมา และโอนข้อมูลให้กับโปรแกรม cat ซึ่งก็จะแสดงผลออกมาทางหน้าจอ(หมายเลขไฟล์=1)

และเช่นเดียวกันกับเครื่องหมาย output redirection (>) ถ้าต้องการใช้ input แบบเป็นหมายเลขไฟล์ก็จะต้องใช้ & ตามหลังเครื่อง redirection เหมือนกัน เช่น
cat <& 9

ก็จะเป็นการแสดงข้อมูลทั้งหมดของไฟล์ที่ได้เปิดเอาไว้แล้วและตั้งหมายเลข file descriptor ให้ว่าเป็นหมายเลข 9

การใช้ pipe (เครื่องหมาย "|")
ก็เป็นอีกวิธีในการ redirect ข้อมูล output ของโปรแกรมหนึ่ง เพื่อให้กลายมาเป็นข้อมูล input ของอีกโปรแกรมหนึ่ง เช่น
ls | more
หมายความว่า สั่งให้ ls ทำงานไป เมื่อ ls มีข้อมูลที่จะแสดงออก standard output(หมายเลขไฟล์=1) ก็จะให้นำข้อมูลนั้นมาเป็น standard input(หมายเลขไฟล์=0) ของโปรแกรม more ต่อไป ทำให้โปรแกรม more ได้รับข้อมูลที่โปรแกรม ls แสดงผลออกมาได้โดยตรง โดยไม่ต้องมีการพักข้อมูลลงไฟล์ก่อนแล้วค่อยอ่านขึ้นมาอีกที

การปิดไฟล์
เนื่องจากเมื่อเปิดไฟล์ขึ้นมาแล้ว แต่ละ file descriptor นั้น จะสามารถรับส่งข้อมูลได้สองทางหรือทางเดียว ขึ้นอยู่กับตอนที่เปิดไฟล์ ดังนั้นการปิดไฟล์ก็จะเลือกปิดได้สองวิธี(หรือต้องปิดทั้งสองวิธี) คือ
exec 9<&-      # ปิดช่องทาง input เข้าไฟล์หมายเลข 9
exec 9>&-      # ปิดช่องทาง output ออกจากไฟล์หมายเลข 9

คราวนี้ พอได้พื้นฐานของการเล่นกับไฟล์บน linux แล้ว ก็จะเอาทุกอย่างมาประยุกต์เพื่อเล่นกับ network กันละ

อย่างที่บอกไว้ในตอนแรกว่า  การเชื่อมต่อผ่านทาง network ของ linux ก็สามารถมองให้เป็นการใช้ไฟล์ๆนึงได้
โดยการเขียนเข้าไฟล์ หมายถึง การส่งข้อมูลผ่าน network
และการอ่านข้อมูลจากไฟล์ ก็หมายถึง การรับข้อมูลจากทาง network มาประมวลผลต่อ

ยกตัวอย่างโค้ดอย่างง่ายๆ ก่อนละกัน  โค้ดต่อไปนี้เป็นการติดต่อไปที่ www.google.com ผ่านทาง port 80 เพื่อดึงหน้าเวปหน้าแรกออกมา โดยคำสั่งจะเป็น

exec 7 <> /dev/tcp/www.google.com/80
echo -e "GET / HTTP/1.0\n\n" >& 7
while read line <& 7
do
   echo -n $line
done
exec 7<&-
exec 7>&-


มาดูกันทีละคำสั่งละกัน
     exec 7 <> /dev/tcp/www.google.com/80   #เป็นการเปิดช่องทางแบบ TCP ติดต่อไปหา www.google.com ด้วย port 80 โดยจะให้ใช้ไฟล์หมายเลข 7 แทนช่องทางการติดต่อนี้

     echo -e "GET / HTTP/1.0\n\n" >& 7    # คำสั่งนี้เป็นคำสั่งที่ให้ส่งคำสั่ง HTTP ว่า ขอดึงข้อมูลจากหน้าแรก ("/") หน่อย  โดยใช้ echo ส่งคำสั่งนี้ไปใส่ในไฟล์หมายเลข 7 ซึ่งก็คือช่องทางติดต่อ www.google.com ที่เราเปิดเอาไว้แล้ว   และการใช้ "-e" หลังคำสั่ง echo คือการให้คำสั่ง echo แปลความหมายของ "\n\n" ว่าเป็นการขึ้นบรรทัดใหม่ด้วย ไม่ใช่ว่าต้องการจะแสดงออกมาเป็น "\"+"n"+"\"+"n"

    read line <& 7        # คำสั่ง read นี้เป็นการอ่านข้อมูลจากไฟล์หมายเลข 7 มาทีละบรรทัด และเก็บข้อมูลบรรทัดที่อ่านมาได้นี้ลงในตัวแปรที่ชื่อ line  (เวลานำไปใช้ต้องใส่เครื่องหมาย "$" นำหน้าด้วย เป็น "$line")

    while ... do ......... done    # เป็นคำสั่งวนรอบ โดยจะวนซ้ำ ทำคำสั่งที่อยู่ระหว่าง do....done จนกว่า คำสั่ง read นั้นจะให้ผลลัพธ์ออกมาเป็นเท็จ(แปลว่า ไม่มีข้อมูลเหลือในไฟล์หมายเลข 7 ให้อ่านแล้ว) แล้วจึงลงมาทำคำสั่งที่ถัดจาก done ต่อไป

    echo -n $line         # แสดงค่าที่เก็บอยู่ในตัวแปร line ออกมา โดยมี "-n" เพื่อบอกให้ echo ไม่ต้องเพิ่มการบรรทัดใหม่ต่อท้ายข้อมูลให้ (ปกติ echo จะมีการเพิ่มการขึ้นบรรทัดใหม่ให้หนึ่งครั้งหลังจากพิมพ์ข้อมูลเสร็จ)

   exec 7<&-  และ exec 7>&-    # ก็คือการปิดการใช้งานไฟล์หมายเลข 7  ซึ่งก็มีผลเท่ากับการจบการติดต่อกับ www.google.com ด้วย


จบเพียงแค่นี้ดีกว่า  ลองเล่นกันดูละกันนะคับ

---- อัพเดต ----
 - แก้ไขข้อผิดพลาด ls 2>&1 1>/tmp/abc.txt  เป็น ls 1>/tmp/abc.txt 2>&1 แทน

7/12/2012

Base64 encoding/decoding on Java

วันนี้ลองตั้งโจทย์ให้ตัวเอง ว่าลองทำ base64 encoding/decoding บน Java ซักหน่อย
คือเห็นว่าบน Ruby ทำได้ง่ายมากเลย เช่น
> base64=["abc"].pack("m*").chomp
 => "YWJj"
> base64.unpack("m*")[0]
 => "abc"

พอคิดว่าถ้าจะทำบน Java ก็คงทำได้ง่ายๆแหละ คิดว่าจะง่ายๆ แต่ทว่าจริงๆแล้วไม่ได้ง่ายเลย
ลอง search Google ดูปรากฎว่า Java ไม่มี built-in library สำหรับทำเรื่องนี้เลย
คือ  solution ที่จะทำได้มีดังนี้คือ

  1. เขียนเอง :  ไม่ทำแน่ๆ เพราะเยอะเกินไป และโค้ดจะรกมาก (ถ้าต้องทำก็ search หาโค้ดมาแปะเลยได้)
  2. ใช้ Built-in library ของ Sun : ใช้ class ชื่อ sun.misc.BASE64Decoder กับ sun.misc.BASE64Encoder สองตัวนี้เป็น Java's built-in library แต่ทว่าเป็น library ที่ใช้ภายในของ Sun ซึ่งจะไม่มีเอกสาร API ให้ดูว่าใช้ยังไง และที่สำคัญจะถูกยกเลิกในเร็วๆนี้  ดังนั้นตัวนี้ก็ไม่ใช่เป็นทางเลือกที่ดีนัก แต่ถ้าจะใช้ก็...
    import java.io.IOException;
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    ....
    BASE64Decoder decoder = new BASE64Decoder();
    BASE64Encoder encoder = new BASE64Encoder();
    try{
        String base64 = encoder.encodeBuffer("abc".getBytes("UTF-8"));
        byte[] normal = decoder.decodeBuffer(base64);
    }catch (IOException e){
        e.printStackTrace();
    }
    ...
    
  3. ใช้ Library ของ JavaMail (http://www.oracle.com/technetwork/java/javamail/index.html): โดยใช้ class ชื่อ javax.mail.internet.MimeUtility JavaMail ตัวนี้ต้องมีการดาวน์โหลด jar ของ JavaMail มาแล้วใส่ไว้ใน classpath เพื่อใช้งาน
  4. ใช้ Library ของ Apache Common Codec (http://commons.apache.org/codec/): โดยใช้คลาสชื่อ org.apache.commons.codec.binary.Base64 ข้อดีคือเป็น library ของ Apache แต่ว่ายังไงก็ต้องโหลด jar มาเพิ่มอีกอยู่ดี
  5. ใช้ Library ของ MiGBase64 (http://migbase64.sourceforge.net/) : ตัวนี้ก็อีกเช่นกันที่ต้องโหลด library มาเพิ่มเพื่อรัน
  6. ใช้ Library ของ Jetty (http://download.eclipse.org/jetty/stable-7/apidocs/org/eclipse/jetty/util/B64Code.html)
  7. ใช้ Library ของ iHarder (http://iharder.sourceforge.net/current/java/base64/)
  8. ใช้ Built-in library ของ Java ชื่อ com.sun.org.apache.xml.internal.security.utils.Base64 แต่ว่ามันเป็นส่วนภายในซึ่งจะไม่มีเอกสารอ้างอิง
  9. ใช้ Built-in library ของ Java ชื่อ java.util.prefs.Base64 แต่ว่ามันเป็นส่วนภายในซึ่งจะไม่มีเอกสารอ้างอิง
  10. ใช้ Built-in library ของ Java ชื่อ javax.xml.bind.DatatypeConverter  ซึ่งเพิ่งเพิ่มมาใน Java6 และสามารถทำ Base64 encode/decode ได้  พร้อมเอกสาร API ที่ http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/DatatypeConverter.html
คือสรุปแล้วว่า   Java แทบจะไม่มีคำสั่งนี้ให้มาตั้งแต่แรกเลย ทั้งๆที่มันควรจะเป็นคำสั่งพื้นฐานในการจัดการกับข้อมูลสำหรับส่ง binary data โดยเฉพาะผ่านทาง HTTP  
จากที่ลิสต์จะเห็นว่า 6/10 เป็น library ที่จะต้องไปโหลดไฟล์ .jar เพิ่มเพื่อจะใช้งานนั้น
ส่วน 4/10 เป็น library ที่ built-in ใน Java เอง  แต่ใช้งานได้อย่างมาตรฐาน มีเอกสารประกอบครบเพียงแค่ 1 วิธีเท่านั้น  อีก 3 วิธีที่เหลือเป็นคำสั่งที่ใช้งานกันภายในผู้พัฒนาJava ซึ่งจะไม่มีเอกสารประกอบว่าจะใช้งานยังไง แล้วก็อาจจะถูกตัดทิ้งได้ทุกเวลา ก็จะทำให้ไม่สมควรจะใช้ในงานจริงๆ

ข้างล่างต่อไปนี้เป็นโค้ดตัวอย่างของการใช้วิธีที่ 10 (javax.xml.bind.DatatypeConverter)
class TestBase64{
    public static void main(String[] args){
        /*** ascii art gun: from http://www.ascii-art.de/ascii/ghi/gun.txt ***/
        String original_word = 
        "  +-'~`---------------------------------/\\--\n"+
        " ||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" \\\\\\\\\\\\  \\/~)\n"+
        " ||                                  \\\\\\\\\\\\  \\/_\n"+
        "  |~~~~~~~~-________________-_________________\\ ~--_\n"+
        "  !---------|_________       ------~~~~~(--   )--~~\n"+
        "                      \\ /~~~~\\~~\\   )--- \\_ /(\n"+
        "                       ||     |  | \\   ()   \\\\\n"+
        "                       \\____/_ / ()\\        \\\\\n"+
        "                        `~~~~~~~~~-. \\        \\\\\n"+
        "                                    \\ \\  <($)> \\\\\n"+
        "                                     \\ \\        \\\\\n"+
        "                                      \\ \\        \\\\\n"+
        "                                       \\ \\        \\\\\n"+
        "                                        \\ \\  ()    \\|\n"+
        "                                        _\\_\\__====~~~\n";
        System.out.println(original_word);
        try{
            String base64 = DatatypeConverter.printBase64Binary(original_word.getBytes("UTF-8"));
            System.out.println(base64);
            String result = new String(DatatypeConverter.parseBase64Binary(base64),"UTF-8");
            System.out.println(result);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

เอกสารอ้างอิง
[ - ] http://www.javatips.net/blog/2011/08/how-to-encode-and-decode-in-base64-using-java
[ - ] http://stackoverflow.com/questions/469695/decode-base64-data-in-java