152

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")

1

4 Answers 4

222
VAR=$(scan)

Exactly the same way as for programs.

Sign up to request clarification or add additional context in comments.

5 Comments

I discovered that newlines were being stripped when I did "echo $VAR". If instead I quoted $VAR, it preserved the newlines.
That's not 100% right. Command substitution always strips trailing newlines.
This creates a subshell; is there any way to do it in the same shell?
@lmat-ReinstateMonica you can use what's called a nameref in bash 4.3 and above to achieve that. this answer covers some of the notion.
Just beware of the confusion if you are returning a status code from the function. Using this syntax will not assign the status code / error code to the variable. It will assign the output of the command (any echo statements for instance) to the variable. You can still pick up the return status of the function as status_code=$? if you do that in the very next line. The successful assignment will not obscure the status code.
42

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

Comments

2

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
}

1 Comment

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.
2

For sake of clarity, today one can pass a var by "nameref" or "named reference" to a bash function, and the function will be able to modify that exact reference.

Example:

function scrapeToMem() {
  # param 1: url
  local url=$1
  # param 2: use a 'nameref' to return
  local -n result=$2

  # get html in memory variable:
  result=$(wget -qnv --show-progress -O - "${url}")

  if [[ -z "${result}" ]]; then
    return 1
  fi

  return 0
}

urlLatest='https://www.google.com'
scrapeFile=
scrapeToMem "${urlLatest}" scrapeFile
if [ $? -ne 0 ]; then
  echo "Error encountered #1."
  exit 1
fi

if [[ -z "${scrapeFile}" ]]; then
  echo "Error encountered #1."
fi

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.