เริ่มต้นกับสคริปต์ Dash: ความแตกต่างจาก Bash และวิธีเขียนเพื่อให้เข้ากันได้
เคยมีประสบการณ์แบบนี้ไหมครับ "เขียนเชลล์สคริปต์แล้ว แต่ทำงานบน PC ของตัวเองได้ แต่พอไปรันบนเซิร์ฟเวอร์กลับไม่ทำงาน..."? บางทีสาเหตุอาจเป็นเพราะความแตกต่างระหว่าง Dash กับ Bash ก็ได้ครับ
ในบทความนี้ เราจะมาอธิบายวิธีเขียนสคริปต์ Dash ซึ่งเป็นเชลล์มาตรฐานที่ใช้ในระบบ Linux หลายๆ ตัว (โดยเฉพาะ Debian และ Ubuntu) ที่อยู่เบื้องหลัง /bin/sh ครับ เราได้เตรียมตัวอย่างโค้ดที่สามารถคัดลอกไปวางแล้วใช้งานได้ทันที เพื่อให้มือใหม่ได้สัมผัสกับประสบการณ์ "โค้ดที่ใช้งานได้จริง!" นอกจากนี้ยังได้สรุปข้อควรระวังเกี่ยวกับความเข้ากันได้กับ Bash เพื่อให้คุณมีทักษะในการเขียนสคริปต์ที่มีความเสถียรและนำไปใช้ได้ในหลายสภาพแวดล้อม
แล้ว Dash คืออะไร? แตกต่างจาก Bash อย่างไร?
โดยปกติแล้ว สิ่งที่เราเรียกกันติดปากว่า "เชลล์สคริปต์" ส่วนใหญ่มักจะหมายถึง Bash (Bourne Again SHell) ซึ่งเป็นเชลล์ที่มีฟังก์ชันการทำงานสูงและมีรูปแบบการเขียนที่สะดวกสบายมากมาย
ในทางกลับกัน Dash (Debian Almquist SHell) เป็นเชลล์ที่เรียบง่ายและมีขนาดเล็กกว่า โดยยึดตามมาตรฐาน POSIX เนื่องจากทำงานได้รวดเร็วมาก จึงถูกนำมาใช้เป็นเชลล์มาตรฐาน (/bin/sh) และใช้สำหรับสคริปต์ตอนเปิดเครื่องในระบบปฏิบัติการอย่าง Debian และ Ubuntu
นั่นหมายความว่าสคริปต์ที่ขึ้นต้นด้วย #!/bin/sh อาจทำงานได้บน PC ของคุณ (ซึ่งเป็นสภาพแวดล้อมของ Bash) แต่อาจเกิดข้อผิดพลาดบนเซิร์ฟเวอร์ (ที่เป็นสภาพแวดล้อมของ Dash) ได้ วิธีป้องกันที่ดีที่สุดคือการเรียนรู้วิธีการเขียนที่สอดคล้องกับมาตรฐาน POSIX ซึ่ง Dash สามารถเข้าใจได้
พื้นฐานการเขียนสคริปต์ Dash [คัดลอกไปใช้ได้เลย]
ก่อนอื่น เรามาทำความคุ้นเคยกับไวยากรณ์พื้นฐานของ Dash กันก่อน โค้ดทุกตัวอย่างจะขึ้นต้นด้วย #!/bin/sh ดังนั้นคุณสามารถบันทึกเป็นไฟล์แล้วสั่งรันได้เลย
1. Hello World! - การแสดงผลข้อความ
มาเริ่มกับ "Hello World" ตามธรรมเนียมกันครับ เราใช้คำสั่ง echo เพื่อแสดงผลข้อความ ซึ่งเหมือนกับใน Bash ทุกประการ
#!/bin/sh
# บรรทัดแรกของสคริปต์ เราจะใส่ 'shebang' เพื่อระบุว่าจะให้เชลล์ตัวไหนรันสคริปต์นี้
echo "Hello, Dash World!"
2. การใช้ตัวแปร
กฎคือเวลาประกาศตัวแปรจะต้องไม่มีการเว้นวรรคหน้าและหลังเครื่องหมาย = และเวลาเรียกใช้ ให้ใส่เครื่องหมาย $ นำหน้าชื่อตัวแปร ซึ่งก็เหมือนกับใน Bash ครับ
#!/bin/sh
GREETING="สวัสดี"
TARGET="ชาวโลก"
echo "${GREETING}, ${TARGET}!" # แนะนำให้ครอบชื่อตัวแปรด้วย {} เพื่อความชัดเจน
3. การสร้างเงื่อนไขด้วย if
เราใช้คำสั่ง if ในการสร้างเงื่อนไข โดยนิพจน์เงื่อนไขจะถูกครอบด้วย [ ... ] นี่คือข้อควรระวังข้อแรก เพราะ [[ ... ]] ที่เห็นบ่อยใน Bash นั้นไม่สามารถใช้ใน Dash ได้ (จะอธิบายอย่างละเอียดในภายหลัง)
#!/bin/sh
USER_NAME="Alice"
# จำเป็นต้องเว้นวรรคหน้าและหลัง [ ] เสมอ
if [ "$USER_NAME" = "Alice" ]; then
echo "ยินดีต้อนรับ คุณ Alice"
else
echo "คุณคือใคร?"
fi
4. การทำซ้ำด้วย for loop
เรามาลองเขียน for loop แบบง่ายๆ โดยใช้รายการที่คั่นด้วยช่องว่างกัน
#!/bin/sh
# ประมวลผลข้อความที่คั่นด้วยช่องว่างด้วย loop
for fruit in apple banana cherry
do
echo "ฉันชอบ ${fruit}"
done
[สำคัญที่สุด] ข้อควรระวังเรื่องความเข้ากันได้กับ Bash
จากนี้ไปคือประเด็นสำคัญครับ ฟีเจอร์ที่สะดวกสบายซึ่งใช้ได้เป็นปกติใน Bash อาจทำให้เกิดข้อผิดพลาดใน Dash ได้ เรามาดูตัวอย่างที่พบบ่อยกัน
ข้อแตกต่างที่ 1: Array (แถวลำดับ)
Dash ไม่รองรับ Array นี่คือความแตกต่างที่สำคัญมาก
❌ วิธีเขียนแบบ Bash (ใช้ใน Dash ไม่ได้)
#!/bin/sh
# โค้ดนี้จะเกิดข้อผิดพลาดใน Dash!
fruits=("apple" "banana" "cherry")
echo "ผลไม้ชนิดแรกคือ ${fruits[0]}"
⭕ วิธีแก้ใน Dash
เราสามารถทำงานลักษณะเดียวกันได้โดยใช้ข้อความที่คั่นด้วยช่องว่าง หรือใช้รายการ argument ($@)
#!/bin/sh
# loop ที่สอดคล้องกับมาตรฐาน POSIX
fruit_list="apple banana cherry"
for fruit in $fruit_list
do
echo "กำลังประมวลผลผลไม้: $fruit"
done
ข้อแตกต่างที่ 2: นิพจน์เงื่อนไข [[ ... ]] vs [ ... ]
Dash ไม่สามารถแปลความหมายของ [[ ... ]] ได้ เราต้องใช้นิพจน์เงื่อนไขแบบ [ ... ] เสมอ (ซึ่งเป็นรูปแบบย่อของคำสั่ง test)
❌ วิธีเขียนแบบ Bash (ใช้ใน Dash ไม่ได้)[[ นั้นสะดวกสำหรับการเปรียบเทียบข้อความและตัวดำเนินการตรรกะ (&&, ||) แต่มันเป็นส่วนขยายของ Bash โดยเฉพาะ
#!/bin/sh
# โค้ดนี้จะเกิดข้อผิดพลาดใน Dash!
COUNT=10
if [[ "$USER" = "root" && $COUNT -gt 5 ]]; then
echo "เงื่อนไขตรงกัน"
fi
⭕ วิธีเขียนแบบ Dash (สอดคล้องกับ POSIX)
ใช้ [ ... ] และใช้ -a แทน AND และ -o แทน OR
#!/bin/sh
# วิธีเขียนที่คำนึงถึงความเข้ากันได้
COUNT=10
# หากต้องการรวมหลายเงื่อนไข ให้ใช้ -a (AND) หรือ -o (OR)
if [ "$USER" = "root" -a $COUNT -gt 5 ]; then
echo "เงื่อนไขตรงกัน"
fi
ข้อแตกต่างที่ 3: คีย์เวิร์ด function
ใน Dash ไม่สามารถใช้คีย์เวิร์ด function ได้ ต้องประกาศฟังก์ชันในรูปแบบ function_name() { ... }
❌ วิธีเขียนแบบ Bash (ใช้ใน Dash ไม่ได้)
#!/bin/sh
# โค้ดนี้จะเกิดข้อผิดพลาดใน Dash!
function say_hello() {
echo "Hello from function!"
}
say_hello
⭕ วิธีเขียนแบบ Dash (สอดคล้องกับ POSIX)
นี่คือรูปแบบการเขียนมาตรฐานดั้งเดิม และแน่นอนว่าสามารถทำงานใน Bash ได้เช่นกัน
#!/bin/sh
# การประกาศฟังก์ชันที่สอดคล้องกับ POSIX
say_hello() {
echo "สวัสดี! ถูกเรียกจากฟังก์ชัน"
}
# การเรียกใช้ฟังก์ชัน
say_hello
ข้อแตกต่างที่ 4: Process Substitution <()
Process substitution <() ซึ่งช่วยให้เราจัดการกับผลลัพธ์ของคำสั่งเหมือนเป็นไฟล์นั้น เป็นฟีเจอร์ของ Bash
❌ วิธีเขียนแบบ Bash (ใช้ใน Dash ไม่ได้)
#!/bin/sh
# โค้ดนี้จะเกิดข้อผิดพลาดใน Dash!
# คำสั่ง comm ใช้เปรียบเทียบไฟล์ที่เรียงลำดับแล้ว 2 ไฟล์
comm <(ls -1 dir1) <(ls -1 dir2)
⭕ วิธีแก้ใน Dash
ให้บันทึกผลลัพธ์ลงในไฟล์ชั่วคราวก่อน หรือใช้ pipeline ในการประมวลผล
#!/bin/sh
# วิธีการใช้ไฟล์ชั่วคราว
TMP1="/tmp/dir1_list"
TMP2="/tmp/dir2_list"
ls -1 dir1 > "$TMP1"
ls -1 dir2 > "$TMP2"
comm "$TMP1" "$TMP2"
# อย่าลืมลบไฟล์ชั่วคราวทิ้ง
rm "$TMP1" "$TMP2"
ข้อแตกต่างที่ 5: Brace Expansion {1..10}
Brace expansion ซึ่งใช้สร้างชุดตัวเลขหรือข้อความต่อเนื่อง เป็นฟีเจอร์ของ Bash
❌ วิธีเขียนแบบ Bash (ใช้ใน Dash ไม่ได้)
#!/bin/sh
# ใน Dash โค้ดนี้จะแสดงผลข้อความ {1..5} ออกมาตรงๆ
echo "กำลังสร้างไฟล์ file{1..5}.txt"
touch file_{1..5}.txt
⭕ วิธีแก้ใน Dash
สำหรับการสร้างเลขลำดับ ให้ใช้คำสั่ง seq ที่มีมาแต่ดั้งเดิม
#!/bin/sh
# ใช้คำสั่ง seq ร่วมกับ for loop
echo "กำลังสร้างไฟล์เรียงตามลำดับตั้งแต่ 1 ถึง 5"
for i in $(seq 1 5)
do
touch "file_${i}.txt"
echo "สร้างไฟล์ลำดับที่ ${i} แล้ว"
done
ตัวอย่างประยุกต์: สคริปต์ที่ใช้งานได้จริงใน Dash
สุดท้าย เรามาลองใช้ความรู้ที่ได้เรียนมาเขียนสคริปต์ที่ใช้งานได้จริงกันดูครับ สคริปต์นี้จะย้ายไฟล์ .log ทั้งหมดในไดเรกทอรีปัจจุบันไปยังไดเรกทอรีสำรองที่ตั้งชื่อตามวันที่ แน่นอนว่าเป็นสคริปต์ที่เข้ากันได้กับ Dash
#!/bin/sh
# ดึงวันที่ปัจจุบันในรูปแบบ YYYY-MM-DD
# รูปแบบของคำสั่ง date เป็นมาตรฐาน POSIX จึงปลอดภัย
BACKUP_DIR="backup_$(date +%Y-%m-%d)"
# ถ้าไม่มีไดเรกทอรีสำรอง ให้สร้างขึ้นมาใหม่
if [ ! -d "$BACKUP_DIR" ]; then
echo "กำลังสร้างไดเรกทอรีสำรอง ${BACKUP_DIR}"
mkdir "$BACKUP_DIR"
fi
# ประมวลผลไฟล์ .log ด้วย loop
# การขยาย wildcard *.log เป็นฟังก์ชันมาตรฐานของเชลล์
for file in *.log
do
# ตรวจสอบว่าไฟล์มีอยู่จริงและเป็นไฟล์ปกติ
if [ -f "$file" ]; then
echo "กำลังย้าย ${file} ไปยัง ${BACKUP_DIR}/"
mv "$file" "$BACKUP_DIR/"
fi
done
echo "การสำรองข้อมูลเสร็จสมบูรณ์"
สรุป
การตระหนักถึงความแตกต่างระหว่าง Dash และ Bash และพยายามเขียนโค้ดที่สอดคล้องกับมาตรฐาน POSIX จะช่วยเพิ่มความเข้ากันได้ของสคริปต์ของคุณได้อย่างมาก ในตอนแรกอาจรู้สึกไม่สะดวกเล็กน้อย แต่ความพยายามอีกนิดนี้จะช่วยป้องกันข้อผิดพลาดที่ไม่คาดคิดจากความแตกต่างของสภาพแวดล้อม และทำให้คุณเข้าใกล้การเป็น 'โปรแกรมเมอร์ที่มีความสามารถ' ไปอีกก้าวหนึ่ง
เนื้อหาที่แนะนำในครั้งนี้เป็นเพียงพื้นฐานแต่สำคัญอย่างยิ่ง ลองนำสคริปต์ที่ขึ้นต้นด้วย #!/bin/sh ไปรันในสภาพแวดล้อมของคุณดูสิครับ แล้วคุณจะได้สัมผัสกับความสบายใจที่สคริปต์ของคุณสามารถทำงานได้ทุกที่!