บทนำ
ง่ายที่จะตรวจจับเมื่อ shell script เริ่มต้น แต่ไม่ใช่ทุกครั้งที่ง่ายในการรู้ว่าเมื่อไหร่มันหยุด สคริปต์อาจจบปกติ ตามที่ผู้เขียนตั้งใจ แต่ก็อาจล้มเหลวเนื่องจากข้อผิดพลาดร้ายแรงที่ไม่คาดคิด บางครั้งควรเก็บเศษซากของสิ่งที่กำลังดำเนินการเมื่อสคริปต์ล้มเหลว และบางครั้งมันไม่สะดวก ไม่ว่าจะเป็นอย่างไร การตรวจจับการสิ้นสุดของสคริปต์และตอบสนองต่อมันด้วยวิธีที่คำนวณไว้ล่วงหน้า คือเหตุผลที่คำสั่ง trap ของ Bash มีอยู่
การตอบสนองต่อความล้มเหลว
นี่คือตัวอย่างว่าความล้มเหลวหนึ่งในสคริปต์นำไปสู่ความล้มเหลวในอนาคต สมมติคุณเขียนโปรแกรมที่สร้าง temporary directory ใน /tmp เพื่อที่จะ unarchive และประมวลผลไฟล์ ก่อนจะรวมกลับเข้าด้วยกันในรูปแบบที่ต่างออกไป:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
## create tmp dir
mkdir "${TMP}"
## extract files to tmp
tar xf "${1}" --directory "${TMP}"
## move to tmpdir and run commands
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## move back to origin
popd
## bundle with bzip2
bzip2 --compress "${TMP}"/"${1%.*}".tar \
--stdout > "${1%.*}".tbz
## clean up
/usr/bin/rm -r /tmp/tmpdir
ส่วนใหญ่สคริปต์ทำงานตามที่คาดไว้ อย่างไรก็ตาม หากคุณรันมันบน archive ที่เต็มไปด้วย PNG files แทน JPEG ที่คาดไว้ มันจะล้มเหลวระหว่างทาง ความล้มเหลวหนึ่งนำไปสู่อีกความล้มเหลว และสุดท้ายสคริปต์ออกโดยไม่ถึงคำสั่งสุดท้ายที่ลบ temporary directory ถ้าคุณลบไดเรกทอรีด้วยมือ คุณสามารถกู้คืนได้เร็ว แต่ถ้าคุณไม่อยู่ที่นั่นทำมัน ครั้งต่อไปที่สคริปต์รัน มันต้องจัดการกับ temporary directory ที่มีอยู่เต็มไปด้วยไฟล์ leftover ที่ไม่คาดการณ์ได้
วิธีหนึ่งในการต่อสู้คือกลับกันและเพิ่ม logic โดยเพิ่มการลบแบบป้องกันที่ต้นสคริปต์ แม้ถูกต้อง แต่พึ่งพา brute force แทนโครงสร้าง วิธีที่ขlegant กว่าคือ trap
การจับสัญญาณด้วย trap
คีย์เวิร์ด trap จับ สัญญาณ (signals) ที่อาจเกิดขึ้นระหว่างการดำเนินการ คุณเคยใช้สัญญาณหนึ่งหากเคยใช้คำสั่ง kill หรือ killall ซึ่งเรียก SIGTERM โดยค่าเริ่มต้น มีสัญญาณอื่นๆ มากมายที่ shells ตอบสนอง และคุณเห็นส่วนใหญ่ด้วย trap -l (l แทน list):
$ trap --list 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
สัญญาณเหล่านี้ใดๆ ก็ตามสามารถคาดการณ์ได้ด้วย trap นอกจากนี้ trap ยังรู้จัก:
EXIT: เกิดขึ้นเมื่อ shell process ออกจากระบบERR: เกิดขึ้นเมื่อคำสั่ง (เช่น tar หรือ mkdir) หรือ built-in command (เช่น pushd หรือ cd) เสร็จสิ้นด้วยสถานะ non-zeroDEBUG: Boolean แทน debug mode
เพื่อตั้ง trap ใน Bash ใช้ trap ตามด้วยรายการคำสั่งที่ต้องการให้รัน ตามด้วยรายการสัญญาณที่จะทริกเกอร์
ตัวอย่าง trap นี้ตรวจจับ SIGINT สัญญาณที่ส่งเมื่อผู้ใช้กด Ctrl+C ขณะโปรเซสกำลังรัน:
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
สคริปต์ตัวอย่างที่มีปัญหา temporary directory สามารถแก้ได้ด้วย trap ตรวจจับ SIGINT, errors, และ successful exits:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
trap \
"{ /usr/bin/rm -r "${TMP}" ; exit 255; }" \
SIGINT SIGTERM ERR EXIT
## create tmp dir
mkdir "${TMP}"
tar xf "${1}" --directory "${TMP}"
## move to tmp and run commands
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## move back to origin
popd
## zip tar
bzip2 --compress $TMP/"${1%.*}".tar \
--stdout > "${1%.*}".tbz
สำหรับการดำเนินการที่ซับซ้อน คุณสามารถทำให้ trap statements เรียบง่ายขึ้นด้วย Bash functions
Traps ใน Bash
Traps มีประโยชน์เพื่อให้แน่ใจว่าสคริปต์ของคุณจบอย่างสะอาด ไม่ว่าจะสำเร็จหรือล้มเหลว ไม่เคยปลอดภัยที่จะพึ่งพา automated garbage collection อย่างสมบูรณ์ ดังนั้นนี่คือนิสัยที่ดีที่ควรฝึกฝน ลองใช้ในสคริปต์ของคุณ และดูว่ามันทำได้อะไร!