Menggunakan perangkap tidak selalu merupakan opsi. Misalnya, jika Anda sedang menulis beberapa jenis fungsi yang dapat digunakan kembali yang memerlukan penanganan kesalahan dan yang dapat dipanggil dari skrip apa pun (setelah sumber file dengan fungsi pembantu), fungsi itu tidak dapat mengasumsikan apa pun tentang waktu keluar skrip luar, yang membuat menggunakan perangkap sangat sulit. Kerugian lain dari menggunakan jebakan adalah kompabilitas yang buruk, karena Anda berisiko menimpa jebakan sebelumnya yang mungkin ditetapkan sebelumnya dalam rantai penelepon.
Ada sedikit trik yang dapat digunakan untuk melakukan penanganan kesalahan yang tepat tanpa jebakan. Seperti yang mungkin sudah Anda ketahui dari jawaban lain, set -e
tidak berfungsi di dalam perintah jika Anda menggunakan ||
operator setelahnya, bahkan jika Anda menjalankannya dalam subkulit; mis. ini tidak akan berfungsi:
#!/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
Tetapi ||
operator diperlukan untuk mencegah kembali dari fungsi luar sebelum pembersihan. Caranya adalah dengan menjalankan perintah dalam di latar belakang, dan kemudian segera tunggu. The wait
builtin akan mengembalikan kode keluar dari perintah batin, dan sekarang Anda menggunakan ||
setelah wait
, bukan fungsi batin, jadi set -e
bekerja dengan baik dalam kedua:
#!/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
Inilah fungsi generik yang dibangun di atas gagasan ini. Ini harus bekerja di semua shell yang kompatibel dengan POSIX jika Anda menghapus local
kata kunci, yaitu ganti semua local x=y
hanya dengan 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
}
Contoh penggunaan:
#!/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 "$@"
Menjalankan contoh:
$ ./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
Satu-satunya hal yang perlu Anda perhatikan ketika menggunakan metode ini adalah bahwa semua modifikasi variabel Shell yang dilakukan dari perintah yang Anda lewati run
tidak akan merambat ke fungsi panggilan, karena perintah berjalan dalam subkulit.