ป้ายกำกับ: Features

POSIX: Shell ScriptPOSIX: Shell Script

การเขียน Shell Script ให้เป็นไปตามมาตรฐาน POSIX (POSIX-compliant) มีเป้าหมายหลักเพื่อให้สคริปต์สามารถรันได้บนทุกระบบปฏิบัติการตระกูล Unix-like ตั้งแต่ Linux distributions ต่างๆ, macOS, FreeBSD ไปจนถึงสภาพแวดล้อมขนาดเล็กอย่าง Alpine Linux (BusyBox ash) หรือคอนเทนเนอร์ Docker โดยไม่พึ่งพาฟีเจอร์เฉพาะของ Bash (Bashisms)


กฎพื้นฐาน 7 ประการในการหลีกเลี่ยง Bashisms


1. Shebang ต้องชี้ไปที่ /bin/sh

อย่าใช้ #!/bin/bash เพราะในบางระบบ เช่น FreeBSD หรือ Alpine อาจไม่มี Bash ติดตั้งมาแต่แรก

BASH / Shell

#!/bin/sh
set -eu
  • set -e: สั่งให้ออกจากสคริปต์ทันทีเมื่อคำสั่งใดคำสั่งหนึ่งส่งคืน exit status ที่ไม่ใช่ 0
  • set -u: ป้องกันการเรียกใช้ตัวแปรที่ยังไม่ได้ประกาศ (Unset variables)

2. ใช้ [ (test) แทน [[

[[ ... ]] เป็นไวยากรณ์เฉพาะของ Bash/Zsh/Ksh เท่านั้น ใน POSIX ต้องใช้ [ หรือคำสั่ง test และใช้เครื่องมือเปรียบเทียบมาตรฐาน

BASH / Shell

# แบบ POSIX (ถูกต้อง)
if [ "$env_name" = "production" ] && [ -f "$config_file" ]; then
    echo "Running in production"
fi
# ห้ามใช้ (Bashism):
# if [[ $env_name == "production" && -f $config_file ]]; then ...
  • สำหรับการเปรียบเทียบ String ให้ใช้ = เพียงตัวเดียว ไม่ใช่ ==
  • รวมเงื่อนไขด้วย [ ... ] && [ ... ] ไม่ใช้ -a หรือ -o (เนื่องจากพฤติกรรมในบางระบบอาจกำกวมตามข้อกำหนด POSIX)

3. การประกาศฟังก์ชัน (Function Declaration)

POSIX ไม่รองรับคีย์เวิร์ด function ให้ใช้รูปแบบ name() โดยตรง และหลีกเลี่ยงการใช้คำสั่ง local

BASH / Shell

# แบบ POSIX (ถูกต้อง)
deploy_app() {
    target_dir="$1"
    echo "Deploying to $target_dir"
}
# ห้ามใช้ (Bashism):
# function deploy_app { local target_dir="$1"; ... }

หมายเหตุเรื่อง Scope ตัวแปร: คำสั่ง local ไม่มีในสเปก POSIX (แม้เชลล์หลายตัวจะรับได้) หากต้องการแยกขอบเขตตัวแปรอย่างเข้มงวด ควรใช้ Subshell ด้วยวงเล็บโค้ง

BASH / Shell

isolate_scope() (
    temp_var="temporary"
    echo "$temp_var"
)

4. การคำนวณคณิตศาสตร์ (Arithmetic Expansion)

ใช้ $(( ... )) สำหรับการคำนวณจำนวนเต็ม ไม่ใช้ let หรือ $[ ... ]

BASH / Shell

count=1
count=$((count + 1))
total=$(((count * 2) + 5))

5. การรันคำสั่งย่อย (Command Substitution)

ใช้ $(command) แทน backticks (`command`) เนื่องจากอ่านง่ายกว่าและสามารถซ้อนกัน (Nesting) ได้โดยไม่มีปัญหา Escape

BASH / Shell

# แบบ POSIX (ถูกต้อง)
current_user="$(whoami)"
app_files="$(find "$target_dir" -type f)"
# ไม่แนะนำ:
# current_user=`whoami`

6. การแสดงผลด้วย printf แทน echo

คำสั่ง echo มีความแตกต่างของ flag สูงมากในแต่ละระบบ (เช่น echo -n หรือ echo -e ไม่ทำงานบนบางเชลล์) POSIX แนะนำให้ใช้ printf เสมอ

BASH / Shell

# แบบ POSIX (ถูกต้องและคาดเดาผลลัพธ์ได้ 100%)
printf "Processing item: %s\n" "$item_name"
printf "Prompt: " # ไม่ขึ้นบรรทัดใหม่ แทน echo -n
# ห้ามใช้ (ไม่สม่ำเสมอ):
# echo -n "Prompt: "
# echo -e "Line 1\nLine 2"