หมวดหมู่: Shell Script

Shell Script

รวมรหัสข้อผิดพลาดพบบ่อย (SC Errors) และวิธีแก้เชิงลึกรวมรหัสข้อผิดพลาดพบบ่อย (SC Errors) และวิธีแก้เชิงลึก

รหัสข้อผิดพลาดของ ShellCheck ที่พบบ่อย มักเกิดจากพฤติกรรมเฉพาะของ Shell Script เรื่อง Word Splitting, การดักจับ Exit Status, ขอบเขตตัวแปร และไวยากรณ์เฉพาะระบบ (Bashisms)


1. SC2086: Double quote to prevent globbing and word splitting

สาเหตุ
ไม่ใส่เครื่องหมายคำพูด (Double quotes) ครอบตัวแปร ทำให้ Shell ทำการตัดคำตามช่องว่าง (Word Splitting) และขยาย Wildcard เช่น * (Globbing) หากตัวแปรมีเว้นวรรคหรือเป็นค่าว่าง สคริปต์จะทำงานผิดพลาดทันที

  • โค้ดที่ผิด
    filename="my report.txt" rm $filename # ผลลัพธ์: Shell จะมองเป็นสองอาร์กิวเมนต์คือ `rm my` และ `report.txt`
  • วิธีแก้
    rm "$filename"

ข้อยกเว้น: หากตั้งใจให้เกิด Word Splitting เพื่อแยกข้อความเป็นหลาย Argument ให้ใส่ Directive ปิดเฉพาะบรรทัดนั้น

# shellcheck disable=SC2086
./app $CLI_ARGS


2. SC2181: Check exit code directly with e.g. ‘if mycmd;’, not indirectly with $?

สาเหตุ
ตรวจสอบความสำเร็จของคำสั่งผ่านตัวแปร $? ในบรรทัดถัดไป การทำแบบนี้เสี่ยงต่อการถูกแทรกแซง เพราะหากมีคำสั่งอื่นหลุดมาระหว่างกลาง ค่า $? จะเปลี่ยนไปทันที และทำให้อ่านโค้ดเข้าใจยากขึ้น

  • โค้ดที่ผิด
    grep "ERROR" /var/log/syslog if [ $? -eq 0 ]; then echo "Found error" fi
  • วิธีแก้
    นำคำสั่งไปใส่ไว้หลัง if โดยตรง
    if grep -q "ERROR" /var/log/syslog; then echo "Found error" fi

3. SC2154: Variable is referenced but not assigned

