Di bawah ini adalah contoh skrip yang mengimplementasikan try/catch/finallybash.
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 exceptionatau throwke akhir tryblok terdekat . Di sini pesan dihapus dari fifo dan ditempatkan dalam variabel array. Status dikembalikan melalui returndan exitperintah dan ditempatkan di variabel yang berbeda. Untuk memasukkan catchblok, status ini tidak boleh nol. Persyaratan lain untuk memasukkan catchblok dilewatkan sebagai parameter. Jika akhir acatch blok tercapai, maka status diatur ke nol. Jika akhir finallyblok tercapai dan statusnya masih nol, maka lemparan implisit yang berisi pesan dan status dieksekusi. Script membutuhkan pemanggilan fungsi trycatchfinallyyang berisi penangan pengecualian yang tidak tertangani.
Sintaks untuk trycatchfinallyperintah diberikan di bawah ini.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
The -cpilihan menambahkan tumpukan panggilan ke pesan pengecualian.
The -dpilihan memungkinkan debug output.
The -epilihan memungkinkan pengecualian perintah.
The -hpilihan memungkinkan pengguna untuk mengganti sendiri handler perintah pengecualian mereka.
The -kpilihan menambahkan panggilan stack ke output debug.
The -opilihan menggantikan file output standar yang /dev/fd/2.
The -upilihan memungkinkan pengguna untuk mengganti tertangani handler pengecualian mereka sendiri.
The -vpilihan memungkinkan pengguna pilihan untuk lulus kembali nilai-nilai meskipun penggunaan Command Pergantian.
The fifoadalah FIFO nama file.
Fungsi functionini disebut trycatchfinallysebagai subproses.
Catatan: cdkoOpsi dihapus untuk menyederhanakan skrip.
Sintaks untuk catchperintah 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.
-eberarti [[ $value == "$string" ]](nilai harus cocok dengan setidaknya satu string dalam daftar)
-nberarti [[ $value != "$string" ]](nilai tidak dapat cocok dengan string apa pun dalam daftar)
-oberarti [[ $value != $pattern ]](nilai tidak dapat cocok dengan salah satu pola dalam daftar)
-pberarti [[ $value == $pattern ]](nilai memiliki untuk mencocokkan setidaknya satu pola dalam daftar)
-rberarti [[ $value =~ $regex ]](nilai harus cocok dengan setidaknya satu ekspresi reguler yang diperluas dalam daftar)
-tberarti[[ ! $value =~ $regex ]] (nilai tidak dapat cocok dengan salah satu dari ekspresi reguler yang diperluas dalam daftar)
The try/catch/finallyScript 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 makefifofile berisi script yang dijelaskan dalam jawaban ini . Asumsi dibuat bahwa file bernama 4444kkkkktidak ada, oleh karena itu menyebabkan pengecualian terjadi. Output pesan kesalahan dari ls 4444kkkkkperintah secara otomatis ditekan hingga di dalam catchblok 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 throwdapat dibuat dengan mengganti fungsi MyFunction3dengan 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 throwperintah 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