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

Bash vs Dash: จะเขียน Shell Script เลือกใช้อะไรดี? เปรียบเทียบและวิธีเลือก

เวลาที่เราดูแลหรือพัฒนาเว็บไซต์ ก็คงอยากจะทำให้งานเล็กๆ น้อยๆ ที่ทำซ้ำๆ เป็นอัตโนมัติใช่ไหมครับ? ใน럴 때 "เชลล์สคริปต์ (Shell Script)" คือเครื่องมือที่มีประโยชน์มาก แต่พอจะเริ่มเขียนจริงๆ เคยสงสัยกับโค้ดเหมือนคาถาที่อยู่บรรทัดแรกของไฟล์อย่าง #!/bin/bash หรือ #!/bin/sh บ้างไหมครับว่า "มันต่างกันยังไง?"

จริงๆ แล้วความแตกต่างนี้สำคัญมากนะครับ โดยเฉพาะอย่างยิ่งถ้าอยากจะหลีกเลี่ยงโศกนาฏกรรมอย่าง "บนคอมตัวเองรันได้สมบูรณ์แบบ แต่พอเอาไปรันบนเซิร์ฟเวอร์กลับเจ๊ง!" ความรู้นี้จึงจำเป็นอย่างยิ่ง บทความนี้จะอธิบายความแตกต่างระหว่างเชลล์ที่ใช้กันบ่อย 2 ตัวคือ Bash และ Dash อย่างละเอียด พร้อมโค้ดที่สามารถคัดลอกไปลองทำตามได้ง่ายๆ สำหรับมือใหม่ อ่านบทความนี้จบแล้ว คุณก็จะสามารถเขียนเชลล์สคริปต์ที่แข็งแกร่งและไม่ขึ้นกับสภาพแวดล้อมได้แน่นอนครับ!


Bash กับ Dash คืออะไรกันแน่?

ก่อนอื่น เรามาทำความรู้จักกับพระเอกทั้งสอง (เชลล์ทั้งสองตัว) กันก่อน การได้รู้ว่าแต่ละตัวถนัดอะไรและมีนิสัยอย่างไร คือก้าวแรกสู่การเป็นเพื่อนที่ดีต่อกันครับ

Bash (Bourne-Again SHell) - คู่หูสารพัดประโยชน์ของทุกคน

Bash ถูกใช้เป็น "ล็อกอินเชลล์ (Login Shell)" เริ่มต้นในระบบ Linux ส่วนใหญ่และ macOS (เวอร์ชันเก่าๆ) ซึ่งเป็นเชลล์แรกที่ทำงานเมื่อเราเปิด Terminal ขึ้นมา ฟังก์ชันสะดวกๆ ที่เราใช้กันเป็นประจำโดยไม่รู้ตัว ไม่ว่าจะเป็นการใช้ปุ่มลูกศรย้อนดูประวัติคำสั่ง, การใช้ปุ่ม Tab ช่วยเติมชื่อคำสั่งหรือไฟล์ให้สมบูรณ์, หรือการแสดงผลสีสันต่างๆ... ล้วนเป็นสิ่งที่ Bash เตรียมไว้ให้เราทั้งนั้น เรียกได้ว่าเป็นเชลล์ที่เป็นมิตร มีฟังก์ชันครบครัน และเหมาะอย่างยิ่งสำหรับการใช้งานแบบโต้ตอบ (Interactive) โดยตรงกับผู้ใช้ครับ

Dash (Debian Almquist SHell) - ฮีโร่ผู้อยู่เบื้องหลัง ความเร็วและเบา

ในทางกลับกัน จุดเด่นของ Dash คือความเบาและเร็วเป็นพิเศษ เนื่องจากฟังก์ชันที่ไม่จำเป็นถูกตัดออกไป จึงทำให้มันเชี่ยวชาญด้านการรันสคริปต์ได้อย่างรวดเร็ว ด้วยเหตุนี้ ระบบปฏิบัติการสาย Debian อย่าง Ubuntu จึงเลือกใช้ Dash เป็นเชลล์เริ่มต้นสำหรับสคริปต์ตอนเปิดเครื่อง และเมื่อมีการระบุให้ใช้ #!/bin/sh พูดง่ายๆ ก็คือ ถึงแม้เราจะไม่ค่อยได้เจอมันโดยตรง แต่เบื้องหลังของระบบ Dash ก็กำลังทำงานอย่างแข็งขันอยู่นั่นเองครับ


