Discussion:
[Help-bash] taking the name of a variable from another variable
Robert Durkacz
2018-07-03 12:09:24 UTC
Permalink
How could I write a shell script that takes two arguments, the first to be
interpreted as the name of an environment variable and the second to be the
value of that variable? All the script should do is create an environment
variable with that value.

Whatever I try, I cannot get the shell to evaluate some expression and use
the result as a variable name. Thus for instance
varvar=var
$varvar=val
results in
var=val: command not found

In other words, can I obtain the name of a name of a variable from another
variable?
Greg Wooledge
2018-07-03 13:44:51 UTC
Permalink
Before even reading the body, responding only to the subject:

Use an associative array instead.
Post by Robert Durkacz
How could I write a shell script that takes two arguments, the first to be
interpreted as the name of an environment variable and the second to be the
value of that variable? All the script should do is create an environment
variable with that value.
A script CAN'T set an environment variable. Once the script exits, that
variable will be gone.

A script is a separate child process with its own copy of the environment,
and any changes it makes to its environment will not be visible to its
caller.
Marco Ippolito
2018-07-03 14:21:55 UTC
Permalink
In short, follow Greg's advice.

If this is an interview question (I got it once), the answer is "if you
mean setting a variable in the _current_ shell's environment, a child
process won't normally be able to do that, modifying its own environment
instead". Demonstrates some understanding of the separation between
parent and child processes.

