557

I'd like to return a string from a Bash function.

I'll write the example in java to show what I'd like to do:

public String getSomeString() {
  return "tadaa";
}

String variable = getSomeString();

The example below works in bash, but is there a better way to do this?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Jeff Puckett
  • 37,464
  • 17
  • 118
  • 167
Tomas F
  • 7,226
  • 6
  • 27
  • 36
  • 10
    As an aside, `function funcName {` is pre-POSIX legacy syntax inherited from early ksh (where it had semantic differences that bash doesn't honor). `funcName() {`, with no `function`, should be used instead; see http://wiki.bash-hackers.org/scripting/obsolete – Charles Duffy Mar 07 '18 at 17:14
  • 2
    That link says to use NAME() COMPOUND-CMD or function NAME { CMDS; } So `function myFunction { blah; }` is fine; it's `function myFunction() { blah }` that is not fine, i.e the use of parenthesis with the keyword function. – Will Mar 17 '19 at 11:09
  • 1
    See this answer that explains how to create namerefs in bash functions: https://stackoverflow.com/a/52678279/1583763 – Kent Jan 06 '21 at 22:11
  • @Will Look at the second table where `NAME()` is suggested as a replacement for `function NAME` thus ultimately leading to what @Charles Duffy wrote in his comment. – Piotr Dobrogost Oct 19 '21 at 07:23

21 Answers21

346

There is no better way I know of. Bash knows only status codes (integers) and strings written to the stdout.

Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 16
    +1 @tomas-f : you have to be really careful on what you have in this function "getSomeString()" as having any code which will eventually echo will mean that you get incorrect return string. – Mani Sep 14 '12 at 16:38
  • 15
    **This is just plain wrong.** You can return arbitrary data inside a return variable. Which clearly is a better way. – Evi1M4chine Jan 05 '16 at 16:18
  • 46
    @Evi1M4chine, um...no, you can't. You can set a global variable and call it "return", as I see you do in your scripts. But then that is by convention, NOT actually tied programmatically to the execution of your code. "clearly a better way"? Um, no. Command substitution is far more explicit and modular. – Wildcard Jan 06 '16 at 03:24
  • 8
    "Command substitution is far more explicit and modular" would be relevant if the question were about commands; this question is how to return a string, from a bash function! A built in way to do what the OP has asked is available since Bash 4.3 (2014?) - see my answer below. – zenaan Oct 10 '16 at 03:07
  • As EviM4chine, I do not agree with your point of view. You can pass variable to function – Tony Chemit Sep 05 '17 at 13:58
  • 4
    The original question contains the simplest way to do it, and works well in most cases. Bash return values should probably be called "return codes" because they're less like standard return values in scripting, and more like numeric shell command exit codes (you can do stuff like `somefunction && echo 'success'`). If you think of a function like just another command, it makes sense; commands don't "return" anything on exit other than a status code, but they may output things in the meantime that you can capture. – Beejor Nov 22 '17 at 06:44
  • 4
    I believe this is a good answer. If you are trying to return values just like you do in other languages, you are going to have a bad time. Either embrace Bash's global variables way, or use another scripting language. If you're going the third way, it's time to take two steps back and re-evaluate if you're using the right tool for the job. – Bruno Laturner Dec 30 '17 at 04:54
  • Fun fact: ALL variables set inside a function in bash are de-facto output values! :D … Because they will be global unless the `local` keyword is used. So this answer is “not even wrong”. It is unintentionally misleading as it implies the behavior of other languages instead of what’s idiomatic code for bash. … In short: Setting (environment) globals is idiomatic bash for returning values. The built-in function `read` proves that. – Evi1M4chine Mar 20 '23 at 03:16
  • And to be fair to @Philipp: This is really rather unusual behavior for a programmind/scripting language nowadays, and not really documented either. So technically, but only technically, he’s still correct. – Evi1M4chine Mar 20 '23 at 03:17
218

You could have the function take a variable as the first arg and modify the variable with the string you want to return.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

Prints "foo bar rab oof".

Edit: added quoting in the appropriate place to allow whitespace in string to address @Luca Borrione's comment.

Edit: As a demonstration, see the following program. This is a general-purpose solution: it even allows you to receive a string into a local variable.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar=''
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

This prints:

+ return_var=
+ pass_back_a_string return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local lvar=
+ pass_back_a_string lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

Edit: demonstrating that the original variable's value is available in the function, as was incorrectly criticized by @Xichen Li in a comment.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original $1 is \$$1"
    eval "$1='foo bar rab oof'"
}

