ChatGPT解决这个技术问题 Extra ChatGPT

Returning value from called function in a shell script

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
Not related to your question, but anyway... if you are trying to get a lock you may use "lockfile" command.

9
9 revs, 4 users 86%

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

Echo a string Return an exit status, which is a number, not a string 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

Don't use a function keyword to define a bash function. That would make it less portable. Removing it.
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".
In the second example, rather than assigning from $?, it is more idiomatic to write "if testlock; then ..."
@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 ;-)
@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
g
glenn jackman

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

Exactly what I was looking for.
Is there a way to use this notation as well for parameterized functions ?
@alex can you give an example of what you mean by "parameterized function"?
'myCopyFunc ${SOURCE} ${DEST}', return 0 on success. E.g. like in this issue: stackoverflow.com/questions/6212219/…
Yes, that's perfectly fine
W
William Pursell

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.


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: 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 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 what is the purpose of that last ; before the closing curly brace? What would happen if I missed it?
@SasQ If you omit the trailing ; before the }, it is a syntax error. The command needs to be terminated before the closing brace.
R
Rishi Bansal

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

O
Ola Aronsson

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