โหมดเข้มงวดด้วย set -euo pipefail
เปิดใช้การหยุดทำงานทันทีเมื่อเกิดข้อผิดพลาด และทำความเข้าใจว่าแฟล็กของโหมดเข้มงวดแต่ละตัวตรวจจับหรือไม่ตรวจจับข้อผิดพลาดใด
โหมดเข้มงวดด้วย set -euo pipefail เป็นบทเรียน Linux Command Line & Bash Scripting Mastery ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Linux Command Line & Bash Scripting Mastery และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใด Bash จึงล้มเหลวอย่างเงียบ ๆ ตามค่าเริ่มต้น
ตามค่าเริ่มต้น Bash จะยังคงทำงานต่อแม้คำสั่งจะล้มเหลว ซึ่งนำไปสู่ปัญหาร้ายแรงในสคริปต์ที่ใช้งานจริงและตรวจหาสาเหตุได้ยาก
ลองพิจารณาสคริปต์ที่พยายามสร้างข้อมูลสำรองนี้:
- การพิมพ์เส้นทางผิดทำให้
cpล้มเหลว - Bash ไม่สนใจความล้มเหลวและทำงานต่อ
- สคริปต์รายงานว่าสำเร็จ ทั้งที่ไม่ได้สำรองข้อมูลเลย
นี่คือปัญหาความล้มเหลวแบบเงียบ ๆ โหมดเข้มงวดช่วยแก้ปัญหานี้โดยทำให้ Bash ทำงานคล้ายภาษาที่คอมไพล์แล้ว กล่าวคือหยุดทันทีเมื่อเกิดสิ่งผิดพลาด
#!/usr/bin/env bash
# Without strict mode — dangerous default behavior
cp /important/data /backups/data # fails (path doesn't exist)
echo "Backup complete" # still prints — false confidence!
rm -rf /tmp/staging # still runs — potentially destructiveแฟล็กหลักสามรายการ: set -euo pipefail
เปิดใช้โหมดเข้มงวดโดยวางบรรทัดนี้ไว้ใกล้ส่วนต้นของทุกสคริปต์:
set -euo pipefail
คำสั่งนี้จะเปิดใช้ตัวป้องกันสามแบบที่แตกต่างกัน:
-e— จบการทำงานทันทีหากคำสั่งใด ๆ ส่งคืนสถานะที่ไม่ใช่ศูนย์-u— ถือว่าตัวแปรที่ยังไม่ได้กำหนดค่าเป็นข้อผิดพลาด (แทนที่จะขยายเป็นสตริงว่าง)-o pipefail— ไปป์ไลน์จะล้มเหลวหากคำสั่งใด ๆ ในไปป์ไลน์ล้มเหลว ไม่ใช่เฉพาะคำสั่งสุดท้าย
เมื่อนำมารวมกัน ทั้งสามรายการจะเป็นส่วนหัวมาตรฐานเพื่อป้องกันปัญหาในสคริปต์ Bash ที่จริงจัง แฟล็กแต่ละรายการช่วยตรวจจับข้อบกพร่องคนละประเภท
#!/usr/bin/env bash
set -euo pipefail
echo "Strict mode is now active"
echo "Every command failure will abort the script"ทำความเข้าใจ set -e (errexit)
set -e (เขียนได้อีกแบบเป็น set -o errexit) ทำให้สคริปต์จบการทำงานทันทีเมื่อคำสั่งจบลงด้วยสถานะที่ไม่ใช่ศูนย์
พฤติกรรมสำคัญที่ควรทราบ:
- คำสั่งทั่วไป เช่น
false,grep pattern file(ไม่พบข้อมูล) และls /nonexistentล้วนทำให้จบการทำงาน - รหัสจบการทำงานของคำสั่งสุดท้ายในสคริปต์จะกลายเป็นรหัสจบการทำงานของสคริปต์
- คำสั่งในเงื่อนไข
ifจะได้รับยกเว้น —-eจะไม่ทำงานกับนิพจน์ทดสอบ - คำสั่งที่ตามด้วย
|| trueก็ได้รับการยกเว้นเช่นกัน (ดูฉากถัดไป)
ให้คิดว่า -e เป็นแนวป้องกันด่านแรกของคุณจากการทำงานต่ออย่างเงียบ ๆ หลังเกิดความล้มเหลว
#!/usr/bin/env bash
set -e
echo "Before failure"
ls /this/path/does/not/exist # exits here with code 2
echo "This line never runs"ทำความเข้าใจ set -u (nounset)
set -u (เขียนได้อีกแบบเป็น set -o nounset) ทำให้ Bash ถือว่าการอ้างอิงตัวแปรที่ยังไม่ได้กำหนดค่าเป็นข้อผิดพลาดร้ายแรง
หากไม่มี -u การพิมพ์ผิด เช่น $FLENAME แทนที่จะเป็น $FILENAME จะถูกขยายเป็นสตริงว่างอย่างเงียบ ๆ ทำให้คำสั่งทำงานอย่างไม่คาดคิดหรืออาจเป็นอันตรายได้ (ลองนึกภาพ rm -rf "$TMPDIR/" เมื่อไม่ได้กำหนดค่า $TMPDIR)
ข้อยกเว้นสำคัญ:
${VAR:-default}— การแทนค่าปริยายที่ปลอดภัย จะไม่ทำให้-uทำงาน${VAR:+value}— การขยายค่าแบบมีเงื่อนไข ซึ่งปลอดภัยเช่นกัน"$@"และ"$*"จะได้รับการยกเว้นเมื่อไม่มีอาร์กิวเมนต์ตามตำแหน่ง
#!/usr/bin/env bash
set -euo pipefail
# Safe: provide a default for optional vars
OUTPUT_DIR="${1:-/tmp/output}"
LOG_LEVEL="${LOG_LEVEL:-info}"
echo "Writing to: $OUTPUT_DIR"
echo "Log level: $LOG_LEVEL"
# This would abort the script:
# echo "$UNDEFINED_VAR" # bash: UNDEFINED_VAR: unbound variableทำความเข้าใจ -o pipefail
หากไม่มี pipefail สถานะจบการทำงานของไปป์ไลน์จะถูกกำหนดโดยคำสั่งสุดท้ายเพียงคำสั่งเดียว ความล้มเหลวของคำสั่งก่อนหน้าจะถูกกลืนหายไปอย่างเงียบ ๆ
ตัวอย่างเมื่อไม่มี pipefail:
cat /missing/file | wc -lcatล้มเหลวด้วยรหัสจบการทำงาน 1 แต่wc -lทำงานสำเร็จด้วยรหัส 0- ไปป์ไลน์ส่งคืนค่า 0 ซึ่งหมายถึงสำเร็จ ทั้งที่ข้อมูลสูญหาย
เมื่อเปิดใช้ pipefail Bash จะส่งคืนรหัสจบการทำงานของคำสั่งที่อยู่ขวาสุดและล้มเหลว ทำให้มองเห็นและตรวจจับความล้มเหลวของไปป์ไลน์ได้
หมายเหตุ: pipefail ไม่ใช่แฟล็กตัวอักษร แต่ต้องตั้งค่าด้วย -o pipefail
#!/usr/bin/env bash
set -euo pipefail
# With pipefail: this aborts if grep finds nothing (exit 1)
# grep returns 1 when no match found
ps aux | grep "[n]ginx" | awk '{print $2}'
echo "If we reach here, nginx is running"ความล้มเหลวของคำสั่งโดยเจตนา — การใช้ || true
บางครั้งคำสั่งสามารถล้มเหลวได้ เมื่อใช้ set -e คุณต้องระบุอย่างชัดเจนว่าความล้มเหลวใดที่ยอมรับได้ มิฉะนั้นสคริปต์จะหยุดทำงาน
วิธีมาตรฐานคือใช้ || true ซึ่งต่อท้ายทางเลือกสำรองที่สำเร็จเสมอ:
command || true— ไม่สนใจความล้มเหลวทั้งหมดcommand || echo "Warning: step failed, continuing"— บันทึกคำเตือนแล้วทำงานต่อcommand || { echo "fatal"; exit 1; }— จัดการความล้มเหลวแบบกำหนดเอง
รูปแบบนี้ทำให้เจตนาของคุณในโค้ดชัดเจน: คำสั่งทั่วไปหมายถึง “คำสั่งนี้ต้องสำเร็จ” ส่วน || true หมายถึง “คำสั่งนี้อาจล้มเหลวได้และไม่เป็นไร”
#!/usr/bin/env bash
set -euo pipefail
# Remove temp dir if it exists — OK if it doesn't
rm -rf /tmp/my_workspace || true
mkdir -p /tmp/my_workspace
# Check if a service is running — OK if not
if systemctl is-active --quiet nginx 2>/dev/null || true; then
echo "nginx is active"
fi
# Grep that may find nothing — OK
grep 'ERROR' /var/log/app.log || true
echo "Done"สิ่งที่ set -e ตรวจจับไม่ได้
set -e มีข้อยกเว้นและจุดที่อาจทำให้เกิดปัญหาซึ่งเป็นที่รู้จักกันดี การทำความเข้าใจเรื่องเหล่านี้จะช่วยป้องกันความมั่นใจที่เกินจริง:
- คำสั่งในเงื่อนไข
if/while/until— นิพจน์ทดสอบได้รับการยกเว้นตามการออกแบบ - คำสั่งที่ถูกปฏิเสธด้วย
!—! falseจะไม่ทำให้จบการทำงาน - คำสั่งสุดท้ายก่อน
||— เช่นfalse || handle_error - สถานะจบการทำงานของเชลล์ย่อยในบางบริบท — เช่น
VAR=$(failing_command)ใน Bash บางเวอร์ชัน - ค่าที่ฟังก์ชันส่งคืน — จะพิจารณาเฉพาะคำสั่งสุดท้ายในฟังก์ชัน
โหมดเข้มงวดไม่ใช่สิ่งทดแทนการตรวจสอบข้อผิดพลาดอย่างชัดเจน แต่เป็นตาข่ายนิรภัยที่ช่วยจับความล้มเหลวโดยไม่ตั้งใจส่วนใหญ่
#!/usr/bin/env bash
set -euo pipefail
# These do NOT trigger -e:
if false; then echo "never"; fi # -e exempt in conditions
! false # negation exempts
false || echo "handled" # || exempts the left side
# This DOES trigger -e (no condition, no ||):
# false
echo "Script continues after exempted failures"เชลล์ย่อยและฟังก์ชันในโหมดเข้มงวด
การตั้งค่าโหมดเข้มงวดจะสืบทอดไปยังเชลล์ย่อย แต่มีพฤติกรรมละเอียดอ่อนเมื่อใช้กับฟังก์ชันและการแทนที่คำสั่ง
กฎสำคัญ:
- ฟังก์ชันจะสืบทอด
-e,-uและpipefailจากเชลล์ที่เรียกใช้ - การส่งคืนค่าที่ไม่ใช่ศูนย์จากฟังก์ชันจะทำให้ผู้เรียกจบการทำงาน (เมื่อกำหนด
-e) — ยกเว้นเมื่อการเรียกอยู่ในเงื่อนไขหรืออยู่หลัง|| - การแทนที่คำสั่ง
$(): ใน Bash รุ่นเก่า คำสั่งที่ล้มเหลวภายใน$()อาจไม่ทำให้-eในโปรเซสหลักทำงาน เพื่อความปลอดภัยให้กำหนดค่าแล้วใช้งานแยกกัน - เชลล์ย่อยแบบระบุชัดเจน
()จะสืบทอดแฟล็กทั้งหมด
#!/usr/bin/env bash
set -euo pipefail
setup_workspace() {
local dir="$1"
mkdir -p "$dir" # fails here if permissions denied
cd "$dir"
echo "Ready in $(pwd)"
}
# Safe pattern: assign result then use it
TODAY=$(date +%Y-%m-%d) # capture separately
WORKDIR="/tmp/run_${TODAY}"
setup_workspace "$WORKDIR"
echo "Workspace: $WORKDIR"การใช้โหมดเข้มงวดร่วมกับการดักจับข้อผิดพลาด
โหมดเข้มงวดจะบอก Bash ว่าให้หยุดเมื่อใด ส่วน trap บน ERR ช่วยให้คุณเรียกใช้การล้างข้อมูลหรือการวินิจฉัยก่อนที่สคริปต์จะจบการทำงาน
รูปแบบทั่วไปคือ:
- ตั้งค่าโหมดเข้มงวดไว้ด้านบนสุด
- กำหนดฟังก์ชัน
cleanupหรือon_error - ลงทะเบียนฟังก์ชันด้วย
trap 'on_error' ERR - เลือกดักจับ
EXITเพิ่มด้วยเพื่อให้ล้างข้อมูลได้แน่นอนไม่ว่าจะสำเร็จหรือล้มเหลว
สำคัญ: ใช้ set -E (E ตัวพิมพ์ใหญ่ หรือเรียกอีกอย่างว่า errtrace) เพื่อให้การดักจับ ERR สืบทอดไปยังฟังก์ชันและเชลล์ย่อยด้วย หากไม่ใช้ การดักจับจะทำงานเฉพาะในเนื้อหาของเชลล์หลักเท่านั้น
#!/usr/bin/env bash
set -Eeuo pipefail
on_error() {
local exit_code=$?
local line_number=${BASH_LINENO[0]}
echo "ERROR: command failed with code ${exit_code} at line ${line_number}" >&2
}
cleanup() {
echo "Cleaning up temporary files..." >&2
rm -rf /tmp/my_run_dir 2>/dev/null || true
}
trap on_error ERR
trap cleanup EXIT
mkdir -p /tmp/my_run_dir
echo "hello" > /tmp/my_run_dir/output.txt
cat /tmp/my_run_dir/output.txt
echo "Done"การปิดใช้โหมดเข้มงวดเฉพาะจุด
บางครั้งบล็อกโค้ดอาจมีลักษณะ “ไม่เป็นระเบียบ” โดยเจตนา เช่น การตรวจสอบว่ามีเครื่องมือเสริมหรือไม่ หรือการเรียกใช้คำสั่งรุ่นเก่าที่ส่งคืนค่าที่ไม่ใช่ศูนย์ด้วยเหตุผลที่ไม่ใช่ข้อผิดพลาด คุณสามารถปิดใช้โหมดเข้มงวดชั่วคราวแล้วเปิดกลับคืนภายหลังได้
รูปแบบที่ปลอดภัย:
- บันทึกสถานะด้วย
set +e(ปิดใช้-e) เรียกใช้บล็อกดังกล่าว แล้วเปิดกลับด้วยset -e - หรือใช้เชลล์ย่อย
( set +e; ... )เพื่อไม่ให้แฟล็กของเชลล์หลักได้รับผลกระทบ - เปิดใช้แฟล็กกลับทันทีที่บล็อกเสี่ยงสิ้นสุด การปล่อยให้ปิดแฟล็กไว้นานเป็นสาเหตุทั่วไปของข้อบกพร่อง
ควรใช้รูปแบบเชลล์ย่อยเมื่อบล็อกมีหลายคำสั่ง เพราะแฟล็กจะถูกคืนค่าโดยอัตโนมัติเมื่อเชลล์ย่อยจบการทำงาน
#!/usr/bin/env bash
set -euo pipefail
# Probe for optional tools without aborting
HAS_JQ=false
(
set +e
command -v jq > /dev/null 2>&1
[[ $? -eq 0 ]] && echo "jq_found"
) && HAS_JQ=true || true
if [[ "$HAS_JQ" == "true" ]]; then
echo "jq is available — using JSON output"
else
echo "jq not found — using plain text"
fiแม่แบบสคริปต์โหมดเข้มงวดฉบับสมบูรณ์
นี่คือแม่แบบที่พร้อมใช้งานจริง ซึ่งรวบรวมแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับโหมดเข้มงวดทั้งหมดในบทเรียนนี้:
set -Eeuo pipefail— แฟล็กทั้งสี่รายการ รวมถึงerrtraceIFS=$'\n\t'— การแยกคำที่ปลอดภัยยิ่งขึ้น (หลีกเลี่ยงการแยกตามช่องว่าง)- การดักจับ ERR + EXIT สำหรับการวินิจฉัยและการล้างข้อมูล
- ค่าปริยายที่ระบุอย่างชัดเจนสำหรับพารามิเตอร์เสริม
readonlyและlocalเพื่อจำกัดขอบเขตของตัวแปร
คัดลอกแม่แบบนี้ไว้ที่ต้นสคริปต์ Bash ที่ไม่ใช่สคริปต์ง่าย ๆ ทุกไฟล์ เพื่อรับประโยชน์จากพฤติกรรมหยุดทันทีเมื่อเกิดความล้มเหลวและข้อผิดพลาดที่ตรวจสอบย้อนกลับได้
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
# ── Constants ────────────────────────────────────────────
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# ── Trap handlers ────────────────────────────────────────
err_handler() {
echo "[${SCRIPT_NAME}] ERROR on line ${BASH_LINENO[0]}: exit ${?}" >&2
}
cleanup() {
echo "[${SCRIPT_NAME}] Exiting" >&2
}
trap err_handler ERR
trap cleanup EXIT
# ── Defaults ─────────────────────────────────────────────
ENV="${1:-production}"
MAX_RETRIES="${MAX_RETRIES:-3}"
# ── Main ─────────────────────────────────────────────────
main() {
echo "Running in env=${ENV}, max_retries=${MAX_RETRIES}"
echo "Script dir: ${SCRIPT_DIR}"
}
main "$@"ตรวจสอบความเข้าใจ: พฤติกรรมของ pipefail
ทดสอบความเข้าใจของคุณเกี่ยวกับผลของ pipefail ที่มีต่อรหัสการออกจากสายคำสั่ง
ทบทวน: โหมดเข้มงวดด้วย set -euo pipefail
ในบทเรียนนี้ คุณได้เรียนรู้วิธีทำให้สคริปต์ Bash หยุดทำงานอย่างรวดเร็วและแจ้งข้อผิดพลาดอย่างชัดเจนโดยใช้โหมดเข้มงวด
แฟล็กทั้งสามรายการและสิ่งที่แฟล็กเหล่านี้ช่วยตรวจสอบ:
-e(errexit) — ออกจากการทำงานเมื่อคำสั่งใด ๆ มีสถานะไม่เป็นศูนย์ ยกเว้นเมื่ออยู่ในเงื่อนไขและหลัง||-u(nounset) — ยกเลิกการทำงานเมื่อมีการอ้างถึงตัวแปรที่ยังไม่ได้กำหนดค่า ใช้${VAR:-default}สำหรับตัวแปรที่ไม่จำเป็นต้องมีค่า-o pipefail— ทำให้สายคำสั่งทั้งหมดล้มเหลวหากขั้นตอนใด ๆ ล้มเหลว ไม่ใช่เฉพาะขั้นตอนสุดท้าย
แนวปฏิบัติเสริม:
- เพิ่ม
-E(errtrace) เพื่อให้กับดัก ERR ส่งต่อเข้าไปยังฟังก์ชัน - ใช้
trapกับERRและEXITเพื่อวินิจฉัยปัญหาและล้างทรัพยากร - ใช้
|| trueเพื่อยอมรับความล้มเหลวโดยตั้งใจ - ปิดใช้งานชั่วคราวด้วย
set +eภายในเชลล์ย่อยสำหรับโค้ดเก่าหรือโค้ดตรวจสอบ
โหมดเข้มงวดไม่ใช่คำตอบที่แก้ได้ทุกปัญหา — คุณต้องทราบข้อยกเว้นของมัน — แต่เป็นนิสัยเพียงอย่างเดียวที่มีประสิทธิภาพสูงสุดในการเขียนสคริปต์ Bash ที่เชื่อถือได้และป้องกันข้อผิดพลาด
คำถามที่พบบ่อย
บทเรียน “โหมดเข้มงวดด้วย set -euo pipefail” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “โหมดเข้มงวดด้วย set -euo pipefail” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Linux Command Line & Bash Scripting Mastery ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Linux Command Line & Bash Scripting Mastery มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “โหมดเข้มงวดด้วย set -euo pipefail”
เปิดใช้การหยุดทำงานทันทีเมื่อเกิดข้อผิดพลาด และทำความเข้าใจว่าแฟล็กของโหมดเข้มงวดแต่ละตัวตรวจจับหรือไม่ตรวจจับข้อผิดพลาดใด คุณปฏิบัติ Linux Command Line & Bash Scripting Mastery ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Linux Command Line & Bash Scripting Mastery หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Linux Command Line & Bash Scripting Mastery บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “โหมดเข้มงวดด้วย set -euo pipefail” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Linux Command Line & Bash Scripting Mastery นี้ได้ไหม
ได้ บทเรียน Linux Command Line & Bash Scripting Mastery ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- โหมดเข้มงวดด้วย set -euo pipefail
- ตัวจัดการ trap สำหรับการล้างข้อมูลและสัญญาณ
- ไฟล์ชั่วคราวและไดเรกทอรีล็อกที่ปลอดภัย
- สคริปต์แบบทำซ้ำได้อย่างปลอดภัยและตรรกะลองใหม่แบบถอยกลับ