return_var='original return_var'
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar='original lvar'
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

This gives output:

+ return_var='original return_var'
+ pass_back_a_string return_var
+ eval 'echo in pass_back_a_string, original return_var is $return_var'
++ echo in pass_back_a_string, original return_var is original return_var
in pass_back_a_string, original return_var is original return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local 'lvar=original lvar'
+ pass_back_a_string lvar
+ eval 'echo in pass_back_a_string, original lvar is $lvar'
++ echo in pass_back_a_string, original lvar is original lvar
in pass_back_a_string, original lvar is original lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally
bstpierre
  • 30,042
  • 15
  • 70
  • 103
  • 1
    This answer is great! Parameters can be passed by references, similar to the idea in C++. – Yun Huang Apr 19 '12 at 06:08
  • 4
    It would be nice to receive a response from an expert about that answer. I've never seen that used in scripts, maybe for a good reason. Anyway: that's +1 It should have been voted for correct answer – John Apr 19 '12 at 11:46
  • Isn't this the same of `fgm` answer written in a simplified way? This won't work if the string `foo` contains white spaces, while the `fgm`'s one will .. as he's showing. – Luca Borrione May 08 '12 at 21:32
  • @LucaBorrione: I think this answer was posted first, and yes, it does appear to be simpler than fgm's answer, which is probably a benefit to the OP. – bstpierre May 09 '12 at 01:46
  • -1. The initial value of this "out" argument cannot be used in the function. – SOUser Dec 04 '12 at 15:10
  • 4
    @XichenLi: thanks for leaving a comment with your downvote; please see my edit. You can get the initial value of the variable in the function with `\$$1`. If you are looking for something different, please let me know. – bstpierre Dec 04 '12 at 16:46
  • Thank you for your efforts to give further instructions! – SOUser Dec 05 '12 at 23:13
  • 1
    I thought this answer was neat. Unfortunately, my return string contains parentheses and single quotes which eval tries to interpret when it should be treated as literal. :( – timiscoding Jun 18 '15 at 11:19
  • 1
    @timiscoding That can be fixed with a `printf '%q' "$var"`. %q is a format string for shell escape. Then just pass it raw. – bb010g Jul 23 '15 at 21:53
  • Will not work if passed variable is local to a function. – kenj Aug 09 '16 at 04:52
  • works in Busybox `ash` shell too - works ok passing `local` variables as long as you pass `my_fn variable` (by reference - note the `$` absence) & not `my_fn $variable` (by value) – Stuart Cardall Apr 08 '17 at 22:26
  • Is eval a security risk? I often read that eval is evil. – openCivilisation Mar 19 '21 at 10:15
  • @openCivilisation yes it's a bit of a risk. You have to judge whether there's any possibility of getting hostile user input into what you're eval'ing. – bstpierre Mar 20 '21 at 16:11
120

All answers above ignore what has been stated in the man page of bash.

  • All variables declared inside a function will be shared with the calling environment.
  • All variables declared local will not be shared.

Example code

#!/bin/bash

f()
{
    echo function starts
    local WillNotExists="It still does!"
    DoesNotExists="It still does!"
    echo function ends
}

echo $DoesNotExists #Should print empty line
echo $WillNotExists #Should print empty line
f                   #Call the function
echo $DoesNotExists #Should print It still does!
echo $WillNotExists #Should print empty line

And output

$ sh -x ./x.sh
+ echo

+ echo

+ f
+ echo function starts 
function starts
+ local 'WillNotExists=It still does!'
+ DoesNotExists='It still does!'
+ echo function ends 
function ends
+ echo It still 'does!' 
It still does!
+ echo

Also under pdksh and ksh this script does the same!

Vicky Ronnen
  • 1,217
  • 1
  • 8
  • 2
  • 14
    This answer does have its merits. I came in here thinking that I wanted to return a string from a function. This answer made me realize that that was just my C#-habits talking. I suspect others may have the same experience. – LOAS Apr 15 '13 at 07:29
  • 5
    @ElmarZander You're wrong, this is entirely relevant. This is a simple way to get into global scope a function-scope value, and some would consider this better/simpler than the eval approach to redefine a global variable as outlined by bstpierre. – KomodoDave May 17 '13 at 12:32
  • local is not portable to non-bash scripts which is one reason some people avoid it. – don bright Mar 22 '14 at 16:05
  • Question: What about variables in loops ? – anu Sep 26 '15 at 21:56
  • 1
    On a mac ($ bash --version GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), it is correct that a matching global variable is initialized, but when I try to side-effect the same variable in another function f2, that side-effect is not persisted. So, it seems very inconsistent and thus not good for my usage. – AnneTheAgile Oct 02 '15 at 19:42
  • 1
    i must add that using this method is super clean BUT you cannot run things in parallel as var name will be overriden each time! so this is --> OK ONLY FOR SYNCHRONOUS WORK – danfromisrael Jul 05 '20 at 09:14
  • Good answer, specially for those (like me) that are new to bash – Leonardo Maffei Oct 19 '21 at 18:35
65

Bash, since version 4.3, feb 2014(?), has explicit support for reference variables or name references (namerefs), beyond "eval", with the same beneficial performance and indirection effect, and which may be clearer in your scripts and also harder to "forget to 'eval' and have to fix this error":

declare [-aAfFgilnrtux] [-p] [name[=value] ...]
typeset [-aAfFgilnrtux] [-p] [name[=value] ...]
  Declare variables and/or give them attributes
  ...
  -n Give each name the nameref attribute, making it a name reference
     to another variable.  That other variable is defined by the value
     of name.  All references and assignments to name, except for⋅
     changing the -n attribute itself, are performed on the variable
     referenced by name's value.  The -n attribute cannot be applied to
     array variables.
...
When used in a function, declare and typeset make each name local,
as with the local command, unless the -g option is supplied...

and also:

PARAMETERS

A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see the descriptions of declare and local below) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is⋅ referenced or assigned to, the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to⋅ the function. For instance, if a variable name is passed to a shell function as its first argument, running

      declare -n ref=$1

