Thật dễ dàng để phát hiện khi một tập lệnh shell bắt đầu, nhưng không phải lúc nào cũng dễ dàng biết khi nào nó dừng lại. Một tập lệnh có thể kết thúc bình thường, giống như tác giả của nó dự định kết thúc, nhưng nó cũng có thể không thành công do một lỗi nghiêm trọng không mong muốn. Đôi khi, việc bảo tồn phần còn lại của bất cứ thứ gì đang diễn ra khi một tập lệnh bị lỗi sẽ có lợi và những lần khác thì điều đó thật bất tiện. Dù bằng cách nào, việc phát hiện phần cuối của một tập lệnh và phản ứng với nó theo một số cách được tính toán trước là lý do tại sao Bash trap
chỉ thị tồn tại.
Phản hồi khi thất bại
Dưới đây là một ví dụ về cách một lỗi trong tập lệnh có thể dẫn đến các lỗi trong tương lai. Giả sử bạn đã viết một chương trình tạo một thư mục tạm thời trong /tmp
để nó có thể hủy lưu trữ và xử lý các tệp trước khi nhóm chúng lại với nhau ở một định dạng khác:
#!/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
Hầu hết thời gian, kịch bản hoạt động như mong đợi. Tuy nhiên, nếu bạn vô tình chạy nó trên một kho lưu trữ chứa đầy các tệp PNG thay vì các tệp JPEG như mong đợi, nó sẽ không thành công giữa chừng. Một thất bại dẫn đến một thất bại khác, và cuối cùng, tập lệnh thoát ra mà không đạt được chỉ thị cuối cùng để xóa thư mục tạm thời. Miễn là bạn xóa thư mục theo cách thủ công, bạn có thể khôi phục nhanh chóng, nhưng nếu bạn không có mặt để làm điều đó, thì lần sau khi tập lệnh chạy, nó phải xử lý một thư mục tạm thời hiện có chứa đầy các tệp còn sót lại không thể đoán trước.
Một cách để chống lại điều này là đảo ngược và lặp lại logic bằng cách thêm một loại bỏ đề phòng vào đầu tập lệnh. Mặc dù hợp lệ, điều đó dựa vào vũ phu thay vì cấu trúc. Một giải pháp thanh lịch hơn là trap
.
Bắt tín hiệu bằng bẫy
trap
từ khóa bắt tín hiệu điều đó có thể xảy ra trong quá trình thực hiện. Bạn đã sử dụng một trong những tín hiệu này nếu bạn đã từng sử dụng kill
hoặc killall
lệnh gọi SIGTERM
theo mặc định. Có nhiều tín hiệu khác mà trình bao phản hồi và bạn có thể thấy hầu hết chúng bằng trap -l
(như trong "danh sách"):
$ 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 trong số này có thể được dự đoán bằng trap
. Ngoài những thứ này, trap
nhận ra:
-
EXIT
:Xảy ra khi quá trình shell tự thoát ra ngoài -
ERR
:Xảy ra khi một lệnh (chẳng hạn như tar hoặc mkdir ) hoặc lệnh tích hợp (chẳng hạn như pushd hoặc cd ) hoàn thành với trạng thái khác 0 -
DEBUG
:Một Boolean đại diện cho chế độ gỡ lỗi
Để đặt bẫy trong Bash, hãy sử dụng trap
tiếp theo là danh sách các lệnh bạn muốn được thực hiện, tiếp theo là danh sách các tín hiệu để kích hoạt lệnh đó.
Ví dụ:bẫy này phát hiện một SIGINT
, tín hiệu được gửi khi người dùng nhấn Ctrl + C trong khi một quá trình đang chạy:
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
Tập lệnh ví dụ có sự cố thư mục tạm thời có thể được khắc phục bằng cách phát hiện bẫy 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
## 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
Đối với các hành động phức tạp, bạn có thể đơn giản hóa trap
câu lệnh với các hàm Bash.
Bẫy trong Bash
Bẫy rất hữu ích để đảm bảo rằng các tập lệnh của bạn kết thúc sạch sẽ, cho dù chúng có chạy thành công hay không. Không bao giờ an toàn khi phụ thuộc hoàn toàn vào việc thu gom rác tự động, vì vậy, đây là một thói quen tốt cần thực hiện nói chung. Hãy thử sử dụng chúng trong các tập lệnh của bạn và xem chúng có thể làm gì!