Is there a TRY CATCH command in Bash
Below is an example of a script which implements try/catch/finally in bash.
Like other answers to this question, exceptions must be caught after exiting a subprocess.
The example scripts start by creating an anonymous fifo, which is used to pass string messages from a command exception or throw to end of the closest try block. Here the messages are removed from the fifo and placed in an array variable. The status is returned through return and exit commands and placed in a different variable. To enter a catch block, this status must not be zero. Other requirements to enter a catch block are passed as parameters. If the end of a catch block is reached, then the status is set to zero. If the end of the finally block is reached and the status is still nonzero, then an implicit throw containing the messages and status is executed. The script requires the calling of the function trycatchfinally which contains an unhandled exception handler.
The syntax for the trycatchfinally command is given below.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
The -c option adds the call stack to the exception messages.
The -d option enables debug output.
The -e option enables command exceptions.
The -h option allows the user to substitute their own command exception handler.
The -k option adds the call stack to the debug output.
The -o option replaces the default output file which is /dev/fd/2.
The -u option allows the user to substitute their own unhandled exception handler.
The -v option allows the user the option to pass back values though the use of Command Substitution.
The fifo is the fifo filename.
The function function is called by trycatchfinally as a subprocess.
Note: The cdko options were removed to simplify the script.
The syntax for the catch command is given below.
catch [[-enoprt] list …] …
The options are defined below. The value for the first list is the status. Subsquent values are the messages. If the there are more messages than lists, then the remaining messages are ignored.
-e means [[ $value == “$string” ]] (the value has to match at least one string in the list)
-n means [[ $value != “$string” ]] (the value can not match any of the strings in the list)
-o means [[ $value != $pattern ]] (the value can not match any of the patterns in the list)
-p means [[ $value == $pattern ]] (the value has to match at least one pattern in the list)
-r means [[ $value =~ $regex ]] (the value has to match at least one extended regular expression in the list)
-t means [[ ! $value =~ $regex ]] (the value can not match any of the extended regular expressions in the list)
The try/catch/finally script is given below. To simplify the script for this answer, most of the error checking was removed. This reduced the size by 64%. A complete copy of this script can be found at my other answer.
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” 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&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” }
Below is an example, which assumes the above script is stored in the file named simple. The makefifo file contains the script described in this answer. The assumption is made that the file named 4444kkkkk does not exist, therefore causing an exception to occur. The error message output from the ls 4444kkkkk command is automatically suppressed until inside the appropriate catch block.
#!/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&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-
The above script was tested using GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). The output, from running this script, is shown below.
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
Another example which uses a throw can be created by replacing function MyFunction3 with the script shown below.
MyFunction3() { echo “entered MyFunction3” >&4 echo “This is from MyFunction3” throw “3” “Orginal Status: 3” “Exception Type: throw” echo “leaving MyFunction3” >&4 }
The syntax for the throw command is given below. If no parameters are present, then status and messages stored in the variables are used instead.
throw [status] [message …]
The output, from executing the modified script, is shown below.
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
Source