มาเล่นกันเถอะ: Bash script

มาเล่นกันเถอะ: Bash script

สวัสดีชาวโลกฮะ

ที่ผ่านมา ผมเขียน articles ที่ใช้ JAVA เสียเป็นส่วนใหญ่นะฮะ แต่จะบอกว่า งานหลักๆ ของผมใช้ Bash ฮะ เพราะต้องยุ่งเกี่ยวกับงานที่รันบน CLI (Command Line Interface) เยอะมากๆ เลยฮะ คราวนี้เลยจะเขียนคำสั่ง Bash ที่ใช้บ่อยๆ ในงานของผมเองฮะ

Ref: https://itsfoss.com/bash-5-release/

Bash?

Bash เป็นภาษานึงในกลุ่ม  Shell scripts ซึ่งอีกตัวนึงที่อยู่ในกลุ่มนี้ คือ cmd และ powershell ของ Windows ฮะ ตัว Bash เองสามารถใช้งานได้บน UNIX เช่น Ubuntu หรือ  MacOS ฮะ ซึ่งความสามารถของ Bash นั่นคือการใช้งานได้ตั้งแต่เริ่มต้นเลย คือ ลงระบบปฏิบัติการ UNIX มาใหม่ๆก็ใช้ได้ทันที การันตีว่ารันได้แน่นอนฮะ ไม่ต้องลง compiler/interpreter เหมือนภาษาระดับสูงอื่นๆ แต่ก็แลกมากับ syntax ที่ต้องอาศัยการเรียนรู้เพิ่มเติมสักหน่อยฮะ

ทีนี้ ในโพสต์นี้จะเล่าถึง command ที่ผมชินมือเนอะ ใช้อะไรกันบ้าง ไปดูกันฮะ


หมวด: ที่อยู่แฟ้มและไฟล์

ในระบบไฟล์ของ Unix เราสามารถใช้ ~ เพื่ออ้างอิงที่อยู่หลัก (root path) ได้นะฮะ และมันมี . เพื่ออ้างอิงแฟ้มปัจจุบัน (current directory) ส่วน .. อ้างอิงแฟ้มก่อนหน้า (parent directory)

ถ้าเราอยากได้ข้อความเป็นที่อยู่ปัจจุบัน ใช้คำสั่ง pwd ส่วน ls ใช้สำหรับลิสต์ชื่อไฟล์หรือแฟ้มที่อยู่ในแฟ้มปัจจุบันได้ฮะ ถ้าต้องการเปลี่ยนที่อยู่ของแฟ้มปัจจุบันใช้ cd

  • ~
    root path
  • .
    current directory
  • ..
    parent directory
  • pwd: print working directory’s name
    ข้อความเป็น current path
  • ls: list
    รายการไฟล์และแฟ้มใน current path
  • cd: change directory
    เปลี่ยน directory

พอผมใช้ ls มันก็จะแสดงชื่อแฟ้มและไฟล์ที่อยู่ในที่อยู่ปัจจุบัน (current path) ยิ่งพอใช้ ls -la (list – long format & all) มันจะแสดงชื่อพร้อมรายละเอียด คือ สิทธิ เจ้าของ วันที่แก้ไขล่าสุด และอื่นๆ ออกมาด้วยฮะ

pwd จะแสดง current path แล้วพอผมจะเข้าไปที่แฟ้มก็ใช้ cd ตามด้วยแฟ้มที่ต้องการฮะ ส่วน cd ~ เท่ากับการเข้าไปที่ root path นะฮะ (/root)


หมวด: จัดการแฟ้มและไฟล์

เราสามารถสร้างแฟ้มได้ด้วยคำสั่ง mkdir และสร้างไฟล์เปล่าๆ ด้วย touch ถ้าไม่ต้องการไฟล์นั้นก็ลบออกด้วย rm แต่ถ้าเป็นแฟ้ม ลบด้วยคำสั่ง rm -r

ต้องการก๊อปปี้ไฟล์ใช้คำสั่ง cp ส่วน mv ใช้ย้ายไฟล์ฮะ

  • rm: remove
    ลบไฟล์ ถ้าลบแฟ้มให้เพิ่ม -r
  • mkdir: make directory
    สร้างแฟ้มใหม่
  • mv: move
    เคลื่อนย้าย หรือเปลี่ยนชื่อไฟล์หรือแฟ้ม
  • cp: copy
    ก๊อปปี้ไฟล์หรือแฟ้ม
  • touch
    สร้างไฟล์เปล่าๆ

