Error handling function in Bash by Command Substitution
Tags: bash, shell
Published: 2021-12-10 21:27:39

_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 `return`ing or `exit`ing 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:

```js
function JavaScriptReturn() {
  return 'Oh, is this about Bash?'
}
```

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

```bash
if [ "$1" = "" ] ; then return 1 ; fi
```

you can say

```bash
if [ "$1" = "" ] ; then $(fErr) ; fi
```

where "fErr" is a function like this:

```bash
fErr(){
  1>&2 printf "No arg!" 
  printf "return 1"
}
```

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:

```bash
if [ "$1" = "" ] ; then printf "No arg!" ; return 1 ; fi
```

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

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

```bash
if [ "$1" = "" ] ; then return 1 ; fi
```

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](https://en.wikipedia.org/wiki/Short-circuit_evaluation).

Sometimes using

```bash
[ "$1" = "" ] && printf "No arg!" && return 1
```

instead of

```bash
if [ "$1" = "" ] ; then printf "No arg!" ; return 1 ; fi
```

is indeed just [Code golf](https://en.wikipedia.org/wiki/Code_golf), but short-circuits are very handy for [Guard clauses](https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html).

Imagine something like this

```bash
[ "$1" = "" ] && $(fErr1)
[ ! -d "$1" ] && ! transmogrify "$1" && $(fErr2)
[ "$2" = "$3" ] &&  unhandle "$2" || deep_unhandle "$2" && $(fErr3) || $(ferr4)
```

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

A slight problem with short-circuits is that

```bash
A || B
```

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

```bash
A || B && C
```

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

```bash
findHolyGrail || printf "Failed to find holy grail" && return 1
tweet "Come party!"
```

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

```bash
findHolyGrail || $(fErr)
tweet "Come party!"
```

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.

```bash
fErr(){
  healWounds
  cancelHotelBookings
  fireLameFellows
  searchNewChallenge
  thinkOverCurrentJob
  1>&2 printf "$messageOfSorrow"
  printf "return 1"
}
```

## Using Command substitution correctly

Note that only exactly

```bash
$(fErr)
```

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.

```bash
"return 1"
```

instead of

```bash
return 1
```

## Backquotes vs. Dollar

While we use

```bash
$(fErr)
```

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

```bash
`fErr`
```

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](https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html), not at least for [Shell functions](https://www.gnu.org/software/bash/manual/html_node/Shell-Functions.html) 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

```bash
! findHolyGrail && $(fErrMsg ; fErrReturn)
```

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

```bash
! findHolyGrail && $(fErrMsg ; return 1)
```

since this would *not* be excecuted like

```bash
! findHolyGrail && fErrMsg && return 1
```

but like

```bash
! findHolyGrail && fErrMsg
```

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

This is because in

```bash
$(fErrMsg ; return 1)
```

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

```bash
! findHolyGrail && $(fErrMsg ; printf "return 1")
```

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


[Bash manual]:(https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html)