inside the function creates a nameref variable ref whose value is the variable name passed as the first argument. References and assignments to ref are treated as references and assignments to the variable whose name was passed as⋅ $1. If the control variable in a for loop has the nameref attribute, the list of words can be a list of shell variables, and a name reference will be⋅ established for each word in the list, in turn, when the loop is executed. Array variables cannot be given the -n attribute. However, nameref variables can reference array variables and subscripted array variables. Namerefs can be⋅ unset using the -n option to the unset builtin. Otherwise, if unset is executed with the name of a nameref variable as an argument, the variable referenced by⋅ the nameref variable will be unset.

For example (EDIT 2: (thank you Ron) namespaced (prefixed) the function-internal variable name, to minimize external variable clashes, which should finally answer properly, the issue raised in the comments by Karsten):

# $1 : string; your variable to contain the return value
function return_a_string () {
    declare -n ret=$1
    local MYLIB_return_a_string_message="The date is "
    MYLIB_return_a_string_message+=$(date)
    ret=$MYLIB_return_a_string_message
}

and testing this example:

$ return_a_string result; echo $result
The date is 20160817

Note that the bash "declare" builtin, when used in a function, makes the declared variable "local" by default, and "-n" can also be used with "local".

I prefer to distinguish "important declare" variables from "boring local" variables, so using "declare" and "local" in this way acts as documentation.

EDIT 1 - (Response to comment below by Karsten) - I cannot add comments below any more, but Karsten's comment got me thinking, so I did the following test which WORKS FINE, AFAICT - Karsten if you read this, please provide an exact set of test steps from the command line, showing the problem you assume exists, because these following steps work just fine:

$ return_a_string ret; echo $ret
The date is 20170104

(I ran this just now, after pasting the above function into a bash term - as you can see, the result works just fine.)

