I can easily capture stdout from a function call (in subshell) to a variable with:
val="$(get_value)"
I can also modify variables (for instance, an array) in a shell by reference so to speak in the same shell with something like:
function array.delete_by_index {
local array_name="$1"
local key="$2"
unset "$array_name[$key]"
eval "$array_name=(\"\${$array_name[@]}\")"
}
array.delete_by_index "array1" 0
But what I'm struggling to figure out how to do is to do both at the same time, in a clean manner. An example of where I want this is popping a value from an array:
function array.pop {
local array_name="$1"
local last_index=$(( $(eval "echo \${#$array_name[@]}") - 1 ))
local tmp="$array_name[\"$last_index\"]"
echo "${!tmp}"
# Changes "$array_name" here, but not in caller since this is a sub shell
array.delete_by_index "$array_name" $last_index
}
val="$(array.pop "array1")"
It seems to me like all forms of capturing stdout to a variable require a subshell in bash, and using a sub shell will not allow me the ability to change a value by reference in the caller's context.
I'm wondering if anyone know a magical bashism to accomplish this? I do not particularly want a solution that uses any kind of file/fifo on the filesystem.
The 2nd answer in this question seems to suggest that this is possible in ksh
using val="${ cmd; }"
, as this construct apparently allows for capturing output, but without using sub shells. So yes, I could technically switch to ksh, but I'd like to know if this is possible in bash.
Best Answer
This works in both
bash
(since release 4.3) andksh93
. To "bashify" it, replace alltypeset
withlocal
in the functions, and thetypeset
in the global scope withdeclare
(while keeping all the options!). I honestly don't know why Bash has so many different names for things that are just variations oftypeset
.Using a nameref in the function, you avoid
eval
(I've never had to useeval
anywhere in any script!). By providing thestack_pop
function with a place to store the popped value, you avoid the subshell. By avoiding the subshell, thestack_pop
function can modify the value of thestack
variable in the outer scope.The underscores in the local variables in the function is to avoid having a nameref that has the same name as the variable that it references (Bash doesn't like it,
ksh
doesn't mind, see this question).In
ksh
you could write thestack_pop
function likeAnd then call it with
(
${ ... }
is the same as$( ... )
but does not create a subshell)But I'm not a big fan of this. IMHO,
stack_pop
should not have to send the data to stdout, and I should not have to call it with${ ... }
to get the data. I could possibly be more ok with my originalstack_pop
, and then add astack_pop_print
that does the above, if needed.For Bash, you could go with the
stack_pop
in the beginning of my post, and then have astack_top_print
that just prints the top element of the stack to stdout, without removing it (which it can't because it would most likely be running in a$( ... )
subshell).