Differences between revisions 13 and 20 (spanning 7 versions)
Revision 13 as of 2012-10-16 17:56:51
Size: 3184
Editor: GreyCat
Comment: delete blank line that breaks the bot
Revision 20 as of 2022-11-14 19:36:10
Size: 5090
Editor: GreyCat
Comment: printf is actually easier to read here, especially in the tmpfile example
Deletions are marked like this. Additions are marked like this.
Line 5: Line 5:
If you need to send back arbitrary data from a function to its caller, there are at least three methods by which this can be achieved: If you need to send back arbitrary data from a function to its caller, there are several different methods by which this can be achieved.
Line 7: Line 7:
 * You may have your function write the data to stdout, and then have the caller capture stdout.
  . {{{
  foo() {
     echo "this is my data"
  }
  x=$(foo)
  echo "foo returned '$x'"
}}}
 One drawback of this method is that the function is executed in a SubShell, which means that any variable assignments, etc. performed in the function will not take effect in the caller's environment (and incurs a speed penalty as well, due to a `fork()`). This may or may not be a problem, depending on the needs of your program and your function. Another drawback is that ''everything'' printed by the function `foo` is captured and put into the variable instead. This leads to problems if `foo` also writes things that are not intended to be a returned value. To isolate user prompts and/or error messages from "returned" data, redirect them to stderr which will not be captured by the caller.