zenaan
  • 851
  • 6
  • 10
  • 4
    It is my hope that this percolates to the top. eval should be a last resort. Worthy of mention is that nameref variables are only available since bash 4.3 (according to the [changelog](http://git.savannah.gnu.org/cgit/bash.git/plain/CHANGES) ) (released in feb 2014[?]). This is important if portability is a concern. Please cite the bash manual on the fact that `declare` creates local variables inside functions (that info is not given by `help declare`): "...When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied..." – init_js Oct 08 '16 at 07:52
  • 3
    This has the same aliasing problem as the eval solution. When you call a function and pass in the name of the output variable, you have to avoid passing the name of a variable that is used locally within the function you call. That's a major problem in terms of encapsulation, as you can't just add or rename new local variables in a function without if any of the functions callers might want to use that name for the output parameter. – Karsten Jan 03 '17 at 11:23
  • 2
    @Karsten agreed. in both cases (eval and namerefs), you may have to pick a different name. One advantage with the nameref approach over eval is that one doesn't have to deal with escaping strings. Of course, you can always do something like `K=$1; V=$2; eval "$A='$V'";`, but one mistake (e.g. an empty or omitted parameter), and it would be more dangerous. @zenaan the issue raised by @Karsten applies if you choose "message" as the return variable name, instead of "ret". – init_js Mar 05 '17 at 07:12
  • 3
    A function presumably must be designed from the beginning to accept a nameref argument, so the function author should be aware of the possibility of a name collision and can use some typical convention to avoid that. E.g., inside function X, name local variables with convention "X_LOCAL_name". – Ron Burk Apr 08 '17 at 17:54
  • 1
    Unfortunately the version of bash shipped with OSX as of 2021 is 3.2.57. – Dalin Mar 22 '21 at 20:11
37

Like bstpierre above, I use and recommend the use of explicitly naming output variables:

function some_func() # OUTVAR ARG1
{
   local _outvar=$1
   local _result # Use some naming convention to avoid OUTVARs to clash
   ... some processing ....
   eval $_outvar=\$_result # Instead of just =$_result
}

Note the use of quoting the $. This will avoid interpreting content in $result as shell special characters. I have found that this is an order of magnitude faster than the result=$(some_func "arg1") idiom of capturing an echo. The speed difference seems even more notable using bash on MSYS where stdout capturing from function calls is almost catastrophic.

It's ok to send in a local variables since locals are dynamically scoped in bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
  • 497
  • 4
  • 4
  • 4
    This helps me because I like to use multiple echo statements for debugging / logging purposes. The idiom of capturing echo fails since it captures all of them. Thank you! – AnneTheAgile Oct 02 '15 at 20:03
  • **This is the (second-best) proper solution!** Clean, fast, elegant, sensible. – Evi1M4chine Jan 05 '16 at 16:21
  • 1
    +2 for keeping it real. I was about to say. How can so many people ignore combining an `echo` inside of a function, combined with command substitution! – Anthony Rutledge May 18 '19 at 23:50
32

You could also capture the function output:

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

Looks weird, but is better than using global variables IMHO. Passing parameters works as usual, just put them inside the braces or backticks.

chiborg
  • 26,978
  • 14
  • 97
  • 115
  • 13
    apart from the alternative syntax note, isn't this the exact same thing the op already wrote in his own question? – Luca Borrione May 08 '12 at 21:43
  • Process substitution burns CPU unnecessary because `fork` and `stdio` are much costly than string allocation in a process memory. – gavenkoa Jun 16 '21 at 10:57
15

The most straightforward and robust solution is to use command substitution, as other people wrote:

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

The downside is performance as this requires a separate process.

The other technique suggested in this topic, namely passing the name of a variable to assign to as an argument, has side effects, and I wouldn't recommend it in its basic form. The problem is that you will probably need some variables in the function to calculate the return value, and it may happen that the name of the variable intended to store the return value will interfere with one of them:

assign()
{
    local x
    x="Test"
    eval "$1=\$x"
}

assign y # This assigns string "Test" to y, as expected

assign x # This will NOT assign anything to x in this scope
         # because the name "x" is declared as local inside the function

You might, of course, not declare internal variables of the function as local, but you really should always do it as otherwise you may, on the other hand, accidentally overwrite an unrelated variable from the parent scope if there is one with the same name.

One possible workaround is an explicit declaration of the passed variable as global:

assign()
{
    local x
    eval declare -g $1
    x="Test"
    eval "$1=\$x"
}

If name "x" is passed as an argument, the second row of the function body will overwrite the previous local declaration. But the names themselves might still interfere, so if you intend to use the value previously stored in the passed variable prior to write the return value there, be aware that you must copy it into another local variable at the very beginning; otherwise the result will be unpredictable! Besides, this will only work in the most recent version of BASH, namely 4.2. More portable code might utilize explicit conditional constructs with the same effect:

assign()
{
    if [[ $1 != x ]]; then
      local x
    fi
    x="Test"
    eval "$1=\$x"
}

Perhaps the most elegant solution is just to reserve one global name for function return values and use it consistently in every function you write.

Tomasz Żuk
  • 1,288
  • 1
  • 12
  • 14
  • 4
    This ^^^. The inadvertent aliasing that breaks encapsulation is the big problem with both the `eval` and `declare -n` solutions. The workaround of having a single dedicated variable name like `result` for all output parameters seems the only solution that doesn't require a functions to know all it's callers to avoid conflicts. – Karsten Jan 03 '17 at 11:31
14

As previously mentioned, the "correct" way to return a string from a function is with command substitution. In the event that the function also needs to output to console (as @Mani mentions above), create a temporary fd in the beginning of the function and redirect to console. Close the temporary fd before returning your string.

#!/bin/bash
# file:  func_return_test.sh
returnString() {
    exec 3>&1 >/dev/tty
    local s=$1
    s=${s:="some default string"}
    echo "writing directly to console"
    exec 3>&-     
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

executing script with no params produces...

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]

hope this helps people

-Andy

Adam Gordon Bell
  • 3,083
  • 2
  • 26
  • 53
Andy
  • 149
  • 1
  • 2
  • 6
    That has its uses, but on the whole you should avoid making an explicit redirect to the console; the output may already be redirected, or the script may be running in a context where no tty exists. You could get around that by duplicating `3>&1` at the head of the script, then manipulating `&1` `&3` and another placeholder `&4` within the function. Ugly all round, though. – jmb Mar 13 '14 at 11:40
  • if you are sure about no errors will be sent to stderr then you might even "abuse" the stderr channel. details for a solution are up to you. – Alexander Stohr Mar 16 '23 at 10:08
8

You could use a global variable:

declare globalvar='some string'

string ()
{
  eval  "$1='some other string'"
} # ----------  end of function string  ----------

string globalvar

echo "'${globalvar}'"

This gives

'some other string'
Fritz G. Mehner
  • 16,550
  • 2
  • 34
  • 41
6

To illustrate my comment on Andy's answer, with additional file descriptor manipulation to avoid use of /dev/tty:

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=$1
    s=${s:="some default string"}
    echo "writing to stdout"
    echo "writing to stderr" >&2
    exec >&4-
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

Still nasty, though.

jmb
  • 363
  • 2
  • 8
4

The way you have it is the only way to do this without breaking scope. Bash doesn't have a concept of return types, just exit codes and file descriptors (stdin/out/err, etc)

Daenyth
  • 35,856
  • 13
  • 85
  • 124
3

Addressing Vicky Ronnen's head up, considering the following code:

function use_global
{
    eval "$1='changed using a global var'"
}

function capture_output
{
    echo "always changed"
}

function test_inside_a_func
{
    local _myvar='local starting value'
    echo "3. $_myvar"

    use_global '_myvar'
    echo "4. $_myvar"

    _myvar=$( capture_output )
    echo "5. $_myvar"
}

function only_difference
{
    local _myvar='local starting value'
    echo "7. $_myvar"

    local use_global '_myvar'
    echo "8. $_myvar"

    local _myvar=$( capture_output )
    echo "9. $_myvar"
}

declare myvar='global starting value'
echo "0. $myvar"

use_global 'myvar'
echo "1. $myvar"

myvar=$( capture_output )
echo "2. $myvar"

test_inside_a_func
echo "6. $_myvar" # this was local inside the above function

only_difference



will give

0. global starting value
1. changed using a global var
2. always changed
3. local starting value
4. changed using a global var
5. always changed
6. 
7. local starting value
8. local starting value
9. always changed

Maybe the normal scenario is to use the syntax used in the test_inside_a_func function, thus you can use both methods in the majority of cases, although capturing the output is the safer method always working in any situation, mimicking the returning value from a function that you can find in other languages, as Vicky Ronnen correctly pointed out.

Community
  • 1
  • 1
Luca Borrione
  • 16,324
  • 8
  • 52
  • 66
3

The options have been all enumerated, I think. Choosing one may come down to a matter of the best style for your particular application, and in that vein, I want to offer one particular style I've found useful. In bash, variables and functions are not in the same namespace. So, treating the variable of the same name as the value of the function is a convention that I find minimizes name clashes and enhances readability, if I apply it rigorously. An example from real life:

UnGetChar=
function GetChar() {
    # assume failure
    GetChar=
    # if someone previously "ungot" a char
    if ! [ -z "$UnGetChar" ]; then
        GetChar="$UnGetChar"
        UnGetChar=
        return 0               # success
    # else, if not at EOF
    elif IFS= read -N1 GetChar ; then
        return 0           # success
    else
        return 1           # EOF
    fi
}

function UnGetChar(){
    UnGetChar="$1"
}

And, an example of using such functions:

function GetToken() {
    # assume failure
    GetToken=
    # if at end of file
    if ! GetChar; then
        return 1              # EOF
    # if start of comment
    elif [[ "$GetChar" == "#" ]]; then
        while [[ "$GetChar" != $'\n' ]]; do
            GetToken+="$GetChar"
            GetChar
        done
        UnGetChar "$GetChar"
    # if start of quoted string
    elif [ "$GetChar" == '"' ]; then
# ... et cetera

As you can see, the return status is there for you to use when you need it, or ignore if you don't. The "returned" variable can likewise be used or ignored, but of course only after the function is invoked.

Of course, this is only a convention. You are free to fail to set the associated value before returning (hence my convention of always nulling it at the start of the function) or to trample its value by calling the function again (possibly indirectly). Still, it's a convention I find very useful if I find myself making heavy use of bash functions.

As opposed to the sentiment that this is a sign one should e.g. "move to perl", my philosophy is that conventions are always important for managing the complexity of any language whatsoever.

Ron Burk
  • 6,058
  • 1
  • 18
  • 20
2

In my programs, by convention, this is what the$REPLY variable is for, which read uses for that exact purpose.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

By default (unless one uses the local keyword), bash has the strange behavior of making all variables global to an entire script (at least anything running after it was created). Even if they are created inside the scope/closure of a function. So this echoes

tadaa

I prefer $REPLY, as it is also used by read and select, so it might as well be predefined and people are probably less likely to use it for other purposes. But to avoid conflicts, any other global variable will do.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

declareing the variable prior to usage also keeps this working if bash ever decides to fix this (to me badly designed) behavior. exporting will extend its scope even to other processes called by your script.

If that isn’t enough, I recommend Markarian451’s solution.

Evi1M4chine
  • 6,992
  • 1
  • 24
  • 18
  • After going back and forth between different solutions mentioned here: the 'REPLY' solution is the way to go. No boilerplate, no extra vars, adapting stuff already there – just plain simple. +1 for that! – NickSdot May 17 '22 at 06:22
  • REPLY is not existing for me - its a Ubuntu 16.04 system. no idea where/why you claim to have those variable - its not a bash standard, i think. – Alexander Stohr Mar 16 '23 at 12:30
  • 1
    @AlexanderStohr: Interesting… You are indeed correct. It is not. `$REPLY` is used by `read` and `select` to return values, according to `man bash`. Plus, strangely, it still works in my example, and the variable is leaking out of the context of the function. That’s why I assumed it must have been pre-defined. :) … I will fix the answer. – Evi1M4chine Mar 20 '23 at 02:54
  • export is only exporting to called sub-shells/scripts. thus excluding all calling scripts and other caller contexts. – Alexander Stohr Mar 20 '23 at 15:38
2

They key problem of any 'named output variable' scheme where the caller can pass in the variable name (whether using eval or declare -n) is inadvertent aliasing, i.e. name clashes: From an encapsulation point of view, it's awful to not be able to add or rename a local variable in a function without checking ALL the function's callers first to make sure they're not wanting to pass that same name as the output parameter. (Or in the other direction, I don't want to have to read the source of the function I'm calling just to make sure the output parameter I intend to use is not a local in that function.)

The only way around that is to use a single dedicated output variable like REPLY (as suggested by Evi1M4chine) or a convention like the one suggested by Ron Burk.

However, it's possible to have functions use a fixed output variable internally, and then add some sugar over the top to hide this fact from the caller, as I've done with the call function in the following example. Consider this a proof of concept, but the key points are

  • The function always assigns the return value to REPLY, and can also return an exit code as usual
  • From the perspective of the caller, the return value can be assigned to any variable (local or global) including REPLY (see the wrapper example). The exit code of the function is passed through, so using them in e.g. an if or while or similar constructs works as expected.
  • Syntactically the function call is still a single simple statement.

The reason this works is because the call function itself has no locals and uses no variables other than REPLY, avoiding any potential for name clashes. At the point where the caller-defined output variable name is assigned, we're effectively in the caller's scope (technically in the identical scope of the call function), rather than in the scope of the function being called.

#!/bin/bash
function call() { # var=func [args ...]
  REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?"
}

function greet() {
  case "$1" in
    us) REPLY="hello";;
    nz) REPLY="kia ora";;
    *) return 123;;
  esac
}

