Result Pattern

Use name ref pattern instead

While RESULT pattern could be useful for older BASH. If you have access to modern bash name reference pattern is MUCH preferred as it doesn't have the gotchas of result pattern. While retaining similar performance characteristics (RESULT pattern is faster than name ref though see Result pattern is Faster than Name Ref pattern.).

See: Name Reference Functions for Performance Improvement

Result Pattern

To address the cost of spawning a process to capture output $()

RESULT pattern can be used where we set a variable as means of communicating back results from the function rather than printing to STDOUT and capturing it with $(), so that we avoid spawning a child process with $()

Example problem

Let's say you have a function that is called in a performant way like now_millis

You have optimized the function itself using EPOCHREALTIME to avoid function calls within it when possible, and the function runs

now_millis(){
  if [[ -n "${EPOCHREALTIME}" ]]; then
      # Using EPOCHREALTIME (Bash 5.0+) - faster than 'date' (date uses a couple millis)
      # This uses a bash internal variable, no external process needed
      #
      # EPOCHREALTIME gives seconds.microseconds
      # Convert to milliseconds by removing the decimal and dividing by 1000
      local epoch_micro="${EPOCHREALTIME//.}"
      echo "$((epoch_micro / 1000))"
  else
    # Fallback for older bash versions
    gnu_date +%s%3N || throw "now_millis: Could not get the milliseconds using [gnu_date +%s%3N]"
  fi
}
export -f now_millis

And the function runs FAST enough to not be detectable by time

❯time now_millis
1758923999550

real    0m0.000s
user    0m0.000s
sys     0m0.000s

But the problem is that to use it you have to spawn a subshell (to capture current now_millis). Now its quite detectable by time.

❯time echo "$(now_millis)"
1758924102082

real    0m0.003s
user    0m0.000s
sys     0m0.003s

Result pattern solution:

# Sets _NOW_MILLIS variable upon successful execution.
#
# The use case for this over $(now_millis) is to avoid spawning a child process.
set_NOW_MILLIS() {
  if [[ -n "${EPOCHREALTIME}" ]]; then
    local epoch_micro="${EPOCHREALTIME//.}"
    _NOW_MILLIS="$((epoch_micro / 1000))"
  else
    _NOW_MILLIS="$(gnu_date +%s%3N)" || throw "set_NOW_MILLIS: Could not get the milliseconds using [gnu_date +%s%3N]"
  fi
}
export -f set_NOW_MILLIS

Now we can call the function and get results back WITHOUT spawning a child process.

set_NOW_MILLIS
local start_millis="${_NOW_MILLIS:?}"

Safer Result pattern: unset after read.

The main caveat is not making a mistake of referencing _NOW_MILLIS without calling set_NOW_MILLIS each time. As _NOW_MILLIS will contain the value from the last set_NOW_MILLIS.

We can also unset the variable after the get to make this pattern safer:

set_NOW_MILLIS
local start_millis="${_NOW_MILLIS:?}"
unset _NOW_MILLIS 

After unset any attempts to use ${_NOW_MILLIS:?} with :? check will interrupt the execution.


Children
  1. Result pattern is Faster than Name Ref pattern.

Backlinks