Bash indirect variable reference
This is really quite awkward in bash: you require a temporary variable to hold the constructed variable name:
$ var_1=hello
$ num=1
$ tmp=var_$num
$ echo ${!tmp}
hello
Arrays are much simpler
$ var=( [1]=world )
$ echo ${var[$num]}
world
Bash indirect variable referencing
Use bash
indirect variable reference:
${!var}
And of course can be done with eval
, not recommended:
eval 'echo $'"$var"
Why:
$ bar=xyz
$ var='bar;whoami'
$ eval 'echo $'"$var"
xyz
spamegg
Th command whoami
is being evaluated too as part of evaluation by eval
, imagine a destructive command instead of whoami
.
Example:
$ bar='xyz'
$ var=bar
$ echo "${!var}"
xyz
$ eval 'echo $'"$var"
xyz
What is indirect expansion? What does ${!var*} mean?
If you read the bash
man page, it basically confirms what you have stated:
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.
However, reading on from there:
The exceptions to this are the expansions of
${!prefix*}
and${!name[@]}
described below.
${!prefix*}
Names matching prefix. Expands to the names of variables whose names begin with prefix, separated by the first character of theIFS
special variable.
In other words, your particular example ${!N*}
is an exception to the rule you quoted. It does, however, work as advertised in the expected cases, such as:
$ export xyzzy=plugh ; export plugh=cave
$ echo ${xyzzy} # normal, xyzzy to plugh
plugh
$ echo ${!xyzzy} # indirection, xyzzy to plugh to cave
cave
Using indirect variable references in a Bash script
It seems like you're looking for an indirect variable reference. Your $cmd
variable points to the variable $Apr
and you want to use the value of $Apr
.
Here's how you can do this (just the relevant lines):
cmd=$(date | awk '{print $2}')
eval month=\$$cmd
dat=$(ls -ltr | grep $month | awk '{print $9}')
The eval
command resolves the indirect reference created by \$$cmd
.
More details here: http://www.tldp.org/LDP/abs/html/ivr.html
Indirect variable assignment in bash
The main point is that the recommended way to do this is:
eval "$var=\$val"
with the RHS done indirectly too. Since eval
is used in the same
environment, it will have $val
bound, so deferring it works, and since
now it's just a variable. Since the $val
variable has a known name,
there are no issues with quoting, and it could have even been written as:
eval $var=\$val
But since it's better to always add quotes, the former is better, or
even this:
eval "$var=\"\$val\""
A better alternative in bash that was mentioned for the whole thing that
avoids eval
completely (and is not as subtle as declare
etc):
printf -v "$var" "%s" "$val"
Though this is not a direct answer what I originally asked...
Bash indirect reference where one var references multiple
eval sound quite scary...
here is a better solution:
Bash Templating: How to build configuration files from templates with Bash?
you just have to tweak it a bit so it follows your syntax.
How to iterate over an array using indirect reference?
${!ARRAYNAME[@]}
means "the indices of ARRAYNAME
". As stated in the bash man page since ARRAYNAME
is set, but as a string, not an array, it returns 0
.
Here's a solution using eval
.
#!/usr/bin/env bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
eval array=\( \${${ARRAYNAME}[@]} \)
for fruit in "${array[@]}"; do
echo ${fruit}
done
What you were originally trying to do was create an Indirect Reference. These were introduced in bash version 2 and were meant to largely replace the need for eval
when trying to achieve reflection-like behavior in the shell.
What you have to do when using indirect references with arrays is include the [@]
in your guess at the variable name:
#!/usr/bin/env bash
ARRAYNAME='FRUITS'
FRUITS=( APPLE BANANA ORANGE )
array="${ARRAYNAME}[@]"
for fruit in "${!array}"; do
echo $fruit
done
All that said, it's one thing to use Indirect References in this trivial example, but, as indicated in the link provided by Dennis Williamson, you should be hesitant to use them in real-world scripts. They are all but guaranteed to make your code more confusing than necessary. Usually you can get the functionality you need with an Associative Array.
Declaring variables with indirect reference with a prefix
If your Bash is recent enough, then use the -n
indirect variable attribute like this:
#!/usr/bin/env bash
source values.sh
read -r -p 'Enter Identifier : ' pk
declare -n \
value1="${pk}_value1" \
value2="${pk}_value2" \
value3="${pk}_value3"
Alternate method with populating an associative array from the file values.sh
:
#!/usr/bin/env bash
declare -A values="($(
xargs -l1 \
bash -c \
'IFS="=" read -r k v <<<"$@"; printf "[%q]=%q\n" "$k" "$v"' _ \
<values.sh
))"
read -r -p 'Enter Identifier : ' pk
declare -- \
value1="${values[${pk}_value1]}" \
value2="${values[${pk}_value2]}" \
value3="${values[${pk}_value3]}"
Working of the Associative array population:
xargs -l1
will translate the stdio
input stream's lines (here: <values.sh
) into arguments to a command.
The command called by xargs
is bash -c
, witch executes an inline script detailed here:
# Read variables k and v from the arguments
# streamed as a here-string <<<"",
# using the = sign as the Internal Field Separator.
# Actually splitting key=value into k and v.
IFS="=" read -r k v <<<"$@"
# Format variables k and v into an Associative array
# entry declaration in the form [key]=value,
# with %q adding quotes or escaping if required.
printf "[%q]=%q\n" "$k" "$v"
Finally the Associative array declaration and assignment declare -A values="($(commands))"
gets the entries generated by the xarg
and inline shell script commands
.
bash indirect reference by reference
I think that would be unsafe. If $1
gets a value that's not dev or stg it would cause syntax error and other unexpected things may happen. Even with eval
it wouldn't be a good idea unless you add a conditional filter. You could use a case
statement:
case "$1" in
dev)
"$dev_path"/bin/execute
;;
std)
"$std_path"/bin/execute
;;
*)
echo "Invalid argument: $1"
;;
esac
Or make sure it's valid and use eval:
[[ $1 == dev || $1 == std ]] && eval "\"\${${1}_path}/bin/execute\""
Using if ... elif ...
could be valid too but the case
method is simpler.
As for variable indirection
you would need to store "${1}_path"
to another variable first which is unnecessary.
You could also use dev|std) eval ... ;;
in the case
statement at your preference.
Related Topics
How to Build a Linux Kernel Module So That It Is Compatible with All Kernel Releases
If I Have a Process, and I Clone It, Is the Pid the Same
Bash Script to Find and Display Oldest File
Cuda Compiler Not Working with Gcc 4.5 +
-Bash: /Usr/Bin/Yum: /Usr/Bin/Python: Bad Interpreter: No Such File or Directory
Linux Command or Script Counting Duplicated Lines in a Text File
Need Explanations for Linux Bash Builtin Exec Command Behavior
Count Occurrences of Character Per Line/Field on Unix
How to Recover After Deleting the Symbolic Link Libc.So.6
List of Possible Internal Socket Statuses from /Proc
Pyqt5 Error "Pycapsule_Getpointer Called with Incorrect Name"
Different Results Between Ps Aux and 'Ps Aux' Inside a Script
Qt Creator: Add Qt Module to Project
Why Didn't I Get Segmentation Fault When Storing Past the End of the Bss
Turning Multiple Lines into One Comma Separated Line
Linux Shell to Restrict Sftp Users to Their Home Directories