557

I am working with a bash script and I want to execute a function to print a return value:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

When I execute fun2, it does not print "34". Why is this the case?

learningbee
  • 333
  • 1
  • 5
  • 11
mindia
  • 6,991
  • 4
  • 22
  • 20
  • 20
    `return` in your case is essentially the same as `exit code` which range from `0 - 255`. Use `echo` as suggested by @septi. Exit codes can be captured with `$?`. – devnull Jun 27 '13 at 07:26
  • 3
    In this case it is much more flexible to already use echo in fun1. It’s the idea of unix-programming: echo sends the results to standard output which then can be reused by other functions with res=$(fun1) - or directly be piped to other functions: `function a() { echo 34; }` `function b() { while read data; do echo $data ; done ;}` `a | b` – Arne Babenhauserheide Jan 08 '14 at 22:37
  • The proper way to do this is to put the top level stuff in a function and use a local with bash's dynamic scoping rule. I'll create an answer to demonstrate, it is not a well-known feature but one fully supported. – Oliver Aug 29 '18 at 15:47
  • See also: https://stackoverflow.com/a/8743103/12887 – Jonathan Tran Apr 16 '20 at 19:43
  • See https://www.linuxjournal.com/content/return-values-bash-functions – kgbook May 29 '20 at 03:15
  • bash Functions return a value, called an exit status. This is analogous to the exit status returned by a command. The exit status may be explicitly specified by a return statement, otherwise it is the exit status of the last command in the function (0 if successful, and a non-zero error code if not). This exit status may be used in the script by referencing it as $?. This mechanism effectively permits script functions to have a "return value" similar to C functions. quote from: https://tldp.org/LDP/abs/html/complexfunct.html – opinion_no9 Dec 11 '20 at 15:36

11 Answers11

681

Although Bash has a return statement, the only thing you can specify with it is the function's own exit status (a value between 0 and 255, 0 meaning "success"). So return is not what you want.

You might want to convert your return statement to an echo statement - that way your function output could be captured using $() braces, which seems to be exactly what you want.