จุดแตกต่างที่ใหญ่ที่สุด: การรองรับ POSIX ในฐานะ "ภาษากลาง" และ "ภาษาถิ่น"

ความแตกต่างที่ใหญ่ที่สุดระหว่าง Bash และ Dash คือการที่มันปฏิบัติตามมาตรฐานที่เรียกว่า POSIX (โพสิกส์) ได้อย่างเคร่งครัดแค่ไหน

เจอศัพท์เทคนิคเข้าไปอาจจะงงนิดหน่อยนะครับ พูดง่ายๆ ก็คือ POSIX เป็นเหมือน "ภาษากลางของวงการเชลล์สคริปต์" ครับ สคริปต์ที่เขียนด้วยภาษากลางนี้จะได้รับการรับรองว่าจะทำงานได้เหมือนกันในทุกสภาพแวดล้อม (OS)

ฟังก์ชันสะดวกๆ ที่เราใช้ใน Terminal เป็นประจำนั้น ส่วนใหญ่มักเป็น "ภาษาถิ่น" ของ Bash ครับ และในหลายๆ ระบบ /bin/sh จะชี้ไปยังเชลล์ที่ "พูดแต่ภาษากลาง" (เช่น Dash) นี่คือสาเหตุหลักของปรากฏการณ์ "รันบน PC (Bash) ของตัวเองได้ แต่รันบนเซิร์ฟเวอร์ (/bin/sh) ไม่ได้" นั่นเอง

การเขียน #!/bin/bash ในบรรทัดแรกของสคริปต์ ก็เหมือนกับการประกาศว่า "สคริปต์นี้จะใช้ภาษาถิ่นของ Bash นะ!" ทำให้เราสามารถใช้ฟังก์ชันสะดวกๆ ของ Bash ได้ ในทางกลับกัน การเขียน #!/bin/sh ก็เหมือนกับการบอกว่า "ขอเป็นภาษากลางนะครับ!" หากคุณต้องการเพิ่มความสามารถในการนำไปใช้ต่อ (Portability) และความเข้ากันได้ (Compatibility) การใช้ #!/bin/sh และเขียนโค้ดโดยคำนึงถึงมาตรฐาน POSIX จึงเป็นหลักการที่ถูกต้อง


【ภาคปฏิบัติ】เปรียบเทียบด้วยโค้ด! วิธีเขียน Bash กับ Dash ต่างกันอย่างไร?

จากนี้ไป เราจะมาดูโค้ดตัวอย่างที่เป็นรูปธรรม เพื่อเปรียบเทียบ "ภาษาถิ่น" ของ Bash กับ "ภาษากลาง" ที่รองรับ POSIX และสามารถทำงานบน Dash ได้ เพื่อให้ได้สัมผัสกับประสบการณ์ "รันได้จริง" ลองคัดลอกไปรันใน Terminal ของคุณดูได้เลยครับ!

1. การจัดการกับอาร์เรย์ (Array)

อาร์เรย์ใช้เมื่อต้องการจัดการกับค่าหลายๆ ค่าพร้อมกัน ใน Bash สามารถเขียนได้อย่างเป็นธรรมชาติ แต่มาตรฐาน POSIX ไม่มีข้อกำหนดเกี่ยวกับอาร์เรย์ ดังนั้นการจะทำสิ่งเดียวกันใน Dash จึงต้องใช้เทคนิคเล็กน้อย

วิธีเขียนแบบ Bash (ภาษาถิ่น)

