59

I know some very basic commands in Linux and am trying to write some scripts. I have written a function which evaluates the sum of last 2-digits in a 5-digit number. The function should concatenate this resultant sum in between the last 2-digits and return it. The reason I want to return this value is because I will be using this value in the other function.

Ex: if I have 12345, then my function will calculate 4+5 and return 495.

#!/bin/bash

set -x
echo "enter: "
        read input

function password_formula
{
        length=${#input}
        last_two=${input:length-2:length}
        first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
        second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
        let sum=$first+$second
        sum_len=${#sum}
        echo $second
        echo $sum

        if [ $sum -gt 9 ]
        then
               sum=${sum:1}
        fi

        value=$second$sum$first
        return $value
}
result=$(password_formula)
echo $result

I am trying to echo and see the result but I am getting the output as shown below.

-bash-3.2$ ./file2.sh 
+++ password_formula
+++ echo 'enter: '
+++ read input
12385
+++ length=8
+++ last_two=85
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $2}'
+++ first=5
++++ echo 85
++++ sed -e 's/\(.\)/\1 /g'
++++ awk '{print $1}'
+++ second=8
+++ let sum=5+8
+++ sum_len=2
+++ echo 5
+++ echo 8
+++ echo 13
+++ '[' 13 -gt 9 ']'
+++ sum=3
+++ value=835
+++ return 835
++ result='enter: 
5
8
13'
++ echo enter: 5 8 13
enter: 5 8 13

I also tried to print the result as:

password_formula
RESULT=$?
echo $RESULT

But that is giving some unknown value:

++ RESULT=67
++ echo 67
67

How can I properly store the correct value and print (to double check) on the screen?

starball
  • 20,030
  • 7
  • 43
  • 238
itsh
  • 1,063
  • 3
  • 14
  • 28
  • The use of backticks here seems out of place. You could do `first=${last_two:0:1}` and `second=${last_two:1:1}`, or `first=${last_two%?}` and `second=${last_two#?}` – William Pursell Feb 21 '13 at 23:18
  • 1
    the "return" statement is misleadingly named for those accustomed to high-level languages - a better name might be "exit_status". If you want to return a result consisting of a string value or numeric value >255 then it must be done by "echo" or other means of outputting to stdout. – MikeW Feb 19 '18 at 14:38

8 Answers8

48

Simplest answer:

the return code from a function can be only a value in the range from 0 to 255 . To store this value in a variable you have to do like in this example:

#!/bin/bash

function returnfunction {
    # example value between 0-255 to be returned 
    return 23
}

# note that the value has to be stored immediately after the function call :
returnfunction
myreturnvalue=$?

echo "myreturnvalue is "$myreturnvalue
Samuele Catuzzi
  • 610
  • 6
  • 7
  • 2
    This is the only correct answer here. The top-rated answer changing the function from return to echo might work in this specific case, but is of course not the desired answer most readers here are looking for. – Andreas May 20 '19 at 11:48
34

The return value (aka exit code) is a value in the range 0 to 255 inclusive. It's used to indicate success or failure, not to return information. Any value outside this range will be wrapped.

To return information, like your number, use

echo "$value"

To print additional information that you don't want captured, use

echo "my irrelevant info" >&2 

Finally, to capture it, use what you did:

 result=$(password_formula)

In other words:

echo "enter: "
        read input

password_formula()
{
        length=${#input}
        last_two=${input:length-2:length}
        first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
        second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
        let sum=$first+$second
        sum_len=${#sum}
        echo $second >&2
        echo $sum >&2

        if [ $sum -gt 9 ]
        then
               sum=${sum:1}
        fi

        value=$second$sum$first
        echo $value
}
result=$(password_formula)
echo "The value is $result"
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • The problem is I need to use stdout not stderr. – Tomáš Zato Apr 21 '15 at 19:18
  • @Eric For the reasons outlined in this post, you can not use `$?` to recover a return value like OP's 495. Even if your value does fit in the limited range of an exit code, reusing it for this purpose violates [posix conventions](https://pubs.opengroup.org/onlinepubs/9699919799/functions/_Exit.html), and confuses other tools that expect the exit code to be a success indicator – that other guy Aug 03 '19 at 21:53
  • Sorry I didn't read the initial question, just the title :) I Didn't realize he asked how to pass values larger than 255. The correct answer is it's impossible by design, but there are workarounds like using stdout. – Eric Aug 04 '19 at 10:12
  • While it is true that a return value can't store >255 and so your answer does meet the needs of the asker in context of his explanation, it fails to actually answer the question that was asked. By far more people will (and have) come to this since it was asked to look for the answer of the actual question than to solve the problem the asker actually wanted solved. For this reason I am downvoting this answer. If you edit your answer to actually also answer the asked question I will change this. – Kurt Fitzner Dec 27 '20 at 15:23
  • Thank you! This was super helpful. – Felipe Aug 19 '21 at 15:39
15

It is easy you need to echo the value you need to return and then capture it like below

demofunc(){
    local variable="hellow"
    echo $variable    
}

val=$(demofunc)
echo $val
Imal Hasaranga Perera
  • 9,683
  • 3
  • 51
  • 41
  • 1
    This should be the accepted answer. The current one is incomplete and incorrect grammar wise. – Buffalo Apr 10 '17 at 07:26
  • I agree. This one is the easiest to use. – Justin Putney Jan 09 '18 at 00:54
  • 2
    If you add `echo 555` or anything like that before `echo $variable`, this will concatenate with `$variable` which is bad – Groosha Feb 06 '18 at 16:48
  • 1
    Note that `$(demofunc)` executes `demofunc` in a subshell. `echo`'s in `demofunc` will not show in the current shell. So I think the better answer is https://stackoverflow.com/a/35223671/1803897 – Roland Puntaier Jun 27 '19 at 11:30
14

Use the special bash variable "$?" like so:

function_output=$(my_function)
function_return_value=$?
Ytsen de Boer
  • 2,797
  • 2
  • 25
  • 36
6

The answer above suggests changing the function to echo data rather than return it so that it can be captured.

For a function or program that you can't modify where the return value needs to be saved to a variable (like test/[, which returns a 0/1 success value), echo $? within the command substitution:

# Test if we're remote.
isRemote="$(test -z "$REMOTE_ADDR"; echo $?)"
# Or:
isRemote="$([ -z "$REMOTE_ADDR" ]; echo $?)"

# Additionally you may want to reverse the 0 (success) / 1 (error) values
# for your own sanity, using arithmetic expansion:
remoteAddrIsEmpty="$([ -z "$REMOTE_ADDR" ]; echo $((1-$?)))"

E.g.

$ echo $REMOTE_ADDR

$ test -z "$REMOTE_ADDR"; echo $?
0
$ REMOTE_ADDR=127.0.0.1
$ test -z "$REMOTE_ADDR"; echo $?
1
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
1
$ unset REMOTE_ADDR
$ retval="$(test -z "$REMOTE_ADDR"; echo $?)"; echo $retval
0

For a program which prints data but also has a return value to be saved, the return value would be captured separately from the output:

# Two different files, 1 and 2.
$ cat 1
1
$ cat 2
2
$ diffs="$(cmp 1 2)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [1] Diffs: [1 2 differ: char 1, line 1]
$ diffs="$(cmp 1 1)"
$ haveDiffs=$?
$ echo "Have differences? [$haveDiffs] Diffs: [$diffs]"
Have differences? [0] Diffs: []

# Or again, if you just want a success variable, reverse with arithmetic expansion:
$ cmp -s 1 2; filesAreIdentical=$((1-$?))
$ echo $filesAreIdentical
0
Calpau
  • 921
  • 10
  • 21
1

It's due to the echo statements. You could switch your echos to prints and return with an echo. Below works

#!/bin/bash

set -x
echo "enter: "
read input

function password_formula
{
        length=${#input}
        last_two=${input:length-2:length}
        first=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $2}'`
        second=`echo $last_two| sed -e 's/\(.\)/\1 /g'|awk '{print $1}'`
        let sum=$first+$second
        sum_len=${#sum}
        print $second
        print $sum

        if [ $sum -gt 9 ]
        then
           sum=${sum:1}
        fi

        value=$second$sum$first
        echo $value
}
result=$(password_formula)
echo $result
David Newcomb
  • 10,639
  • 3
  • 49
  • 62
Kat Farrell
  • 340
  • 1
  • 2
  • 6
  • Thanks a lot to all of you for the detail explanations. It works now. – itsh Feb 21 '13 at 22:59
  • The question is how to get the **return value**, not the `echo`ed value. – Tomáš Zato Apr 21 '15 at 19:28
  • it is how to return a value, not a status code. Using return returns a status code. Trying to overload that as a value is error prone due to the restrictions on status code values – Daniel Holmes Oct 19 '21 at 15:35
0

Something like this could be used, and still maintaining meanings of return (to return control signals) and echo (to return information) and logging statements (to print debug/info messages).

v_verbose=1
v_verbose_f=""         # verbose file name
FLAG_BGPID=""

e_verbose() {
        if [[ $v_verbose -ge 0 ]]; then
                v_verbose_f=$(tempfile)
                tail -f $v_verbose_f &
                FLAG_BGPID="$!"
        fi
}

d_verbose() {
        if [[ x"$FLAG_BGPID" != "x" ]]; then
                kill $FLAG_BGPID > /dev/null
                FLAG_BGPID=""
                rm -f $v_verbose_f > /dev/null
        fi
}

init() {
        e_verbose

        trap cleanup SIGINT SIGQUIT SIGKILL SIGSTOP SIGTERM SIGHUP SIGTSTP
}

cleanup() {
        d_verbose
}

init

fun1() {
    echo "got $1" >> $v_verbose_f
    echo "got $2" >> $v_verbose_f
    echo "$(( $1 + $2 ))"
    return 0
}

a=$(fun1 10 20)
if [[ $? -eq 0 ]]; then
    echo ">>sum: $a"
else
    echo "error: $?"
fi
cleanup

In here, I'm redirecting debug messages to separate file, that is watched by tail, and if there is any changes then printing the change, trap is used to make sure that background process always ends.

This behavior can also be achieved using redirection to /dev/stderr, But difference can be seen at the time of piping output of one command to input of other command.

jiwopene
  • 3,077
  • 17
  • 30
dinesh saini
  • 431
  • 5
  • 8
0

Ok the main answers to this are problematic if we have errexit set, e.g.

#!/bin/bash
set -o errexit

my_fun() {
    # returns 0 if the first arguments is "a"
    # 1 otherwise
    [ "${1}" = "a" ]
}

my_fun "a"
echo "a=$?"
my_fun "b"
echo "b=$?"

In this case bash just exit when the result is not 0, e.g. this only prints the a line.

./test_output.sh 
a=0

As already said well here probably the most correct answer is something like this:

# like this
my_val=0 ; my_fun "a" || my_val=$?
echo "a=${my_val}"

# or this
my_fun "b" && my_val=0 || my_val=$?
echo "b=${my_val}"

This print all the values correctly without error

a=0
b=1

I don't know if the "echo" implementation is the most correct, as I still is not clear to me if $() creates a subshell or not. I have a function for example that opens up a file descriptor in a function and return the number and it seems that bash closes the fd after exiting the function. (if someone can help me here :-)