PlusMagi's Blog By Pitt Phunsanit 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ส่งชื่อไฟล์เข้าคำสั่งปลายทางตรงๆ