สามารถใช้วงเล็บ () เพื่อกำหนดอาร์เรย์ได้อย่างง่ายดาย และใช้ ${my_array[1]} เพื่อเข้าถึงข้อมูลโดยระบุตำแหน่ง (index)

#!/bin/bash

# กำหนดค่าอาร์เรย์
fruits=("apple" "banana" "cherry")

# แสดงผลลัพธ์ตัวที่สอง (index เริ่มจาก 0)
echo ${fruits[1]}

ผลลัพธ์:

banana

วิธีเขียนที่ทำงานบน Dash ได้ (ภาษากลาง)

ในกรณีที่เขียนตามมาตรฐาน POSIX เราจะใช้สตริงที่คั่นด้วยช่องว่าง หรือพารามิเตอร์ตำแหน่งของเชลล์ ($1, $2...) แทนอาร์เรย์ ในที่นี้จะขอแนะนำวิธีใช้ลูป for กับสตริง ซึ่งเป็นวิธีที่นิยมใช้กันทั่วไป

#!/bin/sh

# กำหนดเป็นสตริงที่คั่นด้วยช่องว่าง
fruits="apple banana cherry"

# ใช้ลูป for เพื่อแสดงผลทุกตัวตามลำดับ
for fruit in $fruits; do
  echo "I like $fruit!"
done

ผลลัพธ์:

I like apple!
I like banana!
I like cherry!

2. วิธีเขียนเงื่อนไข

การเขียนเงื่อนไขแบบ "ถ้าเป็น XX ให้ทำ YY" เป็นพื้นฐานของสคริปต์ และในส่วนนี้ก็มีความแตกต่างที่สำคัญเช่นกัน

วิธีเขียนแบบ Bash (ภาษาถิ่น)

ใน Bash สามารถใช้คำสั่งทดสอบที่มีฟังก์ชันสูงกว่าอย่าง [[ ... ]] ได้ ซึ่งสะดวกมากเพราะสามารถเชื่อมหลายเงื่อนไขด้วย &&, เปรียบเทียบสตริงด้วย == หรือแม้กระทั่งจับคู่รูปแบบ (pattern matching) ได้

#!/bin/bash

name="Taro"
age=25

# เชื่อม 2 เงื่อนไขด้วย &&
if [[ "$name" == "Taro" && "$age" -gt 20 ]]; then
  echo "Welcome, Taro!"
fi

ผลลัพธ์:

Welcome, Taro!

วิธีเขียนที่ทำงานบน Dash ได้ (ภาษากลาง)

คำสั่งทดสอบที่รองรับ POSIX คือ [ ... ] ซึ่งเป็นคำสั่งที่มีมาแต่ดั้งเดิมและมีข้อจำกัดบางประการ เช่น การเปรียบเทียบสตริงจะใช้ = (เท่ากับตัวเดียว) และหากมีหลายเงื่อนไข จะต้องใช้ -a (AND) หรือ -o (OR) หรือใช้ [ ] หลายๆ ชุดซ้อนกัน เพื่อความปลอดภัย แนะนำให้ใช้ [ ] หลายชุดจะดีกว่า

#!/bin/sh

name="Taro"
age=25

# เขียนเงื่อนไขโดยใช้ [ ] สองชุด
if [ "$name" = "Taro" ] && [ "$age" -gt 20 ]; then
  echo "Welcome, Taro!"
fi

ผลลัพธ์ (เหมือนกัน):

Welcome, Taro!

3. การแทนที่ข้อความ

กรณีที่ต้องการแทนที่ตัวอักษรหนึ่งในตัวแปรด้วยตัวอักษรอื่น ก็เป็นสิ่งที่เจอบ่อยเช่นกัน

วิธีเขียนแบบ Bash (ภาษาถิ่น)

ใน Bash สามารถแทนที่สตริงได้อย่างง่ายดายด้วย синтаксис ${ตัวแปร/ข้อความเดิม/ข้อความใหม่}

#!/bin/bash

filename="photo_2025.jpg"

# แทนที่ "jpg" ด้วย "png"
new_filename=${filename/jpg/png}

echo $new_filename