ผมสร้างแฟ้มใหม่ด้วย mkdir test01 นะฮะ แล้วพอเข้าไป cd test01 ก็สร้างไฟล์เปล่าด้วย touch test01_a.txt จากนั้นก็ก๊อปปี้มันเป็นอีกไฟล์ด้วย cp test01_a.txt test01_b.txt

จากนั้น ในแฟ้ม test01 ผมก็สร้างแฟ้มชื่อ test02 ไว้ข้างในอีกที แล้วย้ายไฟล์ test01_b.txt เข้าไปด้วยคำสั่ง mv test01_b.txt test02/ นั่นคือเปลี่ยนแปลง path ของ test01_b.txt เป็น test02/test01_b.txt ฮะ (การเปลี่ยนชื่อ ทำได้โดยกำหนดชื่อไฟล์ที่ปลายทาง เช่น mv test01_b.txt test02/test02_b.txt นะฮะ)


หมวด: จัดการสิทธิ

บางครั้ง เราต้องการเปลี่ยนแปลงสิทธิให้คนอื่นมาอ่าน/เขียน/รันไฟล์นี้ได้ ก็ใช้ chmod ส่วน chown คือเปลี่ยนแปลงเจ้าของแฟ้มหรือไฟล์นั้นฮะ

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

  • chmod: change mode
    เปลี่ยนสิทธิของไฟล์หรือแฟ้มนั้นๆ
  • chown: change ownership
    เปลี่ยนชื่อเจ้าของของไฟล์หรือแฟ้มนั้นๆ
  • sudo: superuser do
    อ้างสิทธิผู้ใช้ระดับสูง เมื่อต้องการรันคำสั่งบางประเภท
  • su: switch user
    เปลี่ยนผู้ใช้

จากการใช้ ls -la จะแสดงสิทธิของแต่ละแฟ้มหรือไฟล์ จะเห็นว่าไฟล์ test01_a.txt มีสิทธิดังนี้ฮะ

ls -la
-rw-r--r –  1 root  root    0 Nov 10 14:27 test01_a.txt
# (-)rw-r--r – ==> text01_a.txt is a file
# -(rw-)r--r – ==> OWNER name "root" can read or write the file but cannot execute it
# -rw-(r--)r – ==> GROUP name "root" can read it but cannot write or execute it
# -rw-r--(r--) ==> OTHERS can read it but cannot write or execute it

จากนั้น ผมใช้ chmod +x เพื่อให้สิทธิทุกคน (OWNER, GROUP, OTHERS) สามารถรันไฟล์นี้ได้ฮะ เห็นได้ว่า สิทธิเปลี่ยนเป็น -rwxr-xr-x หลังจากนั้นก็เปลี่ยนชื่อ OWNER จาก root เป็น tester ด้วยคำสั่ง chown -R (Recursive to subfolders) เพื่อให้มีผลกับแฟ้มข้างในนั้นด้วย

ลงท้ายด้วยการเปลี่ยน user เป็น tester ด้วยคำสั่ง su tester ฮะ


หมวด: เครื่องหมายดำเนินการ

  • | (pipe)
    มันคือการทำคำสั่งแบบต่อเป็นทอดๆ ฮะ ทำอันแรกแล้วค่อยทำอันต่อไป ผลลัพท์จะเป็นผลจากอันสุดท้ายฮะ
  • & (ampersand)
    เป็นการทำคำสั่งแบบขนาน หรือพูดอีกอย่าง คือ แบ่งคำสั่งสองฝั่งไปทำงานพร้อมๆกันฮะ ผลลัพท์จะมาจากทั้งสองฝั่ง แต่อาจจะไม่เรียงลำดับกันฮะ ขึ้นอยู่กับว่าคำสั่งไหนรันเสร็จก่อน
  • || (logical OR)
    รันคำสั่งตามลำดับ ถ้ารันคำสั่งแรกสำเร็จ จะถือว่าโปรแกรมรันสำเร็จฮะ และจะได้ผลลัพท์ตามคำสั่งที่สำเร็จ
  • && (logical AND)
    รันคำสั่งตามลำดับ ถ้ารันคำสั่งไม่สำเร็จแม้คำสั่งเดียว จะถือว่าโปรแกรมรันไม่สำเร็จฮะ หรืออีกความหมายนึงคือ จะต้องให้คำสั่งทั้งหมดรันสำเร็จจึงจะถือว่าสำเร็จฮะ

