Basic Bash: How to use eval to evaluate variable names made of arbitrary strings.
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Basic Bash: How to use eval to evaluate variable names made of arbitrary strings.
And in addition to the exact subject of the title of this thread: "How to do arrays, without using real arrays."
Here's how to eval variable names, and get their contents, when the variable names are made of arbitrary strings:
Code:
#STEP 1 -- some arbitrary words to make some variable names from:
sasha@reactor:~$ var1='hello'
sasha@reactor:~$ var2='goodbye'
sasha@reactor:~$ var3='blah'
sasha@reactor:~$ var4='leopard'
#STEP 2 -- Form some strings from some of the arbitrary pieces:
sasha@reactor:~$ echo ${var1}_${var3}_${var2}
hello_blah_goodbye <--fresh string
sasha@reactor:~$ echo ${var1}_${var4}_${var2}
hello_leopard_goodbye <--fresh string
sasha@reactor:~$
#STEP 3 -- If those strings were actually variables...
sasha@reactor:~$ echo \$${var1}_${var3}_${var2}
$hello_blah_goodbye <--like this
sasha@reactor:~$ echo \$${var1}_${var4}_${var2}
$hello_leopard_goodbye <--or like this
sasha@reactor:~$
#STEP 4 ...they would be empty variables; we haven't set them to anything:
sasha@reactor:~$ echo $hello_blah_goodbye
sasha@reactor:~$ echo $hello_leopard_goodbye
sasha@reactor:~$
#STEP 5 -- Give the new variables some values..
# If the value is multiple words (contains spaces) we must include "quotes" around the value:
sasha@reactor:~$ eval ${var1}_${var3}_${var2}="'weird greeting'"
sasha@reactor:~$
# if there's no spaces, we don't need quotes:
sasha@reactor:~$ eval ${var1}_${var4}_${var2}='carnivore' <--quotes here optional
sasha@reactor:~$
#STEP 6 -- Now they aren't empty; to see the value of a variable, we use echo:
sasha@reactor:~$ echo $hello_blah_goodbye
weird greeting
sasha@reactor:~$ echo $hello_leopard_goodbye
carnivore
sasha@reactor:~$
#STEP 7 -- Create an echo command for the shell to execute, to return to us the value
# of the variable, when different combinations of our arbitrary words might comprise the variable name:
sasha@reactor:~$ echo "echo \$${var1}_${var3}_${var2}" <--quotes are optional, but safer!
echo $hello_blah_goodbye <-- a command for the shell to run
sasha@reactor:~$ echo echo \$${var1}_${var4}_${var2} <--no quotes, but still works
echo $hello_leopard_goodbye <-- another command for the shell to run
sasha@reactor:~$
# We can run those commands ourselves, like in STEP 6 above:
sasha@reactor:~$ echo $hello_blah_goodbye
weird greeting
sasha@reactor:~$ echo $hello_leopard_goodbye
carnivore
sasha@reactor:~$
#STEP 9 -- ..or the shell can run them for us (as if within a script) and give us the value.
# To get the value of a variable made of various arbitrary parts, we need eval
# to evaluate the parts and string them together:
sasha@reactor:~$ hi_and_goodbye=$(eval echo \$${var1}_${var3}_${var2})
sasha@reactor:~$ echo $hi_and_goodbye
weird greeting
# Or if we know the parts, we can be direct, and don't need eval:
sasha@reactor:~$ cat_thing=$(echo $hello_leopard_goodbye)
sasha@reactor:~$ echo $cat_thing
carnivore
sasha@reactor:~$
Now, Here's a little script (I actually just stuck the code into the console) using the above technology to illustrate simulating an array of 11 elements (for the purpose of using an array-like construct, in a shell environment that doesn't support arrays):
Code:
# The REAL ARRAY method:
# first, fill the array:
for i in $(seq 0 10); do
array[$i]="real array element $i here!"
done
# now, see the values of the array elements:
for i in $(seq 0 10); do
the_content="${array[i]}"
echo $the_content
done
# The NON-ARRAY method:
# be wary of using exclamation point in the fake element
# because bash tries to execute it as a history event. If you want
# to use ! in your fake array element, follow it with a newline (like I did here) or whatever.
# see man bash for details of the ! event operator.
# first, define the fake array's name:
fake_array='fake_array'
# fill the phony array:
for i in $(seq 0 10); do
eval ${fake_array}${i}="'phoney element $i here!
'"
done
# now, see the values of the 'phony array's elements:
for i in $(seq 0 10); do
the_content=$(echo $(eval echo \$$fake_array${i}))
echo $the_content
done
Here's the output in Bash shell:
Code:
# The REAL ARRAY method:
sasha@reactor:~$ for i in $(seq 0 10); do
> array[$i]="real array element $i here"
> done
sasha@reactor:~$ for i in $(seq 0 10); do
> the_content="${array[i]}"
> echo $the_content
> done
real array element 0 here
real array element 1 here
real array element 2 here
real array element 3 here
real array element 4 here
real array element 5 here
real array element 6 here
real array element 7 here
real array element 8 here
real array element 9 here
real array element 10 here
sasha@reactor:~$
# The NON-ARRAY method:
sasha@reactor:~$ fake_array='fake_array'
sasha@reactor:~$ for i in $(seq 0 10); do
> eval ${fake_array}${i}="'phoney element $i here!
> '"
> done
sasha@reactor:~$ for i in $(seq 0 10); do
> the_content=$(echo $(eval echo \$$fake_array${i}))
> echo $the_content
> done
phoney element 0 here!
phoney element 1 here!
phoney element 2 here!
phoney element 3 here!
phoney element 4 here!
phoney element 5 here!
phoney element 6 here!
phoney element 7 here!
phoney element 8 here!
phoney element 9 here!
phoney element 10 here!
sasha@reactor:~$
And for comparison, here's the same procedures run in the Dash shell:
Code:
#The REAL ARRAY method:
\u@\h:\w$ for i in $(seq 0 10); do
> array[$i]="real array element $i here"
> done
dash: array[0]=real array element 0 here: not found
dash: array[1]=real array element 1 here: not found
dash: array[2]=real array element 2 here: not found
dash: array[3]=real array element 3 here: not found
dash: array[4]=real array element 4 here: not found
dash: array[5]=real array element 5 here: not found
dash: array[6]=real array element 6 here: not found
dash: array[7]=real array element 7 here: not found
dash: array[8]=real array element 8 here: not found
dash: array[9]=real array element 9 here: not found
dash: array[10]=real array element 10 here: not found
\u@\h:\w$
# no point trying to get the contents; it's obvious the array is non-existent.
#The FAKE ARRAY method:
\u@\h:\w$ fake_array='fake_array'
\u@\h:\w$ for i in $(seq 0 10); do
> eval ${fake_array}${i}="'phoney element $i here!
> '"
> done
\u@\h:\w$ for i in $(seq 0 10); do
> the_content=$(echo $(eval echo \$$fake_array${i}))
> echo $the_content
> done
phoney element 0 here!
phoney element 1 here!
phoney element 2 here!
phoney element 3 here!
phoney element 4 here!
phoney element 5 here!
phoney element 6 here!
phoney element 7 here!
phoney element 8 here!
phoney element 9 here!
phoney element 10 here!
\u@\h:\w$
# Success!
Not sure about 'brilliant' but thank you Gilbert! I did think it was pretty clever though
What brought this on was this thread: http://www.linuxquestions.org/questi...script-775058/ where I'm thinking about considering trying to convert my large, "already-well-underway" project from "uses arrays" to "doesn't use arrays" so it can be (more) portable.
This sure would have been easier had it occurred to me a long time ago :/ because it's proving darned tricky to convert.. I made a little sed script that did/does a lot of the conversions on the real project for me -- the regexes are about a foot long on average -- and while it *works*, it doesn't catch every instance, so needs a bit of fine tuning..
I have a similar dilemma with a shell project I've been adding to for 5 years now. It's up to over 10,000 lines, so major changes are difficult to implement. Actually I thought for a long time that I'd do the same thing -converting to POSIX, but in the end I went the other way and implemented more stuff in pure bash insetad of calling externals so much. The other sticky thing is that it would be nice to have it internationalized, but, the code is chick full of 'echo' so it would be a major undertaking to convert it to using gettext.
Anyway, thanks for your nice stuff there -I've saved it somewhere where I'm sure it won't get lost -errr, where it won't get deleted.
BTW, you, tuxdev and myself may be the most fanatic 'do it in shell' types around... IF you ever create pure-shell or pure-bash clones of any utilities, send me a copy to add to my collection: http://distro.ibiblio.org/pub/linux/...ects/BashTrix/
I thought/think that echo is portable, no? I realize that `echo -n` or `echo -e` are not portable, so I changed those in my project to "printf", which works great, despite that I needed to add \n to the end of a number of string messages that the program emits, because printf doesn't \n automatically...
I also found one chunk of stuff, a long multi-line wrapped string containing <ENTER> type of newlines and a load of backslashes, I wanted to echo to the screen, and while Bash has no trouble, Dash was interpreting \\ to mean \ and screwing up the output, regardless of using echo/printf/''/' . I ended up calling /bin/echo in that case, and using single quotes around the string. It now works fine in both shells.
BTW, you, tuxdev and myself may be the most fanatic 'do it in shell' types around...
I wrote a 10,000+ lines of ksh for a client's warehousing system (their choice of language!) and currently have a 2,000+ line bash backup script; does that count me in?
My gosh, 2000+ lines for a backup script!! That's 'mongous! Almost as large as my current project -- my little backup script is lucky if it tops a couple dozen lines, though it's just for my desktop machine.
catkin, you are (possibly) welcome in the club... I wasn't really referring to my 10,000 line script when i siad that, though. I meant pure-shell fanatics. I have it on experience that GrapefruiTgirl and tuxdev both try hard to do things with pure shell where others do it with perl/awk/sed. Check out the little project I linked to and you'll see what I meant -~14 clones of basic utilities which use no external programs at all, sort, cat, cut and even wget -all in pure bash. Anyway, I was trying to be exclusive or boast -I take a few potshots for doig things in shell.
My gosh, 2000+ lines for a backup script!! That's 'mongous! Almost as large as my current project -- my little backup script is lucky if it tops a couple dozen lines, though it's just for my desktop machine.
It has a lot of comments and vertical space and, AFAIK, it's just for my desktop machine. <shameless plug>MyBackup.sh 0.3 is downloadable from Denis Corbin, dar's creator samples page. It was developed and tested on ubuntu. Version 0.4 is being tested on SLackware 13.0.</shameless plug>
Solved!!! I've converted all the arrays in my script, and it's working like a charm. Since the several threads I've created in the last few days are related, see these 2 threads for reference:
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.