สาเหตุ: มีการเรียกใช้ตัวแปรที่ ShellCheck ตรวจไม่พบการประกาศค่าภายในไฟล์ มักเกิดจากการสะกดชื่อตัวแปรผิด หรือเป็นตัวแปรที่รับมาจาก Environment ภายนอก

  • โค้ดที่ผิด
    target_dir="/tmp" rm -rf "${tagret_dir}"/* # พิมพ์ชื่อตัวแปรผิด กลายเป็นค่าว่าง นำไปสู่ rm -rf /*
  • วิธีแก้
    • แก้ไขการสะกดชื่อตัวแปรให้ตรงกันหากเป็นตัวแปร Environment ภายนอก ให้กำหนดค่า Default Fallback หรือใส่คอมเมนต์บอก ShellCheck
    # แบบที่ 1: กำหนดค่าปลอดภัยหากไม่มีตัวแปร target_dir="${TARGET_DIR:?Target directory must be set}"
    # แบบที่ 2: คอมเมนต์ปิดการเตือนเฉพาะตัวแปร # shellcheck disable=SC2154 printf "Deploying to: %s\n" "$DEPLOY_ENV"

4. SC2164: Use ‘cd … || exit’ or ‘cd … || return’ in case cd fails

สาเหตุ
ใช้คำสั่ง cd โดด ๆ โดยไม่มีเงื่อนไขรองรับกรณีที่ไดเรกทอรีนั้นไม่มีอยู่จริง ทำให้คำสั่งถัดไปถูกรันบนโฟลเดอร์เดิมที่ไม่ได้ตั้งใจ

  • โค้ดที่ผิด
    cd /var/www/build rm -rf ./* # หากเข้าโฟลเดอร์ไม่ได้ จะทำการลบไฟล์ทั้งหมดในโฟลเดอร์ปัจจุบันแทน
  • วิธีแก้
    cd /var/www/build || exit 1 rm -rf ./*

5. SC2034: Variable appears unused. Verify use (or export if used externally)

สาเหตุ
มีการประกาศตัวแปรและกำหนดค่า แต่ไม่มีการเรียกใช้ที่ใดเลยในสคริปต์ มักเกิดจากการลืมใช้งาน สะกดชื่อตัวแปรตอนเรียกใช้ไม่ตรงกับตอนประกาศ หรือเป็นตัวแปรในไฟล์คอนฟิก

  • โค้ดที่ผิด
    port=8080 echo "Server starting on port $PORT" # ประกาศตัวพิมพ์เล็ก แต่เรียกตัวพิมพ์ใหญ่
  • วิธีแก้
    • แก้ไขชื่อตัวแปรให้ตรงกัน หรือลบทิ้งหากไม่จำเป็น
    • หากเป็นตัวแปรที่ตั้งใจส่งต่อไปยัง Subprocess ให้ใช้คำสั่ง export
    export APP_PORT=8080
    • หากเป็นไฟล์รวมตัวแปรคอนฟิก (ไม่ได้ถูกเรียกใช้ในไฟล์นี้โดยตรง)
    # shellcheck disable=SC2034 DATABASE_HOST="127.0.0.1"

6. SC3010 / SC3014: In POSIX sh, [[ ]] / == is not supported (Bashisms)

สาเหตุ
ไฟล์ใช้ Shebang เป็น #!/bin/sh หรือระบุว่ารันบน POSIX Shell แต่มีการใช้ฟีเจอร์เฉพาะของ Bash เช่น วงเล็บเหลี่ยมคู่ [[ หรือเครื่องหมาย ==

  • โค้ดที่ผิด
    #!/bin/sh if [[ "$mode" == "debug" ]]; then echo "Debug active" fi
  • วิธีแก้
    • ปรับให้เป็น POSIX Standard (แนะนำสำหรับพกพาข้ามระบบ)
    #!/bin/sh if [ "$mode" = "debug" ]; then echo "Debug active" fi
    • หรือเปลี่ยน Shebang ไปใช้ Bash หากตั้งใจพึ่งพาฟีเจอร์ของ Bash จริง ๆ
    #!/bin/bash

7. SC2002: Useless cat. Consider ‘cmd < file | ..’ or ‘cmd file’ instead

สาเหตุ
การใช้คำสั่ง cat เพื่ออ่านไฟล์แล้วส่งต่อไปยัง Pipe โดยไม่จำเป็น ทำให้สิ้นเปลืองทรัพยากรสร้าง Process ใหม่ขึ้นมาฟรี ๆ

  • โค้ดที่ผิด
    cat users.txt | grep "admin" cat data.json | jq .
  • วิธีแก้
    grep "admin" users.txt jq . data.json

สรุป Quick Reference การแก้ไข

รหัสข้อผิดพลาดคำอธิบายสั้นวิธีแก้ไขที่ถูกต้อง
SC2086ลืม Quote ตัวแปรครอบตัวแปรด้วย Double Quotes "$var"
SC2181เช็ค $? หลังคำสั่งยกคำสั่งไปไว้หลัง if หรือ && โดยตรง
SC2154ตัวแปรไม่ได้ประกาศค่าเช็คตัวสะกด หรือใช้ ${VAR:-default}
SC2164cd ลอยๆ เสี่ยงรันผิดที่ต่อท้ายด้วย cd path || exit 1
SC2034ตัวแปรไม่ได้ถูกใช้เช็คตัวพิมพ์เล็ก/ใหญ่ หรือใช้ export
SC3010/3014ติด Bashism บน shเปลี่ยนมาใช้ [ และ = หรือเปลี่ยนเป็น #!/bin/bash
SC2002Useless catส่งชื่อไฟล์เข้าคำสั่งปลายทางตรงๆ