ผลลัพธ์:

photo_2025.png

วิธีเขียนที่ทำงานบน Dash ได้ (ภาษากลาง)

หากเขียนตามมาตรฐาน POSIX โดยทั่วไปจะใช้คำสั่งภายนอกอย่าง sed หรือ awk มาช่วยในการจัดการสตริง ในที่นี้จะขอแนะนำตัวอย่างที่ใช้ sed ซึ่งอาจจะยาวกว่าเล็กน้อย แต่ก็ให้ความมั่นใจได้ว่าจะทำงานได้ในทุกสภาพแวดล้อม

#!/bin/sh

filename="photo_2025.jpg"

# ส่งค่าจากตัวแปรไปยังคำสั่ง sed ด้วย echo เพื่อทำการแทนที่
new_filename=$(echo "$filename" | sed 's/jpg/png/')

echo "$new_filename"

ผลลัพธ์ (เหมือนกัน):

photo_2025.png

สรุป: แล้วจะเลือกใช้อันไหนดี?

หลังจากดูความแตกต่างกันมาแล้ว คำถามที่อยากรู้ที่สุดก็คือ "แล้วจะใช้อันไหนดีล่ะ?" คำตอบคือ "แล้วแต่สถานการณ์" ครับ แต่ก็มีแนวทางแนะนำสำหรับมือใหม่อยู่

วิธีเลือกใช้ตามกรณีต่างๆ

คำแนะนำสำหรับมือใหม่

หากคุณเพิ่งเริ่มเรียนรู้การเขียนเชลล์สคริปต์ ขอแนะนำให้เรียนรู้วิธีเขียนที่รองรับ POSIX ด้วย `#!/bin/sh` ตั้งแต่แรกเลยครับ เพราะอะไรน่ะเหรอ? เพราะ "ของใหญ่ใช้แทนของเล็กได้" นั่นเอง สคริปต์ที่เขียนตามมาตรฐาน POSIX จะทำงานบน Bash ได้ไม่มีปัญหา แต่สคริปต์ที่เขียนสำหรับ Bash อาจทำงานบนเชลล์อื่นไม่ได้ หากคุณเชี่ยวชาญ "ภาษากลาง" ตั้งแต่แรก ก็จะไม่เจอปัญหาไม่ว่าจะไปอยู่ในสภาพแวดล้อมไหนครับ


ข้อควรระวัง! "ภาษาถิ่น" ของ Bash (Bashism) ที่มักเผลอใช้

การเขียนโค้ดด้วยความตั้งใจดี แต่กลับกลายเป็น "ภาษาถิ่น" ของ Bash... เป็นเรื่องที่เกิดขึ้นบ่อยครั้ง ในส่วนนี้จะขอแนะนำ "Bashism (แบช-อิซึม)" ที่เจอบ่อยๆ ครับ

เพียงแค่หลีกเลี่ยง Bashism เหล่านี้ ก็จะช่วยเพิ่มความเข้ากันได้ของสคริปต์ของคุณได้อย่างมากเลยทีเดียว


บทสรุป: มาใช้ประโยชน์จากความสะดวกของ Bash และความเสถียรของ Dash กันเถอะ!

ในครั้งนี้ เราได้อธิบายวิธีเขียนเชลล์สคริปต์ที่มีความเข้ากันได้สูง ผ่านความแตกต่างระหว่าง Bash และ Dash

เมื่อคุณเข้าใจแนวคิดนี้แล้ว ก็จะสามารถป้องกันปัญหา "รันไม่ได้เพราะสภาพแวดล้อม" และเขียนสคริปต์ที่เป็นประโยชน์ในสถานการณ์ต่างๆ ได้มากขึ้น มาตั้งเป้าเขียน "เชลล์สคริปต์ที่รันได้ทุกที่" กันตั้งแต่วันนี้เลยไหมครับ?


อ่านบทความนี้ต่อได้เลย

รวมตัวอย่างสคริปต์ Dash (if, loop, ฟังก์ชันง่ายๆ)