function wrapper() {
  call REPLY=greet "$@"
}

function main() {
  local a b c d
  call a=greet us
  echo "a='$a' ($?)"
  call b=greet nz
  echo "b='$b' ($?)"
  call c=greet de
  echo "c='$c' ($?)"
  call d=wrapper us
  echo "d='$d' ($?)"
}
main

Output:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)
Community
  • 1
  • 1
Karsten
  • 310
  • 1
  • 6
2

Although there were a lot of good answers, they all did not work the way I wanted them to. So here is my solution with these key points:

Helping the forgetful programmer

Atleast I would struggle to always remember error checking after something like this: var=$(myFunction)

Allows assigning values with newline chars \n

Some solutions do not allow for that as some forgot about the single quotes around the value to assign. Right way: eval "${returnVariable}='${value}'" or even better: see the next point below.

Using printf instead of eval

Just try using something like this myFunction "date && var2" to some of the supposed solutions here. eval will execute whatever is given to it. I only want to assign values so I use printf -v "${returnVariable}" "%s" "${value}" instead.

Encapsulation and protection against variable name collision

If a different user or at least someone with less knowledge about the function (this is likely me in some months time) is using myFunction I do not want them to know that he must use a global return value name or some variable names are forbidden to use. That is why I added a name check at the top of myFunction:

    if [[ "${1}" = "returnVariable" ]]; then
        echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!"
        echo "If that is still what you want to do please do that outside of myFunction()!"
        return 1
    fi

