How do I assign a variable in Bash whose name is expanded ($) from another variable?

Emre Canan

I have a part of script as below.

notify() {
printf -v "old_notif" "%d" "$notif_$2"
now=$(date +%s)
fark=$((now - old_notif))
echo $old_notif
if [ -z $old_notif ]; then  

.....

x1=$(date +%s)
export "notif_$2=$x1"

I call notify with 2 parameters.

notify xyz klm

I create a dynamic variable in function with export. And the main script is in the while loop. My question is how can I use notif_$2 variable in if check? Or how can I assign it's decimal content to another variable? In the example I tried with printf it doesn't assign the content.

Eliah Kagan

TL;DR: One way is local tmp="notif_$2"; printf -v "old_notif" "%d" "${!tmp}".

You are trying to expand a parameter (in this case, a variable) whose name must itself be obtained by expanding another parameter (in this case, a positional parameter). Furthermore, the positional parameter must be expanded, then concatenated with the text notif_, to produce the text that must then itself be expanded.

Bash lets you do this cleanly with indirect expansion or a nameref.

The first two sections of this post show drop-in replacements for your printf -v command, using those techniques. The remaining sections are optional; they further explain these features and what you can do with them.

Way 1: Indirect Expansion

Indirect expansion is the most similar, conceptually speaking, to what you have written. Instead of the printf -v command you have now, you can use these two commands:

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"

local makes the tmp parameter local to the shell function. If for some reason you don't want that, just omit the word local. The parameter doesn't need to be called tmp.

Indirect expansion is covered in 3.5.3 Shell Parameter Expansion in the Bash reference manual.

Way 2: Namerefs

Another way is to use a nameref. A nameref refers to a parameter. You create it from the name of the parameter, but once created, it behaves as though it is that parameter when you read from or write to it.

To use a nameref, replace your printf -v command with:

local -n ref="notif_$2"
printf -v "old_notif" "%d" "$ref"

Passing the -n option to local or declare causes the newly introduced parameter to be a nameref. Notice that, in the printf command, you will use ordinary parameter expansion ($ref) -- not indirect expansion -- because the shell performs the indirection automatically for the nameref.

You do need local or declare here, because -n ref="notif_$2" by itself would be an error and ref="notif_$2" by itself wouldn't make a nameref. declare without the -g option in a shell function behaves like local, so you can use that style if you prefer. Or, in the unusual case where you wanted the nameref to be usable after the function returned, you could use declare -g Thanks to G-Man for pointing out a mistake about this in an earlier version of this answer.

(Depending on your needs, it might even make sense to use a nameref for more than just these two lines. To learn how, read on.)

Namerefs are covered in 3.4 Shell Parameters in the Bash reference manual.

Detailed Explanation: How Indirect Expansion Works

Shell features are most easily demonstrated through interactive use, but positional parameters (like 2, expanded via $2) don't have quite the same meaning outside a shell function, and the local builtin doesn't work at all outside a function (you would use declare instead, or nothing).

So, to start with a simplified example, suppose you are working interactively in your shell and you have run:

x=foo
export "notif_$x=1234"

After you run that, 1234 is stored in the parameter notif_foo. (It also exports that parameter as an environment variable.) We can see this by inspecting notif_foo:

echo "$notif_foo"

This outputs 1234.

Your scenario is analogous to not knowing what is in the x parameter. (In your case, you instead don't know what's in the second positional parameter passed to your shell function. I'll get to that soon.)

But you can build the parameter name and put it in another parameter:

y="notif_$x"

Now y holds notif_foo, so you can use indirect expansion on y, which looks like this:

"${!y}"

That expands to 1234, just as if you had used "$notif_foo". But you don't need to know $x is foo to use it.

For example, this will assign 1234 to old_notif:

old_notif="${!y}"

If you need to format the contents of $notif_foo, you can do that, too. For example, you can use printf if you need it. This command is similar to the printf command in your question, and has the effect of assigning 1234 to old_notif:

printf -v old_notif '%d' "${!y}"

(It also works in your original quoting style, i.e., printf -v "old_notif" "%d" "${!y}" has the same effect.)

Of course, this relies on the y parameter being suitably assigned first.

To write your shell function, you will replace $x with $2, and you will probably want to use the local builtin to prevent your temporary variable--which I will now call tmp instead of y--from leaking out of the function's scope.

local tmp="notif_$2"
printf -v old_notif '%d' "${!tmp}"

Or, using the quoting style you used in the question:

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"

Detailed Explanation: How Namerefs Work

To try out namrefs interactively, you must use declare -n rather than local -n (because local only works--or is needed--inside the body of a shell function).

As before, suppose you have run:

x=foo
export "notif_$x=1234"

Thus $notif_foo expands to 1234. You can create a nameref to the parameter named by the result of expanding "notif_$x":

declare -n y="notif_$x"

Now y refers to the name notif_foo, and ordinary parameter expansion on y will automatically dereference that name, thereby expanding the notif_foo parameter. That is, this expands to 1234, just as if you had used $notif_foo:

"$y"

To write your shell function, you will replace $x with $2, and I recommend using local instead of declare. I suggest also using a somewhat more descriptive name than y; for a short function with no other nameref declarations, ref is probably adequately clear.

local -n ref="notif_$2"
print -v old_notif '%d' "$ref"

Or, with the quoting style you've been using:

local -n ref="notif_$2"
print -v "old_notif" "%d" "$ref"

Namerefs are Powerful

A nameref lets you do more with its referred-to parameter than just read from it. You can also, for example, write to it:

x=foo
declare -n y="notif_$x"
y=1234

The second command creates a nameref to a parameter that might not yet exist. That's no problem! It will be created when you first assign to it, even if that assignment is through the nameref.

The third command looks like it assigns 1234 to y, but really it assigns it to notif_foo. Now both $y and $notif_foo expand to 1234. $notif_foo expands to 1234 because that's the value stored in notif_foo; $y expands to 1234 because y is a nameref for notif_foo.

Suppose you want to know what y refers to, though. That is, suppose you want to get notif_foo, rather than 1234, from y. Well, you can, because with a nameref, indirect expansion has the opposite of its usual effect. This expands to notif_foo:

"${!y}"

In your function, you could introduce notif_$2 through a nameref.

This suggests another way to deal with notif_$2 in your function: you can introduce it through a nameref, and use the nameref for each subsequent access.

Currently you have:

x1=$(date +%s)
export "notif_$2=$x1"

As an alternative, you could use:

x1=$(date +%s)
local -n ref="notif_$2"
ref="$x1"
export "${!ref}=$ref"

That's more complicated that necessary, though, since presumably you only created the x1 parameter because export "notif_$2=$(date +%s)" is hard to read. ref="$(date +%s)" is just as simple, though, so you can omit the x1= line and write:

local -n ref="notif_$2"
ref="$(date +%s)"
export "${!ref}=$ref"

It occurs to me that you might have been using export just to assign the parameter for use in your shell. If you don't actually need to export it to child processes, then you can just use the first two lines, and that's simpler than what you have.

If you do need to export it, use all three. It's still a bit more complicated than what you have... but it may let you simplify the rest of your function, because, afterwards:

  • Instead of having to write notif_$2, you can just write ref.
  • This works even in scenarios where writing notif_$2 is inadequate. That is, it is no longer necessary to do anything special (like Way 1 and Way 2 above) to expand the parameter whose name is the result of expanding notif_$2. Just write ref.
  • This even works for writing -- and creating, and unsetting -- the parameter. (After the point of declaration, ref=text writes to and can even create the referred-to parameter; unset ref unsets the referred-to parameter.)

If you're going to use a nameref throughout your whole function, you may want to think of a more meaningful name for it than ref. (The best name will, of course, be determined by the task you are writing the function to carry out.)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How do I get the value of a variable whose name is based on another variable?

Define a recursively expanded variable using a variable whose name is computed from a simply expanded variable

How do I store textbox name reference in a Variable to later assign the value of that textbox to another variable

How do I pass BASH host command a variable and assign the answer to another variable

Bash: Echo a variable whose name is the value of another variable

How can I use the expanded value of a shell variable in the name of another variable?

How do i change a property in state whose name is the value of a variable?

How do I manipulate a variable whose name conflicts with PDB commands?

How do I assign an object to a variable in one module and then access that variable from another module?

How can I use a variable's value to look up another variable whose name is said value

How to call a value of a variable whose name is passed as text to another variable?

How do I set a bash variable that contains another variable?

How do I assign variable value for name in named list?

How do I assign my select option to another variable

How do I Change or Assign a Value to a Private Variable JTextField from another class?

In a bash script, how can I go to a directory whose name is stored in a variable?

How to assign a value to a variable which name is in a variable in bash?

how do I assign a returned value from an async function to a variable

how do I extract a date stamp from a file name stored in a bash variable

How to assign field name of struct from variable

How do I call a function whose name is only known in a variable in python?

How do I assign a value to a BASH variable iff that variable is null/unassigned/falsey?

how to print environment variable value in shell of a variable whose name is present in another variable value

How achieve variable indirection (refer to a variable whose name is stored in another variable) in tcsh

Invoke function whose name is stored in a variable in bash

Reference to a bash variable whose name contains dot

How can I assign a variable with the value of another variable in Kotlin?

How to assign a variable from another variable in a separate file

How to assign a variable value to a variable from another file?