LinuxQuestions.org
Visit Jeremy's Blog.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Software
User Name
Password
Linux - Software This forum is for Software issues.
Having a problem installing a new program? Want to know which application is best for the job? Post your question in this forum.

Notices


Reply
  Search this Thread
Old 11-17-2011, 03:38 PM   #1
davelove
LQ Newbie
 
Registered: Sep 2011
Posts: 5

Rep: Reputation: Disabled
Double-variable indirection in bash


I am currently parsing through a body of key/value pairs which are read into an array token[0], token[1] respectively. What I need is a means of spawning actual variables with the name of the string within the first variable, and the value contained within the second.

Obviously, pains will be taken to make sure that no exploit code is embedded.

For example:

token[0]="roughVariable"
token[1]="/bin/sh"

... magic happens

$ echo $roughVariable
/bin/sh

The standard indirection methodology appears to be unsuited, and doesn't allow for BOTH a variable variable-name AND a variable value string assignment.

In Perl this is trivial:

$varName = "roughVariable"
$$varName = "/bin/sh"

print $roughVariable
/bin/sh

THAT's what I'd like to do in bash!
 
Old 11-17-2011, 04:30 PM   #2
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
If you have Bash 4, you can use the safer bash technique for indirect variable references: associative arrays. Otherwise, depending on your needs you have available the ${!} bash notation and might have to recur to the use of eval (which is the less preferable, especially if security is a priority).

Associative arrays:
Code:
#!/bin/bash
declare -A token
token[indirect]="roughVariable"
token[roughVariable]="/bin/sh"
echo ${token[${token[indirect]}]}
The ${!} notation:
Code:
#!/bin/bash
varName="roughVariable"
temp="varName"
echo ${!temp}
Eval (this also lets you do indirect refs on the left side of an assignment, or indirect array refs):
Code:
#!/bin/bash
varName="roughVariable"
temp="varName"
eval "temp2=\"\$$temp\""
echo $temp2

Last edited by Juako; 11-17-2011 at 04:31 PM.
 
Old 11-17-2011, 05:45 PM   #3
davelove
LQ Newbie
 
Registered: Sep 2011
Posts: 5

Original Poster
Rep: Reputation: Disabled
Regrettably, the site in which I work has not adopted Bash 4 (for reasons too arcane to elucidate here), so "clean" associative array solutions aren't feasible.

The second solution you provided doesn't even touch on the "value" of the key/value pair -- a classic problem in normal bash indirection. You can provide any number of levels of indirection on a variable, but you can't use indirection on both the name AND the value of the string being passed.

The third solution is similarly handicapped; nowhere is the association made between the desired string variable created (here "roughVariable") and the actual value ("/bin/sh" here). It should be noted that the programmer will in all likelihood never see the values "roughVariable" and "/bin/sh" -- they are dynamically defined in the context of an external file not within the programmer's purvey.

I'm still waiting for some bash wizard to poke their head up and puke out a solution that achieves both seemingly impossible things simultaneously.
 
Old 11-17-2011, 05:55 PM   #4
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
Quote:
Originally Posted by davelove View Post
The second solution you provided doesn't even touch on the "value" of the key/value pair -- a classic problem in normal bash indirection. You can provide any number of levels of indirection on a variable, but you can't use indirection on both the name AND the value of the string being passed.

The third solution is similarly handicapped; nowhere is the association made between the desired string variable created (here "roughVariable") and the actual value ("/bin/sh" here). It should be noted that the programmer will in all likelihood never see the values "roughVariable" and "/bin/sh" -- they are dynamically defined in the context of an external file not within the programmer's purvey.
Oh I just posted examples, not adhering to the exact problem you posted, sorry for that. In the third case I can assure you it's doable, as I explained with eval you pretty much can do any kind of indirection. Here's how to do what you mention:

Code:
#!/bin/bash
token[0]="roughVariable"
token[1]="/bin/sh"

eval "${token[0]}=\"${token[1]}\""

echo $roughVariable
 
Old 11-17-2011, 08:46 PM   #5
davelove
LQ Newbie
 
Registered: Sep 2011
Posts: 5

Original Poster
Rep: Reputation: Disabled
Yep. That does it just fine.

About the only fly in the ointment is the "eval" (which has a somewhat negative rep amongst the security nazis) -- and there are allegedly ways of using the ${!} and $(expression) notations to work around it. Now it is just a matter of trial and error to find some tortured syntax that will successfully replicate the example you provided without the nasty 'e' word.

Thanks! I'm not quite over the finish line yet, but I can smell victory from where I'm standing.
 
Old 11-17-2011, 08:55 PM   #6
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
Oh but I definitely can tell you from experience, the security risks are very real. And I love eval!

Its very funny actually, I've just found a way I didn't know or read before on how to do this type of indirection:

Code:
#!/bin/bash
token[0]="roughVariable"
token[1]="/bin/sh"

read ${token[0]} <<< "${token[1]}"

echo $roughVariable
 
Old 11-17-2011, 11:37 PM   #7
chrism01
LQ Guru
 
Registered: Aug 2004
Location: Sydney
Distribution: Rocky 9.2
Posts: 18,358

Rep: Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751Reputation: 2751
In terms of the general concept, I'd use a Perl hash instead where

$hash{$key} = $value