คำสั่งแรก echo "a" | echo "b" คำสั่งแรกรันเสร็จไปแล้ว คำสั่งที่สองจึงรันตาม และได้ผลลัพท์ของคำสั่งที่สอง นั่นคือ แสดงตัว b

คำสั่งที่สอง echo "a" & echo "b" สองคำสั่งรันขนานกัน และได้ผลลัพท์ว่า แสดงตัว b ก่อนตัว a ฮะ

คำสั่งที่สาม echo "a" || echo "b" คำสั่งแรกรันสำเร็จ ถือว่าโปรแกรมสำเร็จ คำสั่งที่สองเลยไม่ได้รัน ผลลัพท์จึงกลายเป็นตัว a ฮะ

คำสั่งสุดท้าย echo "a" && echo "b" คำสั่งแรกรันสำเร็จ แล้วรันคำสั่งที่สอง ก็สำเร็จอีก จึงได้ผลเป็นตัว a และ b ตามลำดับฮะ


หมวด: จัดการข้อความ

เราใช้ echo เพื่อแสดงข้อความออกมา หรือ cat เพื่ออ่านข้อความในไฟล์ได้นะฮะ

ส่วนการเปลี่ยนแปลงข้อความมีหลายคำสั่ง ผมยกตัวอย่างมาเท่าที่ได้ใช้บ่อยๆ ดูได้จากข้างล่างนะฮะ

  • echo
    พิมพ์ข้อความออกมา
  • cat: concatenate
    อ่านเนื้อหาไฟล์
  • sed: stream editor
    เปลี่ยนข้อความเป็นอีกข้อความนึง
  • grep: global search – regular expression – print
    ค้นหาข้อความด้วย regular expression ก่อนจะแสดงผลออกมา
  • cut
    ทำ substring
echo "start"
cat sample_back.sh
echo "a dog wags its tail" | sed -e "s/dog/cat/g"
echo "a fish swims in a jar" | cut -c 1-5
echo "my telephone number is 1234" | grep -o "[0-9]*"

ผมสร้างไฟล์ sample_back.sh มีข้อความเดียวว่า “this is a sample file for reading”

และสร้างอีกไฟล์ คือ sample_front.sh มีคำสั่งพวกนี้ฮะ

  • echo "start" คือ พิมพ์คำว่า start ออกมา
  • cat sample_back.sh อ่านข้อความในไฟล์ sample_back.sh ก็จะได้ข้อความเหมือนข้างบนว่า “this is a sample file for reading”
  • ส่วนอันที่สาม sed -e "s/dog/cat/g" คือเปลี่ยนคำว่า dog เป็น cat ในประโยคตั้งต้น ทำให้ได้ผลสุดท้ายเป็นประโยคว่า “a cat wags its tail” ฮะ
  • อันต่อมา cut -c 1-5 คือ ตัดเอาเฉพาะอักษรลำดับที่ 1-5 ของประโยคออกมา กลายเป็น “a fis” เพราะตัว s อยู่ลำดับที่ห้าพอดี (นับรวมวรรคด้วย)
  • อันสุดท้าย grep -o "[0-9]*" คือ ดึงเอาเฉพาะตัวเลขออกมาจากประโยค “my telephone number is 1234” ก็จะได้ผลลัพท์เป็น “1234” ([0-9]* เป็น regular expression ที่ระบุว่าเป็นตัวเลข 0 ถึง 9 และ *หมายถึงจำนวนตัวเลขกี่ตัวก็ได้ฮะ)

หมวด: วางโครงสร้างเงื่อนไข

โครงสร้างเงื่อนไขหลักๆ ที่ทุกโปรแกรมจะมี ได้แก่

IF-ELSE

if [[ $a == 1 ]]; then 
    echo "a is 1" 
elif [[ $a == 2 ]]; then 
    echo "a is 2" 
else 
    echo "a is not 1 nor 2"
fi 

โครงสร้างตรงไปตรงมาเลยฮะ ifthenelifthenelse แล้วใช้ fi เพื่อจบเงื่อนไขทั้งหมด

