- Redirect the stdout of the function to the FD of the write end of an "automatic" pipe. Then, after the (non-forking) call, ...
- Read the FD of the read end of the same pipe.
#!/usr/bin/env bash
# This code prints 'var=2, out=hello' meaning var was set and the stdout got captured
# See: https://mcmap.net/q/734027/-how-to-get-the-output-of-a-shell-function-without-forking-a-sub-shell
main(){
local -i var=1 # Set value
local -i pipe_write=0 pipe_read=0 # Just defensive programming
create_pipe # Get 2 pipe automatic fd, see function below
# HERE IS THE CALL
callee >&"$pipe_write" # Run function, see below
exec {pipe_write}>&- # Close fd of the pipe writer end (to make cat returns)
local out=$(cat <&"$pipe_read") # Grab stdout of callee
exec {pipe_read}>&- # Just defensive programming
echo "var=$var, out=$out" # Show result
}
callee(){
var=2 # Set an outer scope value
echo hello # Print some output
}
create_pipe(){
: 'From: https://superuser.com/questions/184307/bash-create-anonymous-fifo
Return: pipe_write and pipe_read fd => to outer scope
'
exec 2> /dev/null # Avoid job control print like [1] 1030612
tail -f /dev/null | tail -f /dev/null &
exec 2>&1
# Save the process ids
local -i pid2=$!
local -i pid1=$(jobs -p %+)
# Hijack the pipe's file descriptors using procfs
exec {pipe_write}>/proc/"$pid1"/fd/1
# -- Read
exec {pipe_read}</proc/"$pid2"/fd/0
disown "$pid2"; kill "$pid1" "$pid2"
}
main
Note that it would be much shorter code using an automatic normal fd as follows:
exec {fd}<> <(:)
instead of using the create_pipe
function as this code does (copying this answer). But then the reading FD line used here like:
local out=$(cat <&"$fd")
would block. And it would be necessary to try reading with a timeout like the following:
local out=''
while read -r -t 0.001 -u "${fd}" line; do
out+="$line"$'\n'
done
But I try to avoid arbitrary sleeps
or timeouts
if possible.\
Here the closing of the FD of write end of the pipe makes the read cat
line returns at the end of content (magically from my poor knowledge).