and both $key & $value can be strings or nums or just about anything, and you can have an indefinite num of entries in the hash.
I'd also point out that although you can indeed do $$val in Perl, its definitely not recommended as best practice.
 
Old 11-18-2011, 08:37 AM   #8
davelove
LQ Newbie
 
Registered: Sep 2011
Posts: 5

Original Poster
Rep: Reputation: Disabled
Now we're talking! The latest solution will pass muster with the security Nazis, hands-down. I'd read in some of the more arcane "admin" sites about bash tricks of this sort, and hadn't quite had a chance to invest the time required to dig it out.

As to the "better" solution in Perl: yes, Perl is better (in every way) than shell-script. No question. The fact that the less-than-sanctioned $$x construct is one way to handle the issue shouldn't be taken as a negative -- the language also sports associative arrays, referenced associative arrays, arrays of hashes (my go-to solution for almost everything) and hashes of arrays (not-so-much), to name only a few of the more popular methods.

However, the problem I'm working with at present is a mish-mash of Perl and bash scripts; whenever something changes in either environment the whole production stream crashes to a halt. For that reason they've locked down both to positively antiquated versions. My reasoning is that if solid, testable solutions can be derived in BOTH spaces (using only the pure bash, or only the pure Perl code), we can start upgrading them once again to circumvent known problems.

Besides, I'm an artiste -- mixing umpty-ump languages to yield a business solution is simply not kosher. If you're not good enough to render a bullet-proof solution in any one language (given one platform, one problem), you shouldn't be using it. Alternatively, you should be working in forestry or interpretive dance instead of writing code.
 
Old 11-18-2011, 02:03 PM   #9
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Perhaps it's a bit awkward, but how about using a function to pair up the target values?

Code:
printvar() {
     case $1 in
          "${token[0]}") echo "${token[1]}" ;;
          "${token[1]}") echo "${token[0]}" ;;
     esac
}

token[0]=roughVariable
token[1]=/bin/sh

printvar "${token[0]}"
printvar "${token[1]}"
This page has a good rundown on using indirect references:
http://mywiki.wooledge.org/BashFAQ/006
 
Old 11-18-2011, 03:45 PM   #10
davelove
LQ Newbie
 
Registered: Sep 2011
Posts: 5

Original Poster
Rep: Reputation: Disabled
Actually, that was the way I was doing things originally -- but there were several factors that mitigated doing that way in the future.

The mechanism I was trying to implement was a '.rc-file' capability which pre-read a number of operational values for various flags, states, file-names, directories, etc. from a custom-named file at the launch of the shell application.

For this to work in as seamless a fashion as possible, the variables defined had to look and behave identically to coded assignments in the preamble of the scripts. Leaving things in a 'token[0]', 'token[1]' array wouldn't achieve that end, particularly as token[0]/token[1] got overwritten with every successive key/value pair read from the file.

As noted earlier, in Perl this is easy -- there is even a module expressly for making this happen!

What is now in place is the following code-snippet:

do_rc () {
[ ! -r ${BASENAME}.rc ] && return -1
while read -r line
do
line=${line##\#*}

shopt -s extglob
line=${line##+([[:space:]])}
line=${line%%+([[:space:]])}
shopt -u extglob

[[ ! "${line}" =~ "^${BASENAME}.*$" ]] && continue
{
IFS=$' \t\n'
token=( $line )
read ${token[0]} <<< "${token[1]}"
echo "${token[0]} -> $(!token[0]}"
}
done < ${BASENAME}.rc
} # do_rc ()

######

Some explanation is due:

BASENAME is intended to represent the portion of $0 stripped away from path and extension elements. The routine begins by removing all comments, leading and trailing white-space, etc. It is assumed that each key/value pairing has "BASENAME" as the leading part of the the key-name (since there may be other variables defined which aren't germane to the function in question).

Assuming that a well-formed BASENAME.rc file actually exists, this will pull all the predefined values out and assign them as internal variables which can be used interchangeably with others coded directly into the routine.

Admittedly, I've omitted all the security/hardening bits which are necessary -- both because some are proprietary, and including the whole would detract from legibility.

Enjoy.
 
Old 11-20-2011, 08:13 AM   #11
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
In this thread another, simpler way has been posted. Mind that if you are going to do the indirect assigment inside a function, and want the variable to be available from global scope, read is still the way to go.

This works well if you are doing the assignment in the global scope, or just need it to be available from function scope and subscopes:
Code:
#!/bin/bash
token[0]="roughVariable"
token[1]="/bin/sh"

declare ${token[0]}="${token[1]}"

echo $roughVariable
you can use declare, local or export (the latter will also make the definition available to subshells).

Last edited by Juako; 11-23-2011 at 11:44 AM.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[bash] re-evaluate variable inside variable muzzol Programming 9 12-01-2010 11:31 AM
Bash: How do I read data from a variable while that variable is being populated? theaceoffire Programming 4 04-23-2010 02:29 PM
How to get variable from text file into Bash variable mcdef Linux - Software 2 06-10-2009 01:15 PM
Problem with bash script - variable name within variable name steven.c.banks Linux - Newbie 3 03-10-2009 03:08 AM
bash parameter indirection possible? kornelix Programming 5 11-30-2005 08:35 AM

LinuxQuestions.org > Forums > Linux Forums > Linux - Software

All times are GMT -5. The time now is 06:15 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration