188

I want to return the value from a function called in a shell script. Perhaps I am missing the syntax. I tried using the global variables. But that is also not working. The code is:

lockdir="somedir"
test() {
    retval=""

    if mkdir "$lockdir"
        then    # Directory did not exist, but it was created successfully
            echo >&2 "successfully acquired lock: $lockdir"
            retval="true"
        else
            echo >&2 "cannot acquire lock, giving up on $lockdir"
            retval="false"
    fi
    return retval
}


retval=test()
if [ "$retval" == "true" ]
    then
        echo "directory not created"
    else
        echo "directory already created"
fi
codeforester
  • 39,467
  • 16
  • 112
  • 140
Mridul Vishal
  • 2,160
  • 4
  • 16
  • 17

5 Answers5

389

A Bash function can't return a string directly like you want it to. You can do three things:

  1. Echo a string
  2. Return an exit status, which is a number, not a string
  3. Share a variable

This is also true for some other shells.

Here's how to do each of those options:

1. Echo strings

lockdir="somedir"
testlock(){
    retval=""
    if mkdir "$lockdir"
    then # Directory did not exist, but it was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval="true"
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval="false"
    fi
    echo "$retval"
}

retval=$( testlock )
if [ "$retval" == "true" ]
then
     echo "directory not created"
else
     echo "directory already created"
fi

2. Return exit status

lockdir="somedir"
testlock(){
    if mkdir "$lockdir"
    then # Directory did not exist, but was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval=0
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval=1
    fi
    return "$retval"
}

testlock
retval=$?
if [ "$retval" == 0 ]
then
     echo "directory not created"
else
     echo "directory already created"
fi

3. Share variable

lockdir="somedir"
retval=-1
testlock(){
    if mkdir "$lockdir"
    then # Directory did not exist, but it was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval=0
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval=1
    fi
}

testlock
if [ "$retval" == 0 ]
then
     echo "directory not created"
else
     echo "directory already created"
fi
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
oHo
  • 51,447
  • 27
  • 165
  • 200
  • 5
    Don't use a `function` keyword to define a bash function. That would make it less portable. Removing it. – dimir Jan 05 '12 at 13:37
  • 2
    In your third example, retval is not an environment variable. It is merely a shell variable. It will only become an environment variable if you export it. Perhaps the title of the third example should be "global variable" instead of "environment variable". – William Pursell Jan 05 '12 at 16:24
  • 4
    In the second example, rather than assigning from $?, it is more idiomatic to write "if testlock; then ..." – William Pursell Jan 05 '12 at 16:25
  • @WilliamPursell I have removed wrong 'environment' word. Let's keep "$?" for pedagogical purpose. I have enabled Wiki community, so you are all free to improve the answer ;-) – oHo Jan 05 '12 at 16:59
  • The int result is modulo 256 if you use "2. return exit status". Can't use the result method for pids – ljdelight Oct 16 '15 at 22:11
  • Why within each method/function about `echo` is the `echo >&2 "something"` pattern used and not just `echo > "something"`? - So Why is mandatory use `>&2`? - According with my understanding `>&2` is about `stderr` .. But I see that `>&2` is used even within the `if` scope. – Manuel Jordan Apr 25 '20 at 20:42
  • 1
    @ManuelJordan, Functions can only return exit codes and >&2 logs to stderror, so, the last echo is written to stdout, so, the calling function ONLY captures stdout and not stderr. Assuming execution is single threaded, a better option is to maintain a custom variable specific like TEST_LOCK_STATUS="" outside method that anyone can use after calling testlock and reset it each time at the start of the method – kisna May 23 '20 at 20:39
  • @dimir: For maximum portability, shouldn't () be surrounded by spaces? Just wondering - I'm not sure it could be an issue but I've always seen it with spaces in the doc, never seen `myfunct(){...}` without spaces. – Max Jul 11 '20 at 00:01
  • @max Not sure. I thought it's the matter of taste. But yeah, I've seen different versions. I personally prefer `myfunct() {...}` as it looks similar to most of the other languages. – dimir Jul 11 '20 at 08:48
  • How can I make it work when I have to pass argument to my function? The approach suggested doesn't work. – Jaraws Nov 11 '20 at 11:43
  • @Max, I've never heard of it being a portability issue. The reason I prefer the space-less version (as opposed to e.g. @dimir) is exactly that it differs from the usual style used with real functions in real languages, so my brain can adjust a little bit easier to the "quirks mode" of shell scripting. – Sz. Jan 30 '23 at 22:41
