DEV Community

hhlohmann
hhlohmann

Posted on

Error handling function in Bash by Command Substitution

Or at least some insights into Command Substitution, Side Effects, Short-Circuiting, and Guard clauses

Basically only numerical error "messages"

The error handlling concept of Bash (resp. shell scripting in general) consists in returning or exiting with a number that might help you to look up details in some list. It is not possible to have an informative message as eg. in JavaScript:

function JavaScriptReturn() {
  return 'Oh, is this about Bash?'
}
Enter fullscreen mode Exit fullscreen mode

But it is possible if you use Command Substitution (see the Bash manual on it before heading to Stack Overflow).

Put side effects to numerical errors

Instead of

if [ "$1" = "" ] ; then return 1 ; fi
Enter fullscreen mode Exit fullscreen mode

you can say

if [ "$1" = "" ] ; then $(fErr) ; fi
Enter fullscreen mode Exit fullscreen mode

where "fErr" is a function like this:

fErr(){
  1>&2 printf "No arg!" 
  printf "return 1"
}
Enter fullscreen mode Exit fullscreen mode

You surely remember that functions in Bash are just command groups, so command substitution with a function will result in the defined command group in place of the substitution, so the line above executes as if it was:

if [ "$1" = "" ] ; then printf "No arg!" ; return 1 ; fi
Enter fullscreen mode Exit fullscreen mode

-- but wait: why not 1>&2 printf "No arg!" and printf "return 1"?

Note that the line before is as if, actually executed is:

if [ "$1" = "" ] ; then return 1 ; fi
Enter fullscreen mode Exit fullscreen mode

since the outcome of the command substitution is just return 1.

The printf "No arg!" is not substituted, but executed inside "fErr" before substitution takes place, you could (or must) say as a side effect of the command substitution. It would have been substituted together with return 1 if we would not redirect stdout to stderr by 1>&2 for printf "No arg!".

Remember that stdout and stderr normally are both shown to you on the same visual output screen, but they are two different channels and in scripting you can and often must address them separately.

Redirecting to stderr should be natural for error messages, here it is a big help in distinguishing the immediately control-flow critical action "return / exit with error" from "additional" information.

Powerful error handling in short-circuiting

Calling a function before throwing a "return" statement might not be that big sensation in "if ... else ... fi", but things look different with short-circuiting.

Sometimes using

[ "$1" = "" ] && printf "No arg!" && return 1
Enter fullscreen mode Exit fullscreen mode

instead of

if [ "$1" = "" ] ; then printf "No arg!" ; return 1 ; fi
Enter fullscreen mode Exit fullscreen mode

is indeed just Code golf, but short-circuits are very handy for Guard clauses.

Imagine something like this

[ "$1" = "" ] && $(fErr1)
[ ! -d "$1" ] && ! transmogrify "$1" && $(fErr2)
[ "$2" = "$3" ] &&  unhandle "$2" || deep_unhandle "$2" && $(fErr3) || $(ferr4)
Enter fullscreen mode Exit fullscreen mode

and try to imagine it - whatever it means - with "if" clauses.

A slight problem with short-circuits is that

A || B
Enter fullscreen mode Exit fullscreen mode

means do A, in case of error do B, but

A || B && C
Enter fullscreen mode Exit fullscreen mode

does not mean do A, in case of error do B & C, but do A, in case of error do B, and then - in any case - do C, so that a simple

findHolyGrail || printf "Failed to find holy grail" && return 1
tweet "Come party!"
Enter fullscreen mode Exit fullscreen mode

would exit with error before "tweet", regardless if "findHolyGrail" succeeded or not, but

findHolyGrail || $(fErr)
tweet "Come party!"
Enter fullscreen mode Exit fullscreen mode

would "tweet" on success for "findHolyGrail" and exit else.

Keep in mind that "fErr" might be much more than just a short message, but eg.

fErr(){
  healWounds
  cancelHotelBookings
  fireLameFellows
  searchNewChallenge
  thinkOverCurrentJob
  1>&2 printf "$messageOfSorrow"
  printf "return 1"
}
Enter fullscreen mode Exit fullscreen mode

Using Command substitution correctly

Note that only exactly

$(fErr)
Enter fullscreen mode Exit fullscreen mode

will be substituted with the commands of "fErr".

Do not put double quotes around the substitution expression. With quotes you would get and try to a string, not a command, i.e.

"return 1"
Enter fullscreen mode Exit fullscreen mode

instead of

return 1
Enter fullscreen mode Exit fullscreen mode

Backquotes vs. Dollar

While we use

$(fErr)
Enter fullscreen mode Exit fullscreen mode

here for command substitution, you will often see a variant with backquotes (backticks), ie.

`fErr`
Enter fullscreen mode Exit fullscreen mode

There are contexts in which the backquote variant is more compact and readable, and it looks more modern, but indeed it is "old-style" and runs in many contexts into problems because backquotes are syntactically less clearer than the "$()" construct.

You should prefer "$()" because it resembles not without reason the difference of "()" and "{}" for Grouping commands, not at least for Shell functions where the curlies "{}" execute the commands in place while the parens "()" put the result of executing the commands into place.

Single vs. multiple commands as substitutes

Since command substitution is nothing more than calling one or more commands and substitute the outcomes of that calls at the place of the call, you could also use

! findHolyGrail && $(fErrMsg ; fErrReturn)
Enter fullscreen mode Exit fullscreen mode

with separate functions for an error message and return 1, but you cannot use

! findHolyGrail && $(fErrMsg ; return 1)
Enter fullscreen mode Exit fullscreen mode

since this would not be excecuted like

! findHolyGrail && fErrMsg && return 1
Enter fullscreen mode Exit fullscreen mode

but like

! findHolyGrail && fErrMsg
Enter fullscreen mode Exit fullscreen mode

i.e. without returning, but proceeding despite error.

This is because in

$(fErrMsg ; return 1)
Enter fullscreen mode Exit fullscreen mode

the return 1 part is an instruction to return with "1" from the subshell in which the execution of the command(s) happens whose outcome should be substituted, but itself is no outcome of that execution. You could catch by usual means the return value "1", but that's not the outcome that is used for substitution; you might say that the subshell execution have two results, one a substitutable outcome and one an exit state.

What you had to write is

! findHolyGrail && $(fErrMsg ; printf "return 1")
Enter fullscreen mode Exit fullscreen mode

and this invites to integrate the printf "return 1" part into fErrMsg at least for clearer code.

Top comments (0)