Note this could also be put into a function itself if you have to check a lot of variables. If I still want to use the same name (here: returnVariable) I just create a buffer variable, give that to myFunction and then copy the value returnVariable.

So here it is:

myFunction():

myFunction() {
    if [[ "${1}" = "returnVariable" ]]; then
        echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!"
        echo "If that is still what you want to do please do that outside of myFunction()!"
        return 1
    fi
    if [[ "${1}" = "value" ]]; then
        echo "Cannot give the ouput to \"value\" as a variable with the same name is used in myFunction()!"
        echo "If that is still what you want to do please do that outside of myFunction()!"
        return 1
    fi
    local returnVariable="${1}"
    local value=$'===========\nHello World\n==========='
    echo "setting the returnVariable now..."
    printf -v "${returnVariable}" "%s" "${value}"
}

Test cases:

var1="I'm not greeting!"
myFunction var1
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var1:\n%s\n" "${var1}"

# Output:
# setting the returnVariable now...
# myFunction(): SUCCESS
# var1:
# ===========
# Hello World
# ===========
returnVariable="I'm not greeting!"
myFunction returnVariable
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "returnVariable:\n%s\n" "${returnVariable}"

# Output
# Cannot give the ouput to "returnVariable" as a variable with the same name is used in myFunction()!
# If that is still what you want to do please do that outside of myFunction()!
# myFunction(): FAILURE
# returnVariable:
# I'm not greeting!
var2="I'm not greeting!"
myFunction "date && var2"
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var2:\n%s\n" "${var2}"

