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
Backlinks