Here is an example:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Another way to get the return value (if you just want to return an integer 0-255) is $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Also, note that you can use the return value to use Boolean logic - like fun1 || fun2 will only run fun2 if fun1 returns a non-0 value. The default return value is the exit value of the last statement executed within the function.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tamasgal
  • 24,826
  • 18
  • 96
  • 135
  • Thanks, but i want to use return.How can I get value?in this case – mindia Jun 27 '13 at 07:24
  • 3
    You need to execute `fun1` and then the return value is stored in `$?`. Although I wouldn't recommend to do that… – tamasgal Jun 27 '13 at 07:25
  • 352
    No, I need the damn **return value**. To hell with echo. – Tomáš Zato Apr 21 '15 at 19:29
  • 3
    `fun1 || fun2 will only run fun2 if fun1 returns a 0 value.` shouldn't it be `a non-0 value`? – phil294 Mar 13 '16 at 03:37
  • 11
    @Blauhirn in this environment, with this `||` construct, an exit code of 0 is considered success and therefore "true". Non-zero is error and therefore false. Think of `fun1 || fun2` as shorthand for "if fun1 returns success or fun2 returns success" rather than an operator on the actual return values themselves. – davidA Mar 30 '16 at 21:08
  • 2
    @meowsqueak sorry, I don't get it. `if fun1 returns success or fun2 returns success`. So, if `fun1` already returned success, `fun2` will NOT be executed! right? thus, `fun1` needs to return `NON-0-value` for `fun2` to run. – phil294 Mar 30 '16 at 21:55
  • 2
    @Blauhirn yes, I think you're right. Either it should be "a non-zero value" as you say, or the operator should be `&&` not `||`. My previous comment was incorrect. – davidA Mar 31 '16 at 01:51
  • 13
    What's annoying is that a function that should provide data cannot also echo other stuff to stdout, because the caller using $() will receive that too and get confused or have to parse the output. Global variables are not great because it's just a matter of time before you use the same global var in two places that happen to be nested and data could get lost. There should be separate channels for printing data vs sending data back. – Oliver Aug 02 '18 at 22:19
  • The proper way to do this is to put the top level stuff in a function and use a local with bash's dynamic scoping rule. I'll create an answer to demonstrate, it is not a well-known feature but one fully supported. – Oliver Aug 29 '18 at 15:46
  • @Oliver for *echoing stuff to stdout* I usually echo to stderr instead (when I'm in control of all code) so `$(func)` can still be used and traces intercepted on stderr (which can be redirected to `/dev/null` as in `$(func 2>/dev/null)`). Not the most elegant way but a lot easier most of the times... – Matthieu Mar 27 '19 at 18:16
  • Does this work with non-string values such as an array? Do you echo an array? – Banoona Sep 23 '21 at 16:22
  • 1
    @TomášZato-ReinstateMonica, If you consider functions to be similar to commands, it makes a lot more sense to echo from a function. Bash is not just a language but also a shell. Consider a bash script that calls upon the `ls` command and also calls on a function called `my_ls` that echoes something or runs a command. The code to interact with the command and the function are identical: `ls_output=$(ls);` and `my_ls_output=$(my_ls);`. If you think of both functions and commands as things that produce output and return codes, the echoes in functions make a lot more sense in Bash scripts. – John Pancoast Oct 23 '21 at 18:28
120

Functions in Bash are not functions like in other languages; they're actually commands. So functions are used as if they were binaries or scripts fetched from your path. From the perspective of your program logic, there shouldn't really be any difference.

Shell commands are connected by pipes (aka streams), and not fundamental or user-defined data types, as in "real" programming languages. There is no such thing like a return value for a command, maybe mostly because there's no real way to declare it. It could occur on the man-page, or the --help output of the command, but both are only human-readable and hence are written to the wind.

When a command wants to get input it reads it from its input stream, or the argument list. In both cases text strings have to be parsed.

When a command wants to return something, it has to echo it to its output stream. Another often practiced way is to store the return value in dedicated, global variables. Writing to the output stream is clearer and more flexible, because it can take also binary data. For example, you can return a BLOB easily:

encrypt() {
    gpg -c -o- $1 # Encrypt data in filename to standard output (asks for a passphrase)
}

encrypt public.dat > private.dat # Write the function result to a file

As others have written in this thread, the caller can also use command substitution $() to capture the output.

Parallely, the function would "return" the exit code of gpg (GnuPG). Think of the exit code as a bonus that other languages don't have, or, depending on your temperament, as a "Schmutzeffekt" of shell functions. This status is, by convention, 0 on success or an integer in the range 1-255 for something else. To make this clear: return (like exit) can only take a value from 0-255, and values other than 0 are not necessarily errors, as is often asserted.

When you don't provide an explicit value with return, the status is taken from the last command in a Bash statement/function/command and so forth. So there is always a status, and return is just an easy way to provide it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andreas Spindler
  • 7,568
  • 4
  • 43
  • 34
  • 24
    +1 for explaining functions vs commands and how this impacts the notion of sending data back to the caller – Oliver Aug 02 '18 at 22:16
  • 13
    +1 for explaining that shell programming is about connecting commands via pipes. Other programming languages compose functions via return types. Bash composes commands via streams of text. – jmrah Nov 02 '18 at 00:10
  • 2
    What if a function has to do both? That is, send some output from the script, as well as produce some text as its return value, that is NOT supposed to be disturbed by whatever this function has to log to the script's stdout. – SasQ Sep 05 '20 at 05:01
  • https://tldp.org/LDP/abs/html/complexfunct.html Bash: Functions return a value, called an exit status. This is analogous to the exit status returned by a command. The exit status may be explicitly specified by a return statement, otherwise it is the exit status of the last command in the function (0 if successful, and a non-zero error code if not). This exit status may be used in the script by referencing it as $?. This mechanism effectively permits script functions to have a "return value" similar to C functions. – opinion_no9 Dec 11 '20 at 15:38
103

$(...) captures the text sent to standard output by the command contained within. return does not output to standard output. $? contains the result code of the last command.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 13
    Yes `return` is used for setting `$?` which is the `exit status`. In the above example, `fun1`'s `exit status` would be `34`. Also, note that `$(...)` also captures stderr in addition to stdout from command specified. – akarollil Nov 27 '17 at 21:33
72

The problem with other answers is they either use a global, which can be overwritten when several functions are in a call chain, or echo which means your function cannot output diagnostic information (you will forget your function does this and the "result", i.e. return value, will contain more information than your caller expects, leading to weird bugs), or eval which is way too heavy and hacky.

The proper way to do this is to put the top level stuff in a function and use a local with Bash's dynamic scoping rule. Example:

func1()
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

This outputs

nothing
hi
bye

Dynamic scoping means that ret_val points to a different object, depending on the caller! This is different from lexical scoping, which is what most programming languages use. This is actually a documented feature, just easy to miss, and not very well explained. Here is the documentation for it (emphasis is mine):

Variables local to the function may be declared with the local builtin. These variables are visible only to the function and the commands it invokes.

For someone with a C, C++, Python, Java,C#, or JavaScript background, this is probably the biggest hurdle: functions in bash are not functions, they are commands, and behave as such: they can output to stdout/stderr, they can pipe in/out, and they can return an exit code. Basically, there isn't any difference between defining a command in a script and creating an executable that can be called from the command line.

So instead of writing your script like this:

Top-level code
Bunch of functions
More top-level code

write it like this:

# Define your main, containing all top-level code
main()
Bunch of functions
# Call main
main

where main() declares ret_val as local and all other functions return values via ret_val.

See also the Unix & Linux question Scope of Local Variables in Shell Functions.

Another, perhaps even better solution depending on situation, is the one posted by ya.teck which uses local -n.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oliver
  • 27,510
  • 9
  • 72
  • 103
  • 1
    From what I've read, local is not POSIX compatible, and almost every shell implements it differently, so it's not a portable solution. – user2465201 Dec 04 '20 at 17:00
  • What exactly would be the difference if you only deleted the `local` builtin in this example? (Because that's pretty much the way I use it sometimes..) – BUFU Oct 25 '21 at 07:56
  • That was a big piece of information that I was missing and at first glance it seems this makes scripts difficult to debug, especially if it is not yours. – progonkpa Feb 01 '22 at 16:20
45

Another way to achieve this is name references (requires Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example RESULT
echo $RESULT
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ya.teck
  • 2,060
  • 28
  • 34
40

The return statement sets the exit code of the function, much the same as exit will do for the entire script.

The exit code for the last command is always available in the $? variable.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Austin Phillips
  • 15,228
  • 2
  • 51
  • 50
18

As an add-on to others' excellent posts, here's an article summarizing these techniques:

  • set a global variable
  • set a global variable, whose name you passed to the function
  • set the return code (and pick it up with $?)
  • 'echo' some data (and pick it up with MYVAR=$(myfunction) )

Returning Values from Bash Functions

Tom Hundt
  • 1,694
  • 19
  • 18
11

I like to do the following if running in a script where the function is defined:

POINTER= # Used for function return values

my_function() {
    # Do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # Do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

I like this, because I can then include echo statements in my functions if I want

my_function() {
    echo "-> my_function()"
    # Do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
doc
  • 765
  • 1
  • 6
  • 24
8

The simplest way I can think of is to use echo in the method body like so

get_greeting() {
  echo "Hello there, $1!"
}

STRING_VAR=$(get_greeting "General Kenobi")
echo $STRING_VAR
# Outputs: Hello there, General Kenobi!
Ahmed Sayed
  • 1,429
  • 12
  • 12
4

Instead of calling var=$(func) with the whole function output, you can create a function that modifies the input arguments with eval,

var1="is there"
var2="anybody"

function modify_args() {
    echo "Modifying first argument"
    eval $1="out"
    
    echo "Modifying second argument"
    eval $2="there?"
}

modify_args var1 var2
# Prints "Modifying first argument" and "Modifying second argument"
# Sets var1 = out
# Sets var2 = there?

This might be useful in case you need to:

  1. Print to stdout/stderr within the function scope (without returning it)
  2. Return (set) multiple variables.
Noam Manos
  • 15,216
  • 3
  • 86
  • 85
-3

Git Bash on Windows is using arrays for multiple return values

Bash code:

#!/bin/bash

## A 6-element array used for returning
## values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ## Give the positional arguments/inputs
   ## $1 and $2 some sensible names:
   local out_dex_1="$1" ## Output index
   local out_dex_2="$2" ## Output index

   ## Echo for debugging:
   echo "Running: FN_MULTIPLE_RETURN_VALUES"

   ## Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ## Set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ## <-- Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
## ---------------------------------------------- ##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ## <--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
## ---------------------------------------------- ##
FN_MULTIPLE_RETURN_VALUES 4 5  ## <--- Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

Expected output:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
KANJICODER
  • 3,611
  • 30
  • 17
  • 1
    Why would Git Bash be different? What is the explanation? – Peter Mortensen Mar 16 '21 at 16:26
  • I don't know. But sometimes when I am on stack overflow I just want to find an answer that works in my environment and not think about it. – KANJICODER May 24 '21 at 05:05
  • I'd suggest only mentioning your environment at the **end** of the answer, saying something like "Tested on git bash on windows". This avoids having people ignore your answer at first glance. – Kelvin Feb 10 '22 at 21:28