# Output
# setting the returnVariable now...
# ...myFunction: line ..: printf: `date && var2': not a valid identifier
# myFunction(): FAILURE
# var2:
# I'm not greeting!
myFunction var3
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var3:\n%s\n" "${var3}"

# Output
# setting the returnVariable now...
# myFunction(): SUCCESS
# var3:
# ===========
# Hello World
# ===========
TimFinnegan
  • 583
  • 5
  • 17
1

You can echo a string, but catch it by piping (|) the function to something else.

You can do it with expr, though ShellCheck reports this usage as deprecated.

Will
  • 24,082
  • 14
  • 97
  • 108
apennebaker
  • 681
  • 2
  • 8
  • 17
  • Trouble is that the thing to the right of the pipe is a subshell. So `myfunc | read OUTPUT ; echo $OUTPUT` yields nothing. `myfunc | ( read OUTPUT; echo $OUTPUT )` does get the expected value and clarifies what is happening on the right-hand-side. But of course OUTPUT is not then available where you need it... – Ed Randall Mar 26 '15 at 11:25
1

bash pattern to return both scalar and array value objects:

definition

url_parse() { # parse 'url' into: 'url_host', 'url_port', ...
   local "$@" # inject caller 'url' argument in local scope
   local url_host="..." url_path="..." # calculate 'url_*' components
   declare -p ${!url_*} # return only 'url_*' object fields to the caller
}

invocation

