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

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 directorypwd
: print working directory’s name
ข้อความเป็น current pathls
: list
รายการไฟล์และแฟ้มใน current pathcd
: 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

โครงสร้างตรงไปตรงมาเลยฮะ if
– then
– elif
– then
– else
แล้วใช้ 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 นะฮะ ยังมีเยอะมากๆ ที่ไม่ได้เขียนในนี้ และไม่ได้ใช้กับตัวเองนะฮะ ซึ่งผมก็คิดว่า น่าจะมีประโยชน์สำหรับคนอ่านที่ใช้งานภาษานี้นะฮะ
คราวหน้าจะเป็นเรื่องอะไร จะเก็บมาเล่า มาแชร์นะฮะ
บาย
เครดิตข้อมูลอ้างอิง
- https://www.tecmint.com/add-users-in-linux/
- https://en.wikipedia.org/wiki/AWK
- https://support.rackspace.com/how-to/checking-linux-file-permissions-with-ls/
- https://stackoverflow.com/questions/6961389/exception-handling-in-shell-scripting/6961470
- https://bashitout.com/2013/05/18/Ampersands-on-the-command-line.html