19

You are working way too hard. Your entire script should be:

if mkdir "$lockdir" 2> /dev/null; then 
  echo lock acquired
else
  echo could not acquire lock >&2
fi

but even that is probably too verbose. I would code it:

mkdir "$lockdir" || exit 1

but the resulting error message is a bit obscure.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 1
    The missing error message is easy enough to fix, even though it's slightly more verbose: `mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }` (note the `;` before the closing curly brace). Also, I often define a fail function that takes an optional message parameter which it prints to stderr and then exits with return code 1, enabling me to use the more readable `mkdir "$lockdir" || fail "could not create lock dir"`. – blubberdiblub Dec 11 '16 at 11:52
  • @blubberdiblub: but the fail function can't exit the "current" function or script, can it? so you'd have to use `cmd || fail "error msg" || return 1` if you wish to do that, is it ? – Max Jul 11 '20 at 00:06
  • @Max not the current function, that is correct. But it will exit the current script, as long as you called it as a command and **didn't source** it. I usually think of such a `fail` function as used for fatal situations only. – blubberdiblub Jul 15 '20 at 05:24
  • 1
    @blubberdiblub what is the purpose of that last `;` before the closing curly brace? What would happen if I missed it? – SasQ Sep 05 '20 at 05:15
  • 2
    @SasQ If you omit the trailing `;` before the `}`, it is a syntax error. The command needs to be terminated before the closing brace. – William Pursell Sep 07 '20 at 17:07
  • @SasQ consider `then` a separate command from `if` in shell scripts. Separate commands need to be delimited by semicolon (or some other stuff I won't mention here) or newlines. Therefore you could also put then `then` at the beginning of the next line instead, which would allow you to omit the semicolon. – blubberdiblub Sep 10 '20 at 17:36
18

If it's just a true/false test, have your function return 0 for success, and return 1 for failure. The test would then be:

if function_name; then
  do something
else
  error condition
fi
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
8

In case you have some parameters to pass to a function and want a value in return. Here I am passing "12345" as an argument to a function and after processing returning variable XYZ which will be assigned to VALUE

#!/bin/bash
getValue()
{
    ABC=$1
    XYZ="something"$ABC
    echo $XYZ
}


VALUE=$( getValue "12345" )
echo $VALUE

Output:

something12345
LOrD_ARaGOrN
  • 3,884
  • 3
  • 27
  • 49
2

I think returning 0 for succ/1 for fail (glenn jackman) and olibre's clear and explanatory answer says it all; just to mention a kind of "combo" approach for cases where results are not binary and you'd prefer to set a variable rather than "echoing out" a result (for instance if your function is ALSO suppose to echo something, this approach will not work). What then? (below is Bourne Shell)

# Syntax _w (wrapReturn)
# arg1 : method to wrap
# arg2 : variable to set
_w(){
eval $1
read $2 <<EOF
$?
EOF
eval $2=\$$2
}

as in (yep, the example is somewhat silly, it's just an.. example)

getDay(){
  d=`date '+%d'`
  [ $d -gt 255 ] && echo "Oh no a return value is 0-255!" && BAIL=0 # this will of course never happen, it's just to clarify the nature of returns
  return $d
}

dayzToSalary(){
  daysLeft=0
  if [ $1 -lt 26 ]; then 
      daysLeft=`expr 25 - $1`
  else
     lastDayInMonth=`date -d "`date +%Y%m01` +1 month -1 day" +%d`
     rest=`expr $lastDayInMonth - 25`
     daysLeft=`expr 25 + $rest`
  fi
  echo "Mate, it's another $daysLeft days.."
}

# main
_w getDay DAY # call getDay, save the result in the DAY variable
dayzToSalary $DAY
Ola Aronsson
  • 411
  • 4
  • 7