main() { # invoke url parser and inject 'url_*' results in local scope
   eval "$(url_parse url=http://host/path)" # parse 'url'
   echo "host=$url_host path=$url_path" # use 'url_*' components
}
Andrei Pozolotin
  • 897
  • 3
  • 14
  • 21
0

#Implement a generic return stack for functions:

STACK=()
push() {
  STACK+=( "${1}" )
}
pop() {
  export $1="${STACK[${#STACK[@]}-1]}"
  unset 'STACK[${#STACK[@]}-1]';
}

#Usage:

my_func() {
  push "Hello world!"
  push "Hello world2!"
}
my_func ; pop MESSAGE2 ; pop MESSAGE1
echo ${MESSAGE1} ${MESSAGE2}
loop
  • 825
  • 6
  • 15
0

basing some lines upon what user zenaan had once published: https://stackoverflow.com/a/38997681/3423146

he is copy & pasting his codes to the console - thus using the console environment as a mid-term storage for his codes:

$ function return_a_string () {
>     declare -n ret=$1
>     local MYLIB_return_a_string_message="The date is "
>     MYLIB_return_a_string_message+=$(date)
>     ret=$MYLIB_return_a_string_message
> }

so he can do things like that on the console:

$ declare -f return_a_string
return_a_string () 
{ 
    declare -n ret=$1;
    local MYLIB_return_a_string_message="The date is ";
    MYLIB_return_a_string_message+=$(date);
    ret=$MYLIB_return_a_string_message
}

$ return_a_string result_date; echo $result_date
The date is Do 16. Mär 14:29:04 CET 2023

$ unset -f return_a_string

$ declare -f return_a_string

$

unlike him demonstrating it there is the chance of doing a real set of on-demand library integration for shell scripts that will return string value as well (that still differs somewhat from handing over string results between individual shell scripts) by the means of the 'source' command or abbreviated by '.' to jump across the limit imposed by codes in files.

see here an example for a similar library with even test/demo codes included (in a style you might use it within e.g. python scripts). these file might be called MYLIB.bash:

#!/usr/bin/env bash

function MYLIB_get_current_date_string () {
    # $1 : string; your variable to contain the return value
    declare -n MYLIB_ret=$1

    local MYLIB_get_current_date_string_message="The date is "
    MYLIB_get_current_date_string_message+=$(date)

    MYLIB_ret=$MYLIB_get_current_date_string_message
}

function MYLIB_get_current_date_string_test () {
    local MYLIB_current_date="<unknown>"
    echo "MYLIB_current_date=$MYLIB_current_date"
    MYLIB_get_current_date_string MYLIB_current_date
    echo "MYLIB_current_date=$MYLIB_current_date"
}

MYLIB_base_name=${0##*/}
if [ "$MYLIB_base_name" == "MYLIB.bash" ]; then
    echo "__main__ begin ($MYLIB_base_name)"

    # some test/demo codes:
    MYLIB_get_current_date_string_test

    echo "__main__ end ($MYLIB_base_name)"
fi

# ### EOF ###

and find here a different piece of shell scripting that is making use of the function that is returning a string, as found in the above library. these file might be called MYLIB_app.bash:

#!/usr/bin/env bash
base_name=${0##*/}
echo "begin ($base_name)"

. ./MYLIB.bash

MYLIB_get_current_date_string current_date
echo current_date=$current_date

echo "end ($base_name)"
# ### EOF ###

If you run the first file, you will get outputs like these:

$ ./MYLIB.bash
__main__ begin (MYLIB.bash)
MYLIB_current_date=<unknown>
MYLIB_current_date=The date is Do 16. Mär 14:38:29 CET 2023
__main__ end (MYLIB.bash)

If you run the second file, you will get outputs like these:

$ ./MYLIB_app.bash
begin (MYLIB_app.bash)
current_date=The date is Do 16. Mär 14:39:12 CET 2023
end (MYLIB_app.bash)
Alexander Stohr
  • 159
  • 1
  • 18
-2
agt@agtsoft:~/temp$ cat ./fc 
#!/bin/sh

fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall'

function f1 {
    res=$[($1+$2)*2];
}

function f2 {
    local a;
    eval ${fcall//fname/f1} a 2 3;
    echo f2:$a;
}

a=3;
f2;
echo after:a=$a, res=$res

agt@agtsoft:~/temp$ ./fc
f2:10
after:a=3, res=
agtsoft
  • 11