FOR-loop

for i in $list; do
    echo this is $i
done

ใช้ในเคสที่เรามีตัวแปรเป็น list แล้วต้องการทำแต่ละตัวใน list นั้นฮะ

WHILE-loop

while [[ $a -le 10 ]]; do # check if a less than or equals 10
    echo "$a";
    a=$((a+1)); # a = a + 1
done

ระวังไม่ให้เป็น infinite loop ด้วยนะฮะ สำหรับ while-loop นี้

TRY-CATCH

Bash ไม่มี try-catch นะฮะ แต่มี syntax ที่ให้ผลลัพท์คล้ายๆ กัน นั่นคือ

{
    # try
} || {
    # catch
}

ข้างบนนี้ คือ กำหนดให้รันโค้ดส่วน try ก่อน ถ้าไม่สำเร็จ ให้รันส่วน catch เชื่อมด้วย ||

{
    # try
} && {
    # then
}

ส่วนข้างบนนี้ คือ กำหนดให้รันโค้ดส่วน try ถ้าสำเร็จ ให้รันส่วน then เชื่อมด้วย &&.

ที่ไฟล์แรก test02_v1.sh ผมใช้ exit 1 เพื่อให้โปรแกรมมันกลายเป็น error ขึ้นมา และกำหนดให้ “ถ้าไม่สำเร็จ ให้แสดงประโยค this is part 2″ ผลลัพท์คือ มันก็แสดงประโยคตามนั้น เพราะคำสั่งก้อนแรกรันไม่สำเร็จ

ส่วนไฟล์ที่สอง test02_v2.sh ทำขั้นตอนแรกเช่นเดิม และกำหนดให้ “ถ้าสำเร็จ จงแสดงประโยค this is part 2″ ผลลัพท์คือมันไม่แสดงอะไรเลย เพราะเครื่องหมาย && ทำให้โปรแกรมไม่รันต่อ จากความที่คำสั่งก้อนแรกรันไม่สำเร็จ

และไฟล์ที่สาม test02_v3.sh ผมไม่ได้ใช้ exit 1 แล้ว และกำหนดว่า “ถ้าสำเร็จ ให้แสดง this is part 2″ ผลคือ มันแสดงทั้งประโยค part 1 และ part 2 ฮะ

นี่คือการทำ error handling โดยกำหนดเงื่อนไขจาก error ที่ปรากฎขึ้นมานะฮะ ผมใช้งานตัวนี้บ่อยเลยแหละฮะ

ข้อควรระวัง คือ เว้นวรรคระหว่างเครื่องหมายเชื่อมกับปีกกาด้วยนะฮะ ถ้าไม่เว้นวรรค อาจจะทำให้รันไม่ผ่านได้ฮะ


หมวด: วันและเวลา

หมวดนี้ ผมใช้บ่อยที่สุดมีแค่คำสั่งเดียว คือ date ฮะ แต่วิธีการปรับใช้ของมันมีหลายแบบเลยแหละฮะ

