Sử dụng bẫy không phải lúc nào cũng là một lựa chọn. Ví dụ: nếu bạn đang viết một loại chức năng có thể sử dụng lại cần xử lý lỗi và có thể được gọi từ bất kỳ tập lệnh nào (sau khi tìm nguồn tệp với các hàm trợ giúp), chức năng đó không thể thừa nhận bất cứ điều gì về thời gian thoát của tập lệnh bên ngoài, Điều này làm cho việc sử dụng bẫy rất khó khăn. Một nhược điểm khác của việc sử dụng bẫy là khả năng kết hợp kém, vì bạn có nguy cơ ghi đè lên bẫy trước đó có thể được thiết lập trước đó trong chuỗi người gọi.
Có một mẹo nhỏ có thể được sử dụng để xử lý lỗi thích hợp mà không cần bẫy. Như bạn có thể đã biết từ các câu trả lời khác, set -e
không hoạt động bên trong các lệnh nếu bạn sử dụng ||
toán tử sau chúng, ngay cả khi bạn chạy chúng trong một mạng con; ví dụ, điều này sẽ không hoạt động:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer
set -e
outer() {
echo '--> outer'
(inner) || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
Nhưng người ||
vận hành là cần thiết để ngăn trở về từ chức năng bên ngoài trước khi dọn dẹp. Bí quyết là chạy lệnh bên trong trong nền, và sau đó đợi nó ngay lập tức. Nội dung wait
sẽ trả về mã thoát của lệnh bên trong và bây giờ bạn đang sử dụng hàm ||
sau wait
, không phải hàm bên trong, do đó set -e
hoạt động chính xác bên trong lệnh sau:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup
set -e
outer() {
echo '--> outer'
inner &
wait $! || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
Đây là chức năng chung dựa trên ý tưởng này. Nó sẽ hoạt động trong tất cả các shell tương thích POSIX nếu bạn xóa local
từ khóa, tức là thay thế tất cả local x=y
chỉ bằng x=y
:
# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
local cmd="$1"; shift
local exit_code=0
local e_was_set=1; if ! is_shell_attribute_set e; then
set -e
e_was_set=0
fi
"$cmd" "$@" &
wait $! || {
exit_code=$?
}
if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
set +e
fi
if [ -n "$CLEANUP" ]; then
RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
return $?
fi
return $exit_code
}
is_shell_attribute_set() { # attribute, like "x"
case "$-" in
*"$1"*) return 0 ;;
*) return 1 ;;
esac
}
Ví dụ về cách sử dụng:
#!/bin/sh
set -e
# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh
main() {
echo "--> main: $@"
CLEANUP=cleanup run inner "$@"
echo "<-- main"
}
inner() {
echo "--> inner: $@"
sleep 0.5; if [ "$1" = 'fail' ]; then
oh_my_god_look_at_this
fi
echo "<-- inner"
}
cleanup() {
echo "--> cleanup: $@"
echo " RUN_CMD = '$RUN_CMD'"
echo " RUN_EXIT_CODE = $RUN_EXIT_CODE"
sleep 0.3
echo '<-- cleanup'
return $RUN_EXIT_CODE
}
main "$@"
Chạy ví dụ:
$ ./so_3 fail; echo "exit code: $?"
--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
RUN_CMD = 'inner'
RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127
$ ./so_3 pass; echo "exit code: $?"
--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
RUN_CMD = 'inner'
RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0
Điều duy nhất mà bạn cần lưu ý khi sử dụng phương thức này là tất cả các sửa đổi của các biến Shell được thực hiện từ lệnh bạn truyền đến run
sẽ không truyền đến hàm gọi, bởi vì lệnh chạy trong một lớp con.