Dễ dàng phát hiện khi script shell bắt đầu, nhưng không dễ biết khi nào nó dừng. Script có thể kết thúc bình thường, như tác giả dự định, nhưng cũng có thể thất bại do lỗi tử huyệt bất ngờ. Đôi khi có lợi giữ lại بقايا tiến trình khi script thất bại, đôi khi bất tiện. Cách nào, phát hiện kết thúc script và phản ứng theo cách tính toán trước là lý do chỉ thị trap của Bash tồn tại.
Phản ứng với thất bại
Ví dụ cách một thất bại trong script dẫn đến thất bại sau. Giả sử bạn viết chương trình tạo thư mục tạm trong /tmp để giải nén và xử lý file trước khi gói lại định dạng khác:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
## tạo thư mục tạm
mkdir "${TMP}"
## giải nén file vào tạm
tar xf "${1}" --directory "${TMP}"
## chuyển đến tạm và chạy lệnh
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## quay lại gốc
popd
## nén với bzip2
bzip2 --compress "${TMP}"/"${1%.*}".tar \
--stdout > "${1%.*}".tbz
## dọn dẹp
/usr/bin/rm -r /tmp/tmpdir
Hầu hết thời gian script chạy như mong đợi. Tuy nhiên, nếu vô tình chạy trên archive chứa file PNG thay vì JPEG kỳ vọng, nó thất bại giữa chừng. Một thất bại dẫn đến thất bại khác, và cuối cùng script thoát mà không đạt chỉ thị cuối xóa thư mục tạm. Nếu bạn thủ công xóa thư mục, khôi phục nhanh, nhưng nếu bạn không có mặt, lần chạy sau script phải đối phó với thư mục tạm hiện có đầy file rác bất đoán trước.
Cách chống chọi là đảo ngược và nhân đôi logic bằng cách thêm xóa phòng ngừa ở đầu script. Mặc dù hợp lệ, đó dựa trên बल thay vì cấu trúc. Giải pháp thanh lịch hơn là trap.
Bắt tín hiệu với trap
Từ khóa trap bắt tín hiệu (signals) có thể xảy ra khi thực thi. Bạn đã dùng tín hiệu nếu từng dùng kill hoặc killall, gọi SIGTERM mặc định. Có nhiều tín hiệu khác shell phản hồi, xem hầu hết với trap -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
Bất kỳ tín hiệu nào có thể dự đoán với trap. Ngoài ra, trap nhận diện:
EXIT: Xảy ra khi tiến trình shell thoátERR: Xảy ra khi lệnh (như tar hoặc mkdir) hoặc built-in (như pushd hoặc cd) hoàn thành với trạng thái non-zeroDEBUG: Boolean biểu thị chế độ debug
Để đặt trap trong Bash, dùng trap theo sau danh sách lệnh muốn thực thi, rồi danh sách tín hiệu kích hoạt.
Ví dụ, trap này phát hiện SIGINT, tín hiệu gửi khi user nhấn Ctrl+C khi tiến trình chạy:
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
Script ví dụ với vấn đề thư mục tạm có thể sửa bằng trap phát hiện SIGINT, lỗi, và thoát thành công:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
trap \
"{ /usr/bin/rm -r "${TMP}" ; exit 255; }" \
SIGINT SIGTERM ERR EXIT
## tạo thư mục tạm
mkdir "${TMP}"
tar xf "${1}" --directory "${TMP}"
## chuyển đến tạm và chạy lệnh
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## quay lại gốc
popd
## nén tar
bzip2 --compress $TMP/"${1%.*}".tar \
--stdout > "${1%.*}".tbz
Đối với hành động phức tạp, bạn có thể đơn giản hóa câu lệnh trap với hàm Bash.
Trap trong Bash
Trap hữu ích đảm bảo script kết thúc sạch sẽ, dù thành công hay thất bại. Không bao giờ an toàn phụ thuộc hoàn toàn vào garbage collection tự động, nên đây là thói quen tốt nói chung. Hãy thử dùng trong script, xem chúng làm được gì!