$(date +"%Y-%m-%d")
$(date -d "-[n] day" +"%Y-%m-%d")
$(date -d "[n] day ago" +"%Y-%m-%)
$(date -d "$(date -d @[epoch_in_seconds])" +"%Y-%m-%d" )
$(date -d "$[target_date] [n] day" +"%Y-%m-%d")
if [[ $(date -d "$date_one" +%s) -le $(date -d "$date_two" +%s) ]]; then
    echo "$date_one is LESS THAN OR EQUAL TO $date_two"
else
    echo "$date_one is GREATER THAN $date_two"
fi

คือ -d มันเป็นการแปลงข้อความให้เป็นวันที่ เช่นตัวอย่างข้างบน -1 day มีค่าเท่ากับ 1 day ago คือ เมื่อวาน หรือ 2019-01-01 1 day ก็คือหนึ่งวันถัดจากวันที่ 2019-01-01 นั่นคือ 2019-01-02 ฮะ

ส่วน @[epoch_in_second] คือ ค่า UNIX epoch ที่เป็นรูปแบบนึงของเวลาที่ระบบ UNIX เข้าใจซึ่งอยู่ในรูปของจำนวนเต็มฮะ เช่น @1546300800 เป็น 2019-01-01 00:00:00 AM (ดูเพิ่มเติมได้ที่ https://www.epochconverter.com ฮะ)

ส่วน +"..." คือกำหนดรูปแบบผลลัพท์ เช่น +"%Y-%m-%d" คือปี-เดือน-วัน เช่น 2019-01-10 ก็คือ วันที่ 10 เดือน 01 มกราคม ปีค.ศ. 2019 ฮะ หรือ +"%s" จะเป็น epoch format ที่อธิบายไปแล้วข้างต้นฮะ วิธีนี้สามารถใช้ในการเปรียบเทียบสองวันได้ฮะ โดยเปรียบเทียบ epoch format ของสองวันแทน ดังตัวอย่างข้างบน อันสุดท้ายฮะ


หมวด: รันคำสั่งภายนอก

เมื่อเรามีไฟล์ bash อื่นๆ อยู่ ใช้ source เพื่ออ้างอิงไฟล์นั้น เหมือนรันคำสั่งข้ามไฟล์ได้ฮะ ส่วน sh เอาไว้รันไฟล์นั้นๆ ฮะ

  • source
    อ้างอิงไฟล์อื่นขณะรันโปรแกรม
  • sh
    สั่งรันไฟล์

ไฟล์แรก test01.sh มีตัวแปรชื่อ test_var พอเราใช้คำสั่ง source test01.sh เราจะสามารถแสดงค่าตัวแปร test_var ได้จากนอกไฟล์ฮะ

ไฟล์สอง test02.sh มีคำสั่ง echo อยู่ เมื่อเราใช้คำสั่ง sh test02.sh ก็จะทำให้คำสั่ง echo ทำงานและแสดงข้อความออกมาฮะ


หมวด: สร้างฟังก์ชันเอง

โครงสร้างฟังก์ชันของ Bash คือ

sample_func() {
  local a=$1 # local variable
  local b=$2 # local variable
  echo "first is $a and second is $b"
}

sample_func 1 2

เราจะต้องเขียนฟังก์ชันไว้ก่อนจะเรียกฟังก์ชันนั้นนะฮะ ส่วนการเรียกฟังก์ชัน คือ พิมพ์ชื่อมันไปเลย ตามด้วย parameters คั่นด้วยวรรค

จากตัวอย่าง คือ เราเรียกฟังก์ชัน sample_func แล้วระบุ parameter เป็น 1 และ 2 ทีนี้ เมื่อเข้าสู่ฟังก์ชัน ค่า 1 จะเป็นตัวแปรที่หนึ่ง อ้างอิงด้วย $1 ก่อนจะถูกใส่ไปยังตัวแปร a และ 2 ที่เป็นตัวแปรที่สอง อ้างอิงด้วย $2 ก็ใส่ไปยังตัวแปร b ก่อนจะแสดงข้อความว่า first is 1 and second is 2


หมวด: คอมเมนต์

เมื่อเราต้องการใส่คอมเมนต์ มีสองวิธี นั่นคือ ใช้ # ไว้หน้าสุดเมื่อคอมเมนต์ทั้งบรรทัดนั้น หรือใช้ : << เพื่อเริ่มต้นคอมเมนต์ ถ้าเราต้องการทำคอมเมนต์แบบหลายๆ บรรทัดนะฮะ

# this is a single-line comment
: << 'comment'
    this 
    is 
    the 
    multi-lines
    of 
    comments
comment

จากรูปข้างบนนะฮะ ตัว b จะไม่แสดงเพราะอยู่ในคอมเมนต์ และตัว d e f ก็ไม่แสดง เพราะอยู่ในคอมเมนต์หลายบรรทัดที่ควบคุมด้วยคำว่า a_comment


ที่ร่ายมาเป็นคำสั่งและ syntax ที่ผมใช้งานบ่อยๆ สำหรับ Bash script นะฮะ ยังมีเยอะมากๆ ที่ไม่ได้เขียนในนี้ และไม่ได้ใช้กับตัวเองนะฮะ ซึ่งผมก็คิดว่า น่าจะมีประโยชน์สำหรับคนอ่านที่ใช้งานภาษานี้นะฮะ

คราวหน้าจะเป็นเรื่องอะไร จะเก็บมาเล่า มาแชร์นะฮะ

บาย

เครดิตข้อมูลอ้างอิง

Show Comments