11

I need a bash function to return a dynamically constructed string to returned to the caller space. i.e,

makeName()
{
    echo "Enter Ext: "
    read ext
    return "$fileName.$1.$ext.log"
}


echo -n "Enter fileName:"
read fileName

name1=makeName "type1"
name2=makfName "type2"

So I can get two different filenames with same base file name. I tries doing like this,

# echo $(makeName "type1")

But this code is struck somewhere with out any reason or error. I expect it to accept some kind of I/O with that proc also. That is not happening.

Ashwin
  • 993
  • 1
  • 16
  • 41

3 Answers3

22

The return statement used by bash is used to return a numeric value as a status code to be retrieved through $? by the calling function. You can not return a string. See also

You can either use a special global variable as proposed by @konsolebox, or echo the return value inside your function, and use command substitution when calling the function:

makeName()
{
    echo "$fileName.$1.log"
}


echo -n "Enter fileName:"
read fileName

name1=$(makeName "type1")
name2=$(makeName "type2")

echo $name1
echo $name2

[UPDATE]

The updated question shows that you intend to read yet another value inside the makeName function, while the function also intends to echo some prompt for the user. So the command substitution approach will not work in that case - you need to use a global variable, like

makeName() {
    echo -n "Enter Ext: "
    read ext

    __="$fileName.$1.$ext.log"
}


echo -n "Enter fileName:"
read fileName

makeName "type1" ; name1=${__}
makeName "type2" ; name2=${__}

echo $name1
echo $name2
$ ./sample.sh 
Enter fileName:filename
Enter Ext: ext1
Enter Ext: ext2
filename.type1.ext1.log
filename.type2.ext2.log

Yet better, for cleaner code and to avoid using global variables inside your function, you could use the approach described at Returning Values from Bash Functions and pass the name of the return variable as parameter, and ideally also pass the fileName as parameter:

makeName() {
    local  __type=$1
    local  __fileName=$2
    local  __resultvar=$3
    local ext
    local myresult

    echo -n "Enter Ext: "
    read ext
    myresult="$__fileName.$__type.$ext.log"

    eval $__resultvar="'$myresult'"
}

echo -n "Enter fileName:"
read fileName

makeName "type1" $fileName theResult ; name1=${theResult}
makeName "type2" $fileName theResult ; name2=${theResult}
echo $myresult

echo $name1
echo $name2

Side Note: See Why should eval be avoided in Bash, and what should I use instead? for a discussion why eval should be avoided. When using bash version 3.1 or higher, you can use printf instead of eval:

...
printf -v "$__resultvar" '%s' "$myresult"
...

And, finally, with bash 4.3 or higher, we can assign the nameref attribute to a variable using declare -n, so that the variable is effectively a reference to another variable:

...
declare -n myresult=$3

myresult="$__fileName.$__type.$ext.log"
...
Community
  • 1
  • 1
Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • Hi Andreas, I have updated my question. – Ashwin Jul 10 '14 at 12:40
  • 1
    See my updated answer - essentially, you have misused `return`, and you also need to use the approach proposed by @konsolebox when your function intends to print output to stdout (other than the return value) – Andreas Fester Jul 10 '14 at 13:13
  • `theResult` is still a global variable; you've just taken a more complicated (and more dangerous, using `eval`) path to set it. – chepner Jul 10 '14 at 13:31
  • @chepner Yes, but it is only used in the global scope, not within the function. So the function is well encapsulated - proper encapsulation *is* usually more complicated ;) Anyway, I think that each of the approaches has pro's and con's and you still need to decide wisely which one to use in a specific scenario ... – Andreas Fester Jul 10 '14 at 13:34
  • Typically, encapsulation means that local variables are not accessed globally, rather than local variables are *only* used globally. – chepner Jul 10 '14 at 13:39
  • 1
    Ok since my idea is already included here, I'll give another tip: Instead of using `eval`, since the value returned is an ordinary value, and not an array, you can just use `printf`: `printf -v "$__resultvar" '%s' "$myresult"`. You need Bash 3.1. – konsolebox Jul 10 '14 at 13:43
  • 1
    Also, starting Bash 4.3, we can already use the most elegant solution which is `declare -n`. – konsolebox Jul 10 '14 at 13:46
  • @konsolebox `printf` works perfectly! – Andreas Fester Jul 10 '14 at 13:49
5

Better way is to pick a general variable you could always use. Example:

makeName() {
    __="$fileName.$1.log"
}    

echo -n "Enter fileName:"
read fileName

makeName "type1"
name1=$__
makeName "type2"
name2=$__

echo "$name1"
echo "$name2"

Summoning subshells just to get a value from a function is really inefficient and slow.

konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • +1 good point on the sub shell - I'd probably still prefer sub shells over global variables ;-), but your approach is definitely faster (looping the two `makeName` calls 1000 times takes less than 100ms, while the sub shell based solution takes almost 2 seconds). – Andreas Fester Jul 10 '14 at 12:58
  • cryptic nature of bash never ceases to amaze me :-) – raffian Oct 25 '17 at 02:39
2

Just replace return with echo:

makeName()
{
    echo "Enter Ext: " >&2
    read ext
    echo "$fileName.$1.$ext.log"
}
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    That will end up with `Enter Ext: ` being part of the result ... – Andreas Fester Jul 10 '14 at 13:39
  • Oops. The prompt should be printed to standard error instead, either with `echo "Enter Ext: " >&2`, or by using the `-p` option available to `bash`'s implementation of `read` to display the prompt instead (`read -p "Enter Ext: " ext`). – chepner Jul 10 '14 at 13:42
  • ... and, you still need to use command substitution when calling `makeName` (which OP does not properly do, and which leads to the discussion we just had with @konsolebox ;-) ) – Andreas Fester Jul 10 '14 at 13:43