2

I'm calling some function which sets VARIABLE to some value and return another value. I need to preserver the value of VARIABLE and assigning the function's return value to another VAR. Here is what I tryied:

bar() {
        VAR="$(foo)"
        echo $VARIABLE >&2
        echo $VAR >&2
}

foo() {
        VARIABLE="test"
        echo "retval"
}
bar

But it prints

retval

Is there a way to do that?

cxw
  • 16,685
  • 2
  • 45
  • 81
Some Name
  • 8,555
  • 5
  • 27
  • 77

3 Answers3

4

ksh has a convenient non-subshelling command substitution construct for this:

#!/bin/ksh
foo() {
  echo "cat"
  variable="dog"
}
output="${ foo }"
echo "Output is $output and the variable is $variable"

In bash and other shells, you have to go via a temp file instead:

#!/bin/bash

foo() {
  echo "cat"
  variable="dog"
}

# Create a temp file and register it for autodeletion
file="$(mktemp)"
trap 'rm "$file"' EXIT

# Redirect to it and read it back
foo > "$file"
output="$(< "$file")

echo "Output is $output and the variable is $variable"
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • 2
    Not in bash in general, no. There are often ways to write around it though. For example, if you only need the variable for a third command whose side effects you don't care about, you can do something like `output="$(foo; echo "Variable was set to $variable" >&2)"` – that other guy May 19 '18 at 17:10
3

BASH does not return a value to the caller the way traditional functional programming languages do. The only thing that BASH functions can return (essentially) are "exit codes" as a result of the function.

To enable a function to provide a result to another function, you should use global variables. By default, all variables are global in scope, unless otherwise specified.

Here is a set of functions with some echo statements around it that might help you understand this:

#!/bin/bash

export VARIABLE=""

bar() {
        echo ">>> In function bar() ... "
        echo "        _var :: $_var"
        echo "    VARIABLE :: $VARIABLE"

        echo ">>> Setting '_var' to value 'local to bar' ... "
        local _var="local to bar"

        echo ">>> calling function foo() ... "
        foo
        echo ">>> Back in function bar() ... "

        echo "        _var :: $_var"
        echo "    VARIABLE :: $VARIABLE"
}

foo() {
        echo ">>> In function foo() ... "

        local _var="local to foo"
        VARIABLE="I'm a global variable"

        echo "        _var :: $_var"
        echo "    VARIABLE :: $VARIABLE"
}
bar

In the above example, we set the "_var" variable to be local in scope. When "bar()" funs, we set the value of the local "_var" to be "local to bar". We subsequently call the function "foo()"; which sets the local variable "_var" to the value "local to foo".

We also set the global variable "VARIABLE" to the value of "I'm a global variable".

When execution is returned back to the "bar()" function, we print our VARIABLE and _var. VARIABLE results in the string "I'm a global variable".

For details on Return Values from functions in BASH, please see the question Return value in a Bash function.

Here's the results of the above:

>>> In function bar() ...
        _var ::
    VARIABLE ::
>>> Setting '_var' to value 'local to bar' ...
>>> calling function foo() ...
>>> In function foo() ...
        _var :: local to foo
    VARIABLE :: I'm a global variable
>>> Back in function bar() ...
        _var :: local to bar
    VARIABLE :: I'm a global variable

NOTE that we never set the value of VARIABLE in the "bar()" function. It is set in "foo()", and subsequently, we access the results by use of the global variable.

The local variables "_var" also illustrate that the values are only "local" to each function, and by specifying they are local, they have no value or meaning outside of the function itself.

You can use sub-shelling to do what you want. However, subshells operate in a separate process space, so you often will get unintended side effects. To use within a subshell example:

#!/bin/bash

export VARIABLE="default global value"

function bar()
{
    VAR=$(foo)
    echo "VARIABLE :: $VARIABLE"
    echo "     VAR :: $VAR"
}

function foo()
{
    # in theory, VARIABLE is globally scoped, but called in a
    # subshell, it won't impact/change the calling environment
    export VARIABLE='test'
    echo "$VARIABLE"
}

bar

Although capturing the output via the subshell in this manner works - if your function returns or outputs more than one value, you may end up with odd behaviors you didn't intend. The above example illustrates that casual observation of the functions indicates that "foo()" would change the value of "VARIABLE" in the global scope. But as it is run in a subshell ('$(foo)') - it doesn't actually change the global VARIABLE value at all.

This can lead to subtle logic bugs in larger scripts, and I'd suggest not using this method. Here's the output of the above snippet:

VARIABLE :: default global vaule
     VAR :: test
sygibson
  • 359
  • 2
  • 7
3

Well, it may be ugly, but —

In bash, all variables are global unless declared local. Therefore, you can assign VAR directly in foo:

bar() {
        #### VAR="$(foo)"
        foo                  # Just call it
        echo $VARIABLE >&2
        echo $VAR >&2
}

foo() {
        VARIABLE="test"
        #### echo "retval"
        VAR="retval"         # What would have gone in $VAR before
}
bar

Output:

test
retval
cxw
  • 16,685
  • 2
  • 45
  • 81