140

I have a bash function that produces some output:

function scan {
  echo "output"
}

How can I assign this output to a variable?

ie. VAR=scan (of course this doesn't work - it makes VAR equal the string "scan")

Brent
  • 16,259
  • 12
  • 42
  • 42

3 Answers3

204
VAR=$(scan)

Exactly the same way as for programs.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Robert Obryk
  • 2,369
  • 1
  • 14
  • 6
  • 3
    I discovered that newlines were being stripped when I did "echo $VAR". If instead I quoted $VAR, it preserved the newlines. – Brent Nov 27 '09 at 17:58
  • 2
    That's not 100% right. Command substitution always strips trailing newlines. – TheBonsai Nov 28 '09 at 17:36
  • 17
    This creates a subshell; is there any way to do it in the same shell? – lmat - Reinstate Monica May 02 '18 at 15:58
  • 2
    @lmat-ReinstateMonica you can use what's called a `nameref` in bash 4.3 and above to achieve that. [this answer](https://stackoverflow.com/a/39930905/5556676) covers some of the notion. – init_js May 17 '21 at 20:02
37

You may use bash functions in commands/pipelines as you would otherwise use regular programs. The functions are also available to subshells and transitively, Command Substitution:

VAR=$(scan)

Is the straighforward way to achieve the result you want in most cases. I will outline special cases below.

Preserving trailing Newlines:

One of the (usually helpful) side effects of Command Substitution is that it will strip any number of trailing newlines. If one wishes to preserve trailing newlines, one can append a dummy character to output of the subshell, and subsequently strip it with parameter expansion.

function scan2 () {
    local nl=$'\x0a';  # that's just \n
    echo "output${nl}${nl}" # 2 in the string + 1 by echo
}

# append a character to the total output.
# and strip it with %% parameter expansion.
VAR=$(scan2; echo "x"); VAR="${VAR%%x}"

echo "${VAR}---"

prints (3 newlines kept):

output


---

Use an output parameter: avoiding the subshell (and preserving newlines)

If what the function tries to achieve is to "return" a string into a variable , with bash v4.3 and up, one can use what's called a nameref. Namerefs allows a function to take the name of one or more variables output parameters. You can assign things to a nameref variable, and it is as if you changed the variable it 'points to/references'.

function scan3() {
    local -n outvar=$1    # -n makes it a nameref.
    local nl=$'\x0a'
    outvar="output${nl}${nl}"  # two total. quotes preserve newlines
}

VAR="some prior value which will get overwritten"

# you pass the name of the variable. VAR will be modified.
scan3 VAR

# newlines are also preserved.
echo "${VAR}==="

prints:

output

===

This form has a few advantages. Namely, it allows your function to modify the environment of the caller without using global variables everywhere.

Note: using namerefs can improve the performance of your program greatly if your functions rely heavily on bash builtins, because it avoids the creation of a subshell that is thrown away just after. This generally makes more sense for small functions reused often, e.g. functions ending in echo "$returnstring"

This is relevant. https://stackoverflow.com/a/38997681/5556676

Community
  • 1
  • 1
init_js
  • 4,143
  • 2
  • 23
  • 53
0

I think init_js should use declare instead of local!

function scan3() {
    declare -n outvar=$1    # -n makes it a nameref.
    local nl=$'\x0a'
    outvar="output${nl}${nl}"  # two total. quotes preserve newlines
}
  • the `local` builtin will accept any options that the `declare` builtin will accept. from a quick test, it also looks as if `declare -n` in a function scope also gives the variable local scope. it seems they are interchangeable here. – init_js Sep 06 '17 at 19:58