Di bawah ini adalah contoh skrip yang mengimplementasikan try/catch/finally
bash.
Seperti jawaban lain untuk pertanyaan ini, pengecualian harus ditangkap setelah keluar dari subproses.
Contoh skrip dimulai dengan membuat anonim fifo, yang digunakan untuk meneruskan pesan string dari command exception
atau throw
ke akhir try
blok terdekat . Di sini pesan dihapus dari fifo dan ditempatkan dalam variabel array. Status dikembalikan melalui return
dan exit
perintah dan ditempatkan di variabel yang berbeda. Untuk memasukkan catch
blok, status ini tidak boleh nol. Persyaratan lain untuk memasukkan catch
blok dilewatkan sebagai parameter. Jika akhir acatch
blok tercapai, maka status diatur ke nol. Jika akhir finally
blok tercapai dan statusnya masih nol, maka lemparan implisit yang berisi pesan dan status dieksekusi. Script membutuhkan pemanggilan fungsi trycatchfinally
yang berisi penangan pengecualian yang tidak tertangani.
Sintaks untuk trycatchfinally
perintah diberikan di bawah ini.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
The -c
pilihan menambahkan tumpukan panggilan ke pesan pengecualian.
The -d
pilihan memungkinkan debug output.
The -e
pilihan memungkinkan pengecualian perintah.
The -h
pilihan memungkinkan pengguna untuk mengganti sendiri handler perintah pengecualian mereka.
The -k
pilihan menambahkan panggilan stack ke output debug.
The -o
pilihan menggantikan file output standar yang /dev/fd/2
.
The -u
pilihan memungkinkan pengguna untuk mengganti tertangani handler pengecualian mereka sendiri.
The -v
pilihan memungkinkan pengguna pilihan untuk lulus kembali nilai-nilai meskipun penggunaan Command Pergantian.
The fifo
adalah FIFO nama file.
Fungsi function
ini disebut trycatchfinally
sebagai subproses.
Catatan: cdko
Opsi dihapus untuk menyederhanakan skrip.
Sintaks untuk catch
perintah diberikan di bawah ini.
catch [[-enoprt] list ...] ...
Opsi ditentukan di bawah ini. Nilai untuk daftar pertama adalah status. Nilai subsquent adalah pesannya. Jika ada lebih banyak pesan daripada daftar, maka pesan yang tersisa diabaikan.
-e
berarti [[ $value == "$string" ]]
(nilai harus cocok dengan setidaknya satu string dalam daftar)
-n
berarti [[ $value != "$string" ]]
(nilai tidak dapat cocok dengan string apa pun dalam daftar)
-o
berarti [[ $value != $pattern ]]
(nilai tidak dapat cocok dengan salah satu pola dalam daftar)
-p
berarti [[ $value == $pattern ]]
(nilai memiliki untuk mencocokkan setidaknya satu pola dalam daftar)
-r
berarti [[ $value =~ $regex ]]
(nilai harus cocok dengan setidaknya satu ekspresi reguler yang diperluas dalam daftar)
-t
berarti[[ ! $value =~ $regex ]]
(nilai tidak dapat cocok dengan salah satu dari ekspresi reguler yang diperluas dalam daftar)
The try/catch/finally
Script diberikan di bawah ini. Untuk menyederhanakan skrip untuk jawaban ini, sebagian besar pengecekan kesalahan dihapus. Ini mengurangi ukuran sebesar 64%. Salinan lengkap skrip ini dapat ditemukan di jawaban saya yang lain .
shopt -s expand_aliases
alias try='{ common.Try'
alias yrt='EchoExitStatus; common.yrT; }'
alias catch='{ while common.Catch'
alias hctac='common.hctaC; done; }'
alias finally='{ common.Finally'
alias yllanif='common.yllaniF; }'
DefaultErrHandler() {
echo "Orginal Status: $common_status"
echo "Exception Type: ERR"
}
exception() {
let "common_status = 10#$1"
shift
common_messages=()
for message in "$@"; do
common_messages+=("$message")
done
}
throw() {
local "message"
if [[ $# -gt 0 ]]; then
let "common_status = 10#$1"
shift
for message in "$@"; do
echo "$message" >"$common_fifo"
done
elif [[ ${#common_messages[@]} -gt 0 ]]; then
for message in "${common_messages[@]}"; do
echo "$message" >"$common_fifo"
done
fi
chmod "0400" "$common_fifo"
exit "$common_status"
}
common.ErrHandler() {
common_status=$?
trap ERR
if [[ -w "$common_fifo" ]]; then
if [[ $common_options != *e* ]]; then
common_status="0"
return
fi
eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
chmod "0400" "$common_fifo"
fi
if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
return
else
exit "$common_status"
fi
}
common.Try() {
common_status="0"
common_subshell="$common_trySubshell"
common_trySubshell="$BASH_SUBSHELL"
common_messages=()
}
common.yrT() {
local "status=$?"
if [[ common_status -ne 0 ]]; then
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
chmod "0600" "$common_fifo"
echo "$eof" >"$common_fifo"
common_messages=()
while read "message"; do
[[ $message != *$eof ]] || break
common_messages+=("$message")
done <"$common_fifo"
fi
common_trySubshell="$common_subshell"
}
common.Catch() {
[[ common_status -ne 0 ]] || return "1"
local "parameter" "pattern" "value"
local "toggle=true" "compare=p" "options=$-"
local -i "i=-1" "status=0"
set -f
for parameter in "$@"; do
if "$toggle"; then
toggle="false"
if [[ $parameter =~ ^-[notepr]$ ]]; then
compare="${parameter#-}"
continue
fi
fi
toggle="true"
while "true"; do
eval local "patterns=($parameter)"
if [[ ${#patterns[@]} -gt 0 ]]; then
for pattern in "${patterns[@]}"; do
[[ i -lt ${#common_messages[@]} ]] || break
if [[ i -lt 0 ]]; then
value="$common_status"
else
value="${common_messages[i]}"
fi
case $compare in
[ne]) [[ ! $value == "$pattern" ]] || break 2;;
[op]) [[ ! $value == $pattern ]] || break 2;;
[tr]) [[ ! $value =~ $pattern ]] || break 2;;
esac
done
fi
if [[ $compare == [not] ]]; then
let "++i,1"
continue 2
else
status="1"
break 2
fi
done
if [[ $compare == [not] ]]; then
status="1"
break
else
let "++i,1"
fi
done
[[ $options == *f* ]] || set +f
return "$status"
}
common.hctaC() {
common_status="0"
}
common.Finally() {
:
}
common.yllaniF() {
[[ common_status -eq 0 ]] || throw
}
caught() {
[[ common_status -eq 0 ]] || return 1
}
EchoExitStatus() {
return "${1:-$?}"
}
EnableThrowOnError() {
[[ $common_options == *e* ]] || common_options+="e"
}
DisableThrowOnError() {
common_options="${common_options/e}"
}
GetStatus() {
echo "$common_status"
}
SetStatus() {
let "common_status = 10#$1"
}
GetMessage() {
echo "${common_messages[$1]}"
}
MessageCount() {
echo "${#common_messages[@]}"
}
CopyMessages() {
if [[ ${#common_messages} -gt 0 ]]; then
eval "$1=(\"\${common_messages[@]}\")"
else
eval "$1=()"
fi
}
common.GetOptions() {
local "opt"
let "OPTIND = 1"
let "OPTERR = 0"
while getopts ":cdeh:ko:u:v:" opt "$@"; do
case $opt in
e) [[ $common_options == *e* ]] || common_options+="e";;
h) common_errHandler="$OPTARG";;
u) common_unhandled="$OPTARG";;
v) common_command="$OPTARG";;
esac
done
shift "$((OPTIND - 1))"
common_fifo="$1"
shift
common_function="$1"
chmod "0600" "$common_fifo"
}
DefaultUnhandled() {
local -i "i"
echo "-------------------------------------------------"
echo "TryCatchFinally: Unhandeled exception occurred"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
}
TryCatchFinally() {
local "common_errHandler=DefaultErrHandler"
local "common_unhandled=DefaultUnhandled"
local "common_options="
local "common_fifo="
local "common_function="
local "common_flags=$-"
local "common_trySubshell=-1"
local "common_subshell"
local "common_status=0"
local "common_command="
local "common_messages=()"
local "common_handler=$(trap -p ERR)"
[[ -n $common_handler ]] || common_handler="trap ERR"
common.GetOptions "$@"
shift "$((OPTIND + 1))"
[[ -z $common_command ]] || common_command+="=$"
common_command+='("$common_function" "$@")'
set -E
set +e
trap "common.ErrHandler" ERR
try
eval "$common_command"
yrt
catch; do
"$common_unhandled" >&2
hctac
[[ $common_flags == *E* ]] || set +E
[[ $common_flags != *e* ]] || set -e
[[ $common_flags != *f* || $- == *f* ]] || set -f
[[ $common_flags == *f* || $- != *f* ]] || set +f
eval "$common_handler"
}
Di bawah ini adalah contoh, yang mengasumsikan skrip di atas disimpan dalam file bernama simple
. The makefifo
file berisi script yang dijelaskan dalam jawaban ini . Asumsi dibuat bahwa file bernama 4444kkkkk
tidak ada, oleh karena itu menyebabkan pengecualian terjadi. Output pesan kesalahan dari ls 4444kkkkk
perintah secara otomatis ditekan hingga di dalam catch
blok yang sesuai .
#!/bin/bash
#
if [[ $0 != ${BASH_SOURCE[0]} ]]; then
bash "${BASH_SOURCE[0]}" "$@"
return
fi
source simple
source makefifo
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
ls 4444kkkkk
echo "leaving MyFunction3" >&4
}
MyFunction2() {
echo "entered MyFunction2" >&4
value="$(MyFunction3)"
echo "leaving MyFunction2" >&4
}
MyFunction1() {
echo "entered MyFunction1" >&4
local "flag=false"
try
(
echo "start of try" >&4
MyFunction2
echo "end of try" >&4
)
yrt
catch "[1-3]" "*" "Exception\ Type:\ ERR"; do
echo 'start of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
local -i "i"
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
break
echo 'end of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
hctac >&4
catch "1 3 5" "*" -n "Exception\ Type:\ ERR"; do
echo 'start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
[[ $(MessageCount) -le 1 ]] || echo "$(GetMessage "1")"
echo "-------------------------------------------------"
break
echo 'end of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
hctac >&4
catch; do
echo 'start of catch' >&4
echo "failure"
flag="true"
echo 'end of catch' >&4
hctac
finally
echo "in finally"
yllanif >&4
"$flag" || echo "success"
echo "leaving MyFunction1" >&4
} 2>&6
ErrHandler() {
echo "EOF"
DefaultErrHandler "$@"
echo "Function: $3"
while read; do
[[ $REPLY != *EOF ]] || break
echo "$REPLY"
done
}
set -u
echo "starting" >&2
MakeFIFO "6"
TryCatchFinally -e -h ErrHandler -o /dev/fd/4 -v result /dev/fd/6 MyFunction1 4>&2
echo "result=$result"
exec >&6-
Script di atas diuji menggunakan GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
. Output, dari menjalankan skrip ini, ditampilkan di bawah ini.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "[1-3]" "*" "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 1
Messages:
Orginal Status: 1
Exception Type: ERR
Function: MyFunction3
ls: 4444kkkkk: No such file or directory
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure
Contoh lain yang menggunakan a throw
dapat dibuat dengan mengganti fungsi MyFunction3
dengan skrip yang ditunjukkan di bawah ini.
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
Sintaks untuk throw
perintah diberikan di bawah ini. Jika tidak ada parameter, maka status dan pesan yang disimpan dalam variabel digunakan sebagai gantinya.
throw [status] [message ...]
Output, dari menjalankan skrip yang dimodifikasi, ditampilkan di bawah ini.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 3
Exception Type: throw
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure