LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Basic Bash: How to use eval to evaluate variable names made of arbitrary strings. (http://www.linuxquestions.org/questions/programming-9/basic-bash-how-to-use-eval-to-evaluate-variable-names-made-of-arbitrary-strings-775622/)

GrapefruiTgirl 12-14-2009 02:47 PM

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!


gnashley 12-15-2009 08:28 AM

Brilliant Sasha -you must have spent some time on that...

GrapefruiTgirl 12-15-2009 10:51 AM

Not sure about 'brilliant' but thank you Gilbert! I did think it was pretty clever though :p

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 :scratch: -- and while it *works*, it doesn't catch every instance, so needs a bit of fine tuning..

Still ongoing!

Sasha

gnashley 12-15-2009 12:08 PM

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/

GrapefruiTgirl 12-15-2009 02:28 PM

RE `echo`

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.

catkin 12-15-2009 02:42 PM

Quote:

Originally Posted by gnashley (Post 3792509)
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? :hattip:

GrapefruiTgirl 12-15-2009 02:54 PM

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.

gnashley 12-16-2009 01:09 AM

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.

catkin 12-16-2009 01:10 AM

Quote:

Originally Posted by GrapefruiTgirl (Post 3792695)
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>

GrapefruiTgirl 12-16-2009 10:25 AM

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:

http://www.linuxquestions.org/questi...52#post3793352
http://www.linuxquestions.org/questi...script-775058/

Technically, THIS here thread isn't a question, so I'm not marking IT solved. Comments are welcome from anyone.

Sasha


All times are GMT -5. The time now is 03:24 AM.