Lookup shell variables by name, indirectly [duplicate] - bash

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
Let's say I have a variable's name stored in another variable:
myvar=123
varname=myvar
Now, I'd like to get 123 by just using $varname variable.
Is there a direct way for that? I found no such bash builtin for lookup by name, so came up with this:
function var { v="\$$1"; eval "echo "$v; }
so
var $varname # gives 123
Which doesn't look too bad in the end, but I'm wondering if I missed something more obvious.

From the man page of bash:
${!varname}
If the first character of parameter is an exclamation point, a level of
variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable;
this variable is then expanded and that value is used in the rest of
the substitution, rather than the value of parameter itself. This is
known as indirect expansion.

There isn't a direct Posix-conforming syntax, only a bashism. I usually do this:
eval t="\$$varname"
This will work on any Posix shell, including those systems where bash is the login shell and /bin/sh is something smaller and faster like ash. I like bash and use it for my login shell but I avoid bashisms in command files.
Note: One problem with writing bash-specific scripts is that even if you can count on bash being installed, it could be anywhere on the path. It might be a good idea in that case to use the fully general /usr/bin/env shebang style, but note that this is still not 100% portable and has security issues.

${!varname} should do the trick
$ var="content"
$ myvar=var
$ echo ${!myvar}
content

I usually look at Advance Bash-Scripting Guide when I need to freshen up my Bash skills.
Regarding your question look at Indirect References
Notation is:
Version < 2
\$$var
Version >= 2
${!varname}

# bmuSetIndirectVar()
# TO DOUBLE CHECK THIS COMMENT AND DEMO
# This function is an helper to read indirect variables.
# i.e. get the content of a variable whose name is saved
# within an other variable. Like:
# MYDIR="/tmp"
# WHICHDIR="MYDIR"
# bmuSetIndirectVar "WHICHDIR" "$MYDIR"
#
bmuSetIndirectVar(){
tmpVarName=$1
locVarName=$1
extVarName=$2
#echo "debug Ind Input >$1< >$2<"
eval tmpVarName=\$$extVarName
#echo "debug Ind Output >$tmpVarName< >$extVarName<"
export $locVarName="${tmpVarName}"
}
I am currently using this little function. I am not fully happy with it, and I have seen different solutions on the web (if I could recall I would write them here), but it seems to work. Within these few lines there is already some redundancy and extra data but it was helpful for debugging.
If you want to see it in place, i.e. where I am using it, check:
https://github.com/mariotti/bmu/blob/master/bin/backmeup.shellfunctions.sh
Of course it is not the best solution, but made me going on with the work, in
the hope I can replace it with something a bit more general soon.

Related

Extracting git commit information in GitHub action workflow- use of '$' symbol [duplicate]

