LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   BASH: Assign a variable to a variable name... (https://www.linuxquestions.org/questions/programming-9/bash-assign-a-variable-to-a-variable-name-948420/)

c_henry 06-04-2012 06:30 AM

BASH: Assign a variable to a variable name...
 
Hi,

Apologies for the possibly confusing thread title in advance.

I'm trying to read in a variable then use that variable in a variable name. My specific problem is something like this:

EBS_GW=192.168.0.1
OBIE_GW=192.168.1.1

echo "What is the product: "
read PROD

echo "The gateway for $PROD is $PROD_GW"

So if the user entered EBS, they would get the EBS_GW value of 192.168.0.1.

I've tried the eval command, but I'm still not getting it right.

Any help is appreciated.

Many thanks,

Colin

pan64 06-04-2012 07:10 AM

Try this:
eval echo "The gateway for $PROD is \$${PROD}_GW"

Nominal Animal 06-04-2012 07:29 AM

What are you trying to accomplish? Variable variables are the wrong answer. (I say that because I have not yet seen a case where they are the right answer.)

Assume you have a separate file, say /etc/gateway.list:
Code:

EBS        192.168.0.1
OBIE        192.168.1.1

Now you can do e.g.
Code:

GW=""
while [ -z "$GW" ]; do
    read -p 'Product? ' PROD

    # Exit if empty product
    [ -n "$PROD" ] || exit 0

    # Locate product in gateway list
    while read NAME IP COMMENT ; do
        if [ "$NAME" = "$PROD" ]; then
            GW="$IP"
            break
        fi
    done < /etc/gateway.list

    [ -n "$GW" ] || printf 'Product %s: No gateway found.\n' "$PROD"
done

which aborts the script cleanly if the user supplies an empty string for the product name.

I prefer to keep my syntax POSIX-compatible, but if you use Bash only, then you should probably use the [[ ... ]] compound command for the conditional expressions; see the Bash Reference Manual, Conditional Expressions chapter for details. The above snippet is written in a way which would allow it to work with both Bash and POSIX shells like dash. (I normally would use awk to pick the IP address, but I decided to keep the above strictly within the shell, avoiding any external dependencies.)

If you don't want to have a separate file, replace the done < /etc/gateway.list line with a here document, e.g.
Code:

    done <<ENDOFLIST
        EBS        192.168.0.1
        OBIE        192.168.1.1
ENDOFLIST

Note that ENDOFLIST must not be indented. (You can indent it with tabs if you use <<-ENDOFLIST, but since many editors convert tabs to spaces, and spaces will not work for the indentation, it is always simpler to put it in the first column even if it breaks indentation visually.)

A completely different approach in Bash is to have an array of IP addresses. Bash version 4 and later support associative arrays. To be compatible with older versions, you can just use an array with elements combining both name and address, i.e.
Code:

gateways=('EBS=192.168.0.1' 'OBIE=192.168.1.1')
in which case the loop to find the address matching PROD in the array would be
Code:

for entry in "${gateways[@]}" ; do
    if [ "$PROD" = "${entry%%=*}" ]; then
        GW="${entry#*=}"
        break
    fi
done

where ${entry%%=*} evaluates to the current value of entry with everything after the first = removed, and ${entry#*=} evaluates to the value of entry with everything up to and including the first = removed. (The value of entry itself is not affected.) Note that this works fine with IPv6 addresses, too. It is the reason why I used = instead of : as the separator in the list.

So, what are you trying to accomplish?

c_henry 06-04-2012 07:36 AM

Brilliant that works.

Just one more, how would I assign that to a new variable?

Say once I found what the value it got evaluated to, then I wanted to assign it to say $DEFAULT_GW?

I've tried:

DEFAULT_GW=`eval echo "\$${PROD}_GW"`
echo "DEFAULT_GW is: $DEFAULT_GW

but I get

DEFAULT_GW is: 13219{PROD}_GW

Which I'm guessing is the PID.

Really appreciated the quick reply and hope I'm not pushing my luck (and more importantly your goodwill) too much.

Colin

pan64 06-04-2012 07:46 AM

maybe:
eval "DEFAULT_GW=\$${PROD}_GW"
but maybe better to use a solution similar to the one described by Nominal Animal. You will hardly be able to check errors...

c_henry 06-04-2012 07:55 AM

Quote:

Originally Posted by Nominal Animal (Post 4695061)
What are you trying to accomplish?

Pretty much what you've just described!

c_henry 06-04-2012 07:57 AM

Quote:

Originally Posted by pan64 (Post 4695080)
maybe:
eval "DEFAULT_GW=\$${PROD}_GW"
but maybe better to use a solution similar to the one described by Nominal Animal. You will hardly be able to check errors...

That also works a treat.

Thank you so much.

grail 06-04-2012 08:32 AM

Here are some reasons not to use eval (ever if you can help it)

I was wondering why this had to be so complicated? What was wrong with:
Code:

#!/bin/bash

DEFAULT=10.1.1.1
EBS=192.168.0.1
OBIE=192.168.1.1

echo "What is the product: "
read PROD

echo "Your gateway choice is: ${!PROD:-$DEFAULT}"

Obviously NA has pointed out that you need to incorporate some testing (I have included a type of work around) to make sure the user enters something relevant

rknichols 06-04-2012 08:41 AM

Quote:

Originally Posted by pan64 (Post 4695080)
maybe:
eval "DEFAULT_GW=\$${PROD}_GW"
but maybe better to use a solution similar to the one described by Nominal Animal. You will hardly be able to check errors...

Passing unfiltered user input to eval is extremely dangerous. You've just made it very simple for that user to get arbitrary commands executed. Consider this (user input in red):
Code:

read PROD
(ls *) #
eval "DEFAULT_GW=\$${PROD}_GW"

That will execute "ls *" in the current directory and capture the output in DEFAULT_GW. The ls command is, of course, fairly harmless, but any command could have been entered there.

David the H. 06-04-2012 11:33 AM

As NominalAnimal said, variable variables are almost always the wrong answer.

How can I use variable variables (indirect variables, pointers, references) or associative arrays?
http://mywiki.wooledge.org/BashFAQ/006

And eval is just a variation of evil, in most cases. It's like digging a hole in your garden with an a-bomb. Just don't use it.


One of the biggest problems with trying to use variable variables is that legal variable names in the shell can only contain letters, numbers and the underscore, and cannot start with a number. You'd have to either test or convert any string you get beforehand if you wanted to use them. Much better would be to use an associative array, as described in the above link (and assuming bash or ksh), or just redesign everything to avoid needing them at all, if possible.


Code:

#!/bin/bash

declare -A address

address[EBS]=192.168.0.1
address[OBIE]=192.168.1.1

read -r -p "please enter the product name: " prod

if [[ -n "${address[$prod]}" ]] ; then

        echo "The gateway for $prod is "${address[$prod]}"

else

        echo "There's no gateway matching the product name $prod".

fi



All times are GMT -5. The time now is 06:14 PM.