You could, against my advice, add: "... unless that child process were
something akin to a debugger and we had access to elevated privileges,
attaching back to the launching shell and doing something like: sudo gdb
-p "$1" -batch -ex 'p setenv("baz", "quux")' -ex quit. (You would pass
the parent shell's PID, $$, into $1). I am not sure whether this would
signal negatively or positively in an interview, though.

If the words "shell script" in your question can be understood to mean
"a file containing shell commands that the current shell could source"
(leveraging the debatable ambiguity in formulation), then perhaps
something as simple as this is enough to set your environment variable:

name=foo value=bar; eval "export $name=$value"

Running a new bash instance from the current shell:

bash -c "echo .\$$name.; env | grep \$$name"

confirms the exportation of name=value to the subprocess:

.bar.
foo=bar
Post by Greg Wooledge
Use an associative array instead.
Post by Robert Durkacz
How could I write a shell script that takes two arguments, the first to be
interpreted as the name of an environment variable and the second to be the
value of that variable? All the script should do is create an environment
variable with that value.
A script CAN'T set an environment variable. Once the script exits, that
variable will be gone.
A script is a separate child process with its own copy of the environment,
and any changes it makes to its environment will not be visible to its
caller.
Greg Wooledge
2018-07-03 15:17:02 UTC
Permalink
Post by Marco Ippolito
then perhaps
name=foo value=bar; eval "export $name=$value"
That's an unsafe use of eval. You need to escape the $ before value,
so that after the eval, the shell ends up running:

export foo=$value

All other points (child processes, environment, sourcing) still apply.
Eric Blake
2018-07-03 15:52:58 UTC
Permalink
Post by Greg Wooledge
Post by Marco Ippolito
then perhaps
name=foo value=bar; eval "export $name=$value"
That's an unsafe use of eval. You need to escape the $ before value,
export foo=$value
All other points (child processes, environment, sourcing) still apply.
And, if you are going to take $name from the user rather than something
that you have generated safely yourself, you absolutely want to sanitize
it before expanding it in eval (otherwise, some user will do an action
such as:

name='a; rm -rf /*; b'

just to spite you for your lack of security sanitization of your inputs
to eval).
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3266
Virtualization: qemu.org | libvirt.org
João Eiras
2018-07-03 13:52:59 UTC
Permalink
Post by Robert Durkacz
How could I write a shell script that takes two arguments, the first to be
interpreted as the name of an environment variable and the second to be the
value of that variable? All the script should do is create an environment
variable with that value.
Whatever I try, I cannot get the shell to evaluate some expression and use
the result as a variable name. Thus for instance
varvar=var
$varvar=val
results in
var=val: command not found
In other words, can I obtain the name of a name of a variable from another
variable?
Example:

some_variable="hello world"
another_text="goodbye world"
my_variable_name=some_variable

# Read variable
echo ${!my_variable_name}
# Assign variable
eval "$my_variable_name=\"\$another_text\""
# Read variable again
echo ${!my_variable_name}

More:
http://tldp.org/LDP/abs/html/ivr.html
Daniel Mills
2018-07-03 13:57:20 UTC
Permalink
Post by Robert Durkacz
Post by Robert Durkacz
How could I write a shell script that takes two arguments, the first to
be
Post by Robert Durkacz
interpreted as the name of an environment variable and the second to be
the
Post by Robert Durkacz
value of that variable? All the script should do is create an environment
variable with that value.
Whatever I try, I cannot get the shell to evaluate some expression and
use
Post by Robert Durkacz
the result as a variable name. Thus for instance
varvar=var
$varvar=val
results in
var=val: command not found
In other words, can I obtain the name of a name of a variable from
another
Post by Robert Durkacz
variable?
some_variable="hello world"
another_text="goodbye world"
my_variable_name=some_variable
# Read variable
echo ${!my_variable_name}
# Assign variable
eval "$my_variable_name=\"\$another_text\""
# Read variable again
echo ${!my_variable_name}
http://tldp.org/LDP/abs/html/ivr.html
Ew. Why not just use printf -v "$my_variable_name" %s "$another_text" ?
Greg Wooledge
2018-07-03 14:02:08 UTC
Permalink
Post by Robert Durkacz
Post by João Eiras
http://tldp.org/LDP/abs/html/ivr.html
Ew. Why not just use printf -v "$my_variable_name" %s "$another_text" ?
For the same reason he referenced the ABS: he doesn't know any better.

An alternative reference would be https://mywiki.wooledge.org/BashFAQ/006

But I did not cite that in my first response, because the first paragraph
of the actual question led to an entirely different problem which makes
the "indirect reference" syntax and all the stupid shell hacks to reverse
it completely moot.

Plus, if the goal is to index information by string keys, you should be
using an associative array instead.
Marco Ippolito
2018-07-03 14:29:41 UTC
Permalink
Post by Robert Durkacz
Post by Robert Durkacz
How could I write a shell script that takes two arguments, the first to
be
Post by Robert Durkacz
interpreted as the name of an environment variable and the second to be
the
Post by Robert Durkacz
value of that variable? All the script should do is create an environment
variable with that value.
Whatever I try, I cannot get the shell to evaluate some expression and
use
Post by Robert Durkacz
the result as a variable name. Thus for instance
varvar=var
$varvar=val
results in
var=val: command not found
In other words, can I obtain the name of a name of a variable from
another
Post by Robert Durkacz
variable?
some_variable="hello world"
another_text="goodbye world"
my_variable_name=some_variable
# Read variable
echo ${!my_variable_name}
# Assign variable
eval "$my_variable_name=\"\$another_text\""
# Read variable again
echo ${!my_variable_name}
http://tldp.org/LDP/abs/html/ivr.html
Ew. Why not just use printf -v "$my_variable_name" %s "$another_text" ?
Right, he could be using printf -v there (nicer syntax), but he
mentioned "an environment variable" so I might add an `export' after
that or just a `declare -x <parameter_name>', to the same effect.
Robert Durkacz
2018-07-04 00:29:34 UTC
Permalink
Post by Greg Wooledge
A script CAN'T set an environment variable. Once the script exits, that
variable will be gone.

Yes, I know. I wrote 'shell script' but meant to write 'shell function'.
The next reply in the thread, from João Eiras, answered the question.
Marco Ippolito
2018-07-04 01:09:04 UTC
Permalink
Post by Greg Wooledge
Post by Greg Wooledge
A script CAN'T set an environment variable. Once the script exits, that
variable will be gone.
Yes, I know. I wrote 'shell script' but meant to write 'shell function'.
The next reply in the thread, from João Eiras, answered the question.
With the original intent now disambiguated, may I submit this for your consideration, to illustrate how the nameref attribute of a variable may also help in answering a question you embed in the original post (i.e. "can I obtain the name of a variable from another variable?"):

Before:

$ bash -c 'echo "$foo"'
<blank line>

Defining a function to set, and thus export, an environment variable (parameters such as name=value aren't exported by default, envvars are):

$ setenv() { local -n varname=$1; local -r value=$2; export varname=$value; }

Using said function:

$ setenv foo $'Hello,\nworld!'

After:

$ bash -c 'echo "$foo"'
Hello,
world!

You did not specify which $BASH_VERSION you are on, so Greg offered this link:

https://mywiki.wooledge.org/BashFAQ/006
Post by Greg Wooledge
Most of this page was written prior to Bash 4.3. Namerefs (typeset/declare/local -n) may significantly change the considerations for indirection.
(Source: the linked FAQ)

Jõao's formulation works with versions prior to 4.3, but you might like the cleaner syntax of Namerefs better if you have the choice, modulo personal preference and portability/compatibility concerns.
Greg Wooledge
2018-07-05 12:16:25 UTC
Permalink
Post by Marco Ippolito
Post by Robert Durkacz
Yes, I know. I wrote 'shell script' but meant to write 'shell function'.
$ setenv() { local -n varname=$1; local -r value=$2; export varname=$value; }
Be careful with bash name refs. There's a potential namespace collision
between the variable name provided by the caller, and the local variables
of the function. With name refs in play, they all mingle in a single
namespace.

And it's NOT just the variable name that the ref points to, either. ALL
the local variables of the function are now in play and can wreak havoc:

wooledg:~$ setenv() { local -n varname=$1; local -r value=$2; export varname=$value; }
wooledg:~$ setenv value 42
bash: value: readonly variable

The nameref points to the name "value", so export tries to set the
value of "value", but you've made that readonly, so it can't, and
thus the error. The collision between the caller's variables and the
function's local variables is very real, and very hard to deal with.
The best approach seems to be putting something like _myfuncname_ (with
literal underscores) in front of ALL of the function's local variables
when name refs are in play.

Loading...