=== Capturing standard output ===
You may have your function write the data to stdout, and then have the caller capture stdout.
{{{#!highlight bash
foo() {
   echo "this is my data"
}
Line 17: Line 14:
 . {{{
  foo() {
     echo "running foo()..." >&2 # send user prompts and error messages to stderr
     echo "this is my data" # variable will be assigned this value below
  }
  x=$(foo) # prints: running foo()...
  echo "foo returned '$x'" # prints: foo returned 'this is my data'
x=$(foo)
printf 'foo returned "%s"\n' "$x"
Line 26: Line 18:
 * You may assign data to global variables, and then refer to those variables in the caller.
  . {{{
  foo() {
     return="this is my data"
  }
  foo
  echo "foo returned '$return'"
One drawback of this method is that the function is executed in a SubShell, which means that any variable assignments, etc. performed in the function will not take effect in the caller's environment (and incurs a speed penalty as well, due to a `fork()`). This may or may not be a problem, depending on the needs of your program and your function. Another drawback is that ''everything'' printed by the function `foo` is captured and put into the variable instead. This leads to problems if `foo` also writes things that are not intended to be a returned value. To isolate user prompts and/or error messages from "returned" data, redirect them to stderr which will not be captured by the caller.

{{{#!highlight bash
foo() {
   echo "running foo()..." >&2 # send user prompts and error messages to stderr
   echo "this is my data" # variable will be assigned this value below
}

x=$(foo) # prints: running foo()...
printf 'foo returned "%s"\n' "$x" # prints: foo returned "this is my data"
Line 34: Line 29:
 The drawback of this method is that if the function ''is'' executed in a subshell, then the assignment to a global variable inside the function will ''not'' be seen by the caller. This means you would not be able to use the function in a pipeline, for example.
Line 36: Line 30:
 * Your function may write its data to a file, from which the caller can read it.
  . {{{
  foo() {
     echo "this is my data" > "$1"
  }
  # This is NOT solid code for handling temp files!
  tmpfile=$(mktemp) # GNU/Linux
  foo "$tmpfile"
  echo "foo returned '$(<"$tmpfile")'"
  rm "$tmpfile"
  # If this were a real program, there would have been error checking, and a trap.
=== Global variables ===
You may assign data to global variables, and then refer to those variables in the caller.
{{{#!highlight bash
foo() {
   return="this is my data"
}

foo
printf 'foo returned "%s"\n' "$return"
Line 48: Line 40:
   . The drawbacks of this method should be obvious: you need to manage a temporary file, which is always inconvenient; there must be a writable directory somewhere, and sufficient space to hold the data therein; etc. On the positive side, it will work regardless of whether your function is executed in a subshell.
 For more information about handling temporary files within a shell script, see [[BashFAQ/062|FAQ 62]]. For traps, see SignalTrap.

The advantage of this method (compared to capturing stdout) is that your function is not executed in a SubShell, which means the function call is ''much'' faster. It also means side effects (like other variable assignments and FileDescriptor changes) will affect the rest of the script.

The drawback of this method is that if the function ''is'' executed in a subshell, then the assignment to a global variable inside the function will ''not'' be seen by the caller. This means you would not be able to use the function in a pipeline, for example.

=== Writing to a file ===
Your function may write its data to a file, from which the caller can read it.
{{{#!highlight bash
foo() {
   echo "this is my data" > "$1"
}

# This is NOT solid code for handling temp files!
tmpfile=$(mktemp) # GNU/Linux
foo "$tmpfile"
printf 'foo returned "%s"\n' "$(<"$tmpfile")"
rm "$tmpfile"
# If this were a real program, there would have been error checking, and a trap.
}}}

The drawbacks of this method should be obvious: you need to manage a temporary file, which is always inconvenient; there must be a writable directory somewhere, and sufficient space to hold the data therein; etc. On the positive side, it will work regardless of whether your function is executed in a SubShell.

For more information about handling temporary files within a shell script, see [[BashFAQ/062|FAQ 62]]. For traps, see SignalTrap.

=== Dynamically scoped variables ===
Instead of using global variables, you can use variables whose scope is restricted to the caller and the called function.
{{{#!highlight bash
rand() {
   local max=$((32768 / $1 * $1))
   while (( (r=$RANDOM) >= max )); do :; done
   r=$(( r % $1 ))
}

foo() {
   local r
   rand 6
   echo "You rolled $((r+1))!"
}

foo
# Here at the global scope, 'r' is not visible.
}}}

This has the same advantages and disadvantages as using global variables, plus the additional advantage that the global variable namespace isn't "polluted" by the function return variable.

However, this technique doesn't work with recursive functions.
{{{#!highlight bash
# This example won't work.
fact() {
   local r # to hold the return value of things we call
   if (($1 == 1)); then
      r=1 # to send data back to the caller
   else
      fact $(($1 - 1)) # call ourself recursively
      r=$((r * $1)) # send data back to the caller
   fi
}
}}}

There is a variable name collision -- the example above tries to use `r` for two conflicting purposes at the same time. For recursive functions, stick with the global variable technique.
{{{#!highlight bash
# This example works. It's not the best way to compute a factorial, but
# it's a simple example of a recursive function.
fact() {
   if (($1 <= 1)); then
      r=1
   else
      fact "$(($1 - 1))"
      ((r *= $1))
   fi
}

fact 11
echo "$r"
}}}

How do I return a string (or large number, or negative number) from a function? "return" only lets me give a number from 0 to 255.

Functions in Bash (as well as all the other Bourne-family shells) work like commands: that is, they only "return" an exit status, which is an integer from 0 to 255 inclusive. This is intended to be used only for signaling errors, not for returning the results of computations, or other data.

If you need to send back arbitrary data from a function to its caller, there are several different methods by which this can be achieved.

Capturing standard output

You may have your function write the data to stdout, and then have the caller capture stdout.

   1 foo() {
   2    echo "this is my data"
   3 }
   4 
   5 x=$(foo)
   6 printf 'foo returned "%s"\n' "$x"

One drawback of this method is that the function is executed in a SubShell, which means that any variable assignments, etc. performed in the function will not take effect in the caller's environment (and incurs a speed penalty as well, due to a fork()). This may or may not be a problem, depending on the needs of your program and your function. Another drawback is that everything printed by the function foo is captured and put into the variable instead. This leads to problems if foo also writes things that are not intended to be a returned value. To isolate user prompts and/or error messages from "returned" data, redirect them to stderr which will not be captured by the caller.

   1 foo() {
   2    echo "running foo()..."  >&2        # send user prompts and error messages to stderr
   3    echo "this is my data"              # variable will be assigned this value below
   4 }
   5 
   6 x=$(foo)                               # prints:  running foo()...
   7 printf 'foo returned "%s"\n' "$x"      # prints:  foo returned "this is my data"

Global variables

You may assign data to global variables, and then refer to those variables in the caller.

   1 foo() {
   2    return="this is my data"
   3 }
   4 
   5 foo
   6 printf 'foo returned "%s"\n' "$return"

The advantage of this method (compared to capturing stdout) is that your function is not executed in a SubShell, which means the function call is much faster. It also means side effects (like other variable assignments and FileDescriptor changes) will affect the rest of the script.

The drawback of this method is that if the function is executed in a subshell, then the assignment to a global variable inside the function will not be seen by the caller. This means you would not be able to use the function in a pipeline, for example.

Writing to a file

Your function may write its data to a file, from which the caller can read it.

   1 foo() {
   2    echo "this is my data" > "$1"
   3 }
   4 
   5 # This is NOT solid code for handling temp files!
   6 tmpfile=$(mktemp)   # GNU/Linux
   7 foo "$tmpfile"
   8 printf 'foo returned "%s"\n' "$(<"$tmpfile")"
   9 rm "$tmpfile"
  10 # If this were a real program, there would have been error checking, and a trap.

The drawbacks of this method should be obvious: you need to manage a temporary file, which is always inconvenient; there must be a writable directory somewhere, and sufficient space to hold the data therein; etc. On the positive side, it will work regardless of whether your function is executed in a SubShell.

For more information about handling temporary files within a shell script, see FAQ 62. For traps, see SignalTrap.

Dynamically scoped variables

Instead of using global variables, you can use variables whose scope is restricted to the caller and the called function.

   1 rand() {
   2    local max=$((32768 / $1 * $1))
   3    while (( (r=$RANDOM) >= max )); do :; done
   4    r=$(( r % $1 ))
   5 }
   6 
   7 foo() {
   8    local r
   9    rand 6
  10    echo "You rolled $((r+1))!"
  11 }
  12 
  13 foo
  14 # Here at the global scope, 'r' is not visible.

This has the same advantages and disadvantages as using global variables, plus the additional advantage that the global variable namespace isn't "polluted" by the function return variable.

However, this technique doesn't work with recursive functions.

   1 # This example won't work.
   2 fact() {
   3    local r      # to hold the return value of things we call
   4    if (($1 == 1)); then
   5       r=1       # to send data back to the caller
   6    else
   7       fact $(($1 - 1))       # call ourself recursively
   8       r=$((r * $1))          # send data back to the caller
   9    fi
  10 }

There is a variable name collision -- the example above tries to use r for two conflicting purposes at the same time. For recursive functions, stick with the global variable technique.

   1 # This example works.  It's not the best way to compute a factorial, but
   2 # it's a simple example of a recursive function.
   3 fact() {
   4    if (($1 <= 1)); then
   5       r=1
   6    else
   7       fact "$(($1 - 1))"
   8       ((r *= $1))
   9    fi
  10 }
  11 
  12 fact 11
  13 echo "$r"


CategoryShell

BashFAQ/084 (last edited 2022-11-14 19:36:10 by GreyCat)