This question already has answers here:
Backticks vs braces in Bash
(3 answers)
Brackets ${}, $(), $[] difference and usage in bash
(1 answer)
Closed 4 years ago.
I have two questions and could use some help understanding them.
What is the difference between ${} and $()? I understand that ()
means running command in separate shell and placing $ means passing
the value to variable. Can someone help me in understanding
this? Please correct me if I am wrong.
If we can use for ((i=0;i<10;i++)); do echo $i; done and it works fine then why can't I use it as while ((i=0;i<10;i++)); do echo $i; done? What is the difference in execution cycle for both?
The syntax is token-level, so the meaning of the dollar sign depends on the token it's in. The expression $(command) is a modern synonym for `command` which stands for command substitution; it means run command and put its output here. So
echo "Today is $(date). A fine day."
will run the date command and include its output in the argument to echo. The parentheses are unrelated to the syntax for running a command in a subshell, although they have something in common (the command substitution also runs in a separate subshell).
By contrast, ${variable} is just a disambiguation mechanism, so you can say ${var}text when you mean the contents of the variable var, followed by text (as opposed to $vartext which means the contents of the variable vartext).
The while loop expects a single argument which should evaluate to true or false (or actually multiple, where the last one's truth value is examined -- thanks Jonathan Leffler for pointing this out); when it's false, the loop is no longer executed. The for loop iterates over a list of items and binds each to a loop variable in turn; the syntax you refer to is one (rather generalized) way to express a loop over a range of arithmetic values.
A for loop like that can be rephrased as a while loop. The expression
for ((init; check; step)); do
body
done
is equivalent to
init
while check; do
body
step
done
It makes sense to keep all the loop control in one place for legibility; but as you can see when it's expressed like this, the for loop does quite a bit more than the while loop.
Of course, this syntax is Bash-specific; classic Bourne shell only has
for variable in token1 token2 ...; do
(Somewhat more elegantly, you could avoid the echo in the first example as long as you are sure that your argument string doesn't contain any % format codes:
date +'Today is %c. A fine day.'
Avoiding a process where you can is an important consideration, even though it doesn't make a lot of difference in this isolated example.)
$() means: "first evaluate this, and then evaluate the rest of the line".
Ex :
echo $(pwd)/myFile.txt
will be interpreted as
echo /my/path/myFile.txt
On the other hand ${} expands a variable.
Ex:
MY_VAR=toto
echo ${MY_VAR}/myFile.txt
will be interpreted as
echo toto/myFile.txt
Why can't I use it as bash$ while ((i=0;i<10;i++)); do echo $i; done
I'm afraid the answer is just that the bash syntax for while just isn't the same as the syntax for for.
your understanding is right. For detailed info on {} see bash ref - parameter expansion
'for' and 'while' have different syntax and offer different styles of programmer control for an iteration. Most non-asm languages offer a similar syntax.
With while, you would probably write i=0; while [ $i -lt 10 ]; do echo $i; i=$(( i + 1 )); done in essence manage everything about the iteration yourself

How to make a string, not a value of a variable but a new variable? [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed last year.
I have these variables:
var1=ab
var2=cd
result=${var1}-text-${var2}
ab-text-cd=bingo
I have:
$ echo $result
ab-text-cd
I would like to have:
$ echo $result
bingo
Is it possible and how?
More info:
Var1 and var2 are arguments given to script.
Thanks to #Léa Gris.
I didn't know about "indirect parameter expansion".
"If the first character of PARAMETER is an exclamation point, Bash uses the value of the variable formed from the rest of PARAMETER as the name of the variable."
Solution :
result2=$(echo ${!result1})
You can use eval to achieve this. Be warned though, that using eval is almost always a bad idea, as it has glaring security issues (rooted in its design -- it is meant to execute everything passed to it) and even apart from that, all kinds of things might go wrong when a variable has an unexpected value.
result=${var1}-text-${var2}
eval ${var1}'_text_'${var2}=bingo
echo $ab_text_cd
Also, environment variables cannot have dashes (-) as part of the variable name, so I replaced them by underscores (_) for the example.

Use bash variable to access command arguments? [duplicate]

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
Let's say I have a variable's name stored in another variable:
myvar=123
varname=myvar
Now, I'd like to get 123 by just using $varname variable.
Is there a direct way for that? I found no such bash builtin for lookup by name, so came up with this:
function var { v="\$$1"; eval "echo "$v; }
so
var $varname # gives 123
Which doesn't look too bad in the end, but I'm wondering if I missed something more obvious.
From the man page of bash:
${!varname}
If the first character of parameter is an exclamation point, a level of
variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable;
this variable is then expanded and that value is used in the rest of
the substitution, rather than the value of parameter itself. This is
known as indirect expansion.
There isn't a direct Posix-conforming syntax, only a bashism. I usually do this:
eval t="\$$varname"
This will work on any Posix shell, including those systems where bash is the login shell and /bin/sh is something smaller and faster like ash. I like bash and use it for my login shell but I avoid bashisms in command files.
Note: One problem with writing bash-specific scripts is that even if you can count on bash being installed, it could be anywhere on the path. It might be a good idea in that case to use the fully general /usr/bin/env shebang style, but note that this is still not 100% portable and has security issues.
${!varname} should do the trick
$ var="content"
$ myvar=var
$ echo ${!myvar}
content
I usually look at Advance Bash-Scripting Guide when I need to freshen up my Bash skills.
Regarding your question look at Indirect References
Notation is:
Version < 2
\$$var
Version >= 2
${!varname}
# bmuSetIndirectVar()
# TO DOUBLE CHECK THIS COMMENT AND DEMO
# This function is an helper to read indirect variables.
# i.e. get the content of a variable whose name is saved
# within an other variable. Like:
# MYDIR="/tmp"
# WHICHDIR="MYDIR"
# bmuSetIndirectVar "WHICHDIR" "$MYDIR"
#
bmuSetIndirectVar(){
tmpVarName=$1
locVarName=$1
extVarName=$2
#echo "debug Ind Input >$1< >$2<"
eval tmpVarName=\$$extVarName
#echo "debug Ind Output >$tmpVarName< >$extVarName<"
export $locVarName="${tmpVarName}"
}
I am currently using this little function. I am not fully happy with it, and I have seen different solutions on the web (if I could recall I would write them here), but it seems to work. Within these few lines there is already some redundancy and extra data but it was helpful for debugging.
If you want to see it in place, i.e. where I am using it, check:
https://github.com/mariotti/bmu/blob/master/bin/backmeup.shellfunctions.sh
Of course it is not the best solution, but made me going on with the work, in
the hope I can replace it with something a bit more general soon.

Substitute a bash script variable twice

I would like to know if I can substitute a variable twice.
For example:
#global variable
TEST_SERV_EXT=""
#variables become from myconf.sh
TEST_SERV_EXT_FO='foo01'
TEST_SERV_EXT_BR='bar01'
I want dynamically construct those last two and assign them in TEST_SERV_EXT.
I tried something like this ${$TEST_SERV_COMP} but I'm getting "bad substitution" message.
I need something like php's feature "$$" or tcl's subst command.
Regards,
thandem
TEST_SERV_COMP=TEST_SERV_EXT_FO
TEST_SERV_EXT=${!TEST_SERV_COMP}
Look for indirect expansion in the bash manual.

Tricky brace expansion in shell

When using a POSIX shell, the following
touch {quick,man,strong}ly
expands to
touch quickly manly strongly
Which will touch the files quickly, manly, and strongly, but is it possible to dynamically create the expansion? For example, the following illustrates what I want to do, but does not work because of the order of expansion:
TEST=quick,man,strong #possibly output from a program
echo {$TEST}ly
Is there any way to achieve this? I do not mind constricting myself to Bash if need be. I would also like to avoid loops. The expansion should be given as complete arguments to any arbitrary program (i.e. the program cannot be called once for each file, it can only be called once for all files). I know about xargs but I'm hoping it can all be done from the shell somehow.
... There is so much wrong with using eval. What you're asking is only possible with eval, BUT what you might want is easily possible without having to resort to bash bug-central.
Use arrays! Whenever you need to keep multiple items in one datatype, you need (or, should use) an array.
TEST=(quick man strong)
touch "${TEST[#]/%/ly}"
That does exactly what you want without the thousand bugs and security issues introduced and concealed in the other suggestions here.
The way it works is:
"${foo[#]}": Expands the array named foo by expanding each of its elements, properly quoted. Don't forget the quotes!
${foo/a/b}: This is a type of parameter expansion that replaces the first a in foo's expansion by a b. In this type of expansion you can use % to signify the end of the expanded value, sort of like $ in regular expressions.
Put all that together and "${foo[#]/%/ly}" will expand each element of foo, properly quote it as a separate argument, and replace each element's end by ly.
In bash, you can do this:
#!/bin/bash
TEST=quick,man,strong
eval echo $(echo {$TEST}ly)
#eval touch $(echo {$TEST}ly)
That last line is commented out but will touch the specified files.
Zsh can easily do that:
TEST=quick,man,strong
print ${(s:,:)^TEST}ly
Variable content is splitted at commas, then each element is distributed to the string around the braces:
quickly manly strongly
Taking inspiration from the answers above:
$ TEST=quick,man,strong
$ touch $(eval echo {$TEST}ly)

Resources