🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

เริ่มต้นกับสคริปต์ 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 ไปรันในสภาพแวดล้อมของคุณดูสิครับ แล้วคุณจะได้สัมผัสกับความสบายใจที่สคริปต์ของคุณสามารถทำงานได้ทุกที่!


บทความที่เกี่ยวข้อง

Bash vs Dash: ควรเลือกใช้อะไรเขียนเชลล์สคริปต์? การเปรียบเทียบและแนวทาง