Bash printing value of array reference - bash

p=0
array="host_01"
HostProjects[$p]="project_01"
AvgCr=1569.22
eval "${array}_${HostProjects[$p]}=$AvgCr"
echo "Host Credit is ${host_01_project_01}"
Gives me 1569.22
But how can I get the result 1569.22 from:
printf '%s\n' "${array}_${HostProjects[$p]}"
or even from:
echo "${array}_${HostProjects[$p]}"
Which gives me host_01_project_01
I have tried several things but it ends up in syntax errors.
Thanks.

If you're using Bash, indirect parameter expansion is your friend:
varname="${array}_${HostProjects[$p]}"
echo "The value you want is ${!varname}"

Related

How to do a bash `for` loop in terraform termplatefile?

I'm trying to include a bash script in an AWS SSM Document, via the Terraform templatefile function. In the aws:runShellScript section of the SSM document, I have a Bash for loop with an # sign that seems to be creating an error during terraform validate.
Version of terraform: 0.13.5
Inside main.tf file:
resource "aws_ssm_document" "magical_document" {
name = "magical_ssm_doc"
document_type = "Command"
document_format = "YAML"
target_type = "/AWS::EC2::Instance"
content = templatefile(
"${path.module}/ssm-doc.yml",
{
Foo: var.foo
}
)
}
Inside my ssm-doc.yaml file, I loop through an array:
for i in "$\{arr[#]\}"; do
if test -f "$i" ; then
echo "[monitor://$i]" >> $f
echo "disabled=0" >> $f
echo "index=$INDEX" >> $f
fi
done
Error:
Error: Error in function call
Call to function "templatefile" failed:
./ssm-doc.yml:1,18-19: Invalid character;
This character is not used within the language., and 1 other diagnostic(s).
I tried escaping the # symbol, like \#, but it didn't help. How do I
Although the error is pointing to the # symbol as being the cause of the error, it's the ${ } that's causing the problem, because this is Terraform interpolation syntax, and it applies to templatefiles too. As the docs say:
The template syntax is the same as for string templates in the main Terraform language, including interpolation sequences delimited with ${ ... }.
And the way to escape interpolation syntax in Terraform is with a double dollar sign.
for i in "$${arr[#]}"; do
if test -f "$i" ; then
echo "[monitor://$i]" >> $f
echo "disabled=0" >> $f
echo "index=$INDEX" >> $f
fi
done
The interpolation syntax is useful with templatefile if you're trying to pass in an argument, such as, in the question Foo. This argument could be accessed within the yaml file as ${Foo}.
By the way, although this article didn't give the answer to this exact issue, it helped me get a deeper appreciation for all the work Terraform is doing to handle different languages via the templatefile function. It had some cool tricks for doing replacements to escape for different scenarios.

bash compute average return bad answer

I have a script with:
#!/bin/bash
mark=10+10
nb=2
echo $mark
echo $(($mark/$nb))
I don't understand why the second echo return 15.
It's because the precendence of operators, and because mark is not evaluated itself (in a math-sense of evaulation).
echo $(($mark/$nb)) is first replace to $((10+10/2)) which is then evaluated to 10+5 which is 5.
There are numerous solutions to the problem, e.g:
echo $((mark/nb)
echo $((($mark)/$nb))
Try this instead:
#!/bin/bash
mark=$((10+10))
nb=2
echo $mark
echo $(($mark/$nb))
The reason is that when assigning 10+10 no result is calculated. Instead 10+10/$nb is executed in the last line. And the result is 15 of course.

Iterating over variable name in bash script

I needed to run a script over a bunch of files, which paths were assigned to train1, train2, ... , train20, and I thought 'why not make it automatic with a bash script?'.
So I did something like:
train1=path/to/first/file
train2=path/to/second/file
...
train20=path/to/third/file
for i in {1..20}
do
python something.py train$i
done
which didn't work because train$i echoes train1's name, but not its value.
So I tried unsuccessfully things like $(train$i) or ${train$i} or ${!train$i}.
Does anyone know how to catch the correct value of these variables?
Use an array.
Bash does have variable indirection, so you can say
for varname in train{1..20}
do
python something.py "${!varname}"
done
The ! introduces the indirection, so "get the value of the variable named by the value of varname"
But use an array. You can make the definition very readable:
trains=(
path/to/first/file
path/to/second/file
...
path/to/third/file
)
Note that this array's first index is at position zero, so:
for ((i=0; i<${#trains[#]}; i++)); do
echo "train $i is ${trains[$i]}"
done
or
for idx in "${!trains[#]}"; do
echo "train $idx is ${trains[$idx]}"
done
You can use array:
train[1]=path/to/first/file
train[2]=path/to/second/file
...
train[20]=path/to/third/file
for i in {1..20}
do
python something.py ${train[$i]}
done
Or eval, but it awfull way:
train1=path/to/first/file
train2=path/to/second/file
...
train20=path/to/third/file
for i in {1..20}
do
eval "python something.py $train$i"
done

Bash array expansion using variable indirection expansion

I am trying to expand all values in an array I get to through indirect expansion:
> my_array=(coconut banana)
> echo "${my_array[#]}"
coconut banana
> my_array_name=my_array
> echo ${!my_array_name}
coconut
> echo "${!my_array_name[#]}"
0
I am erroneously using "List of array keys" in the last command because I don't know how to type the right command?
I would like to get:
coconut banana
possibly without resorting to some ugly eval hack.. Example of one suck hack:
> echo \${$my_array_name[#]}
${my_array[#]}
> eval echo \${$my_array_name[#]}
coconut banana
Note
my_array may contain values with spaces!
EDIT
In the function I am writing, my_array_name is set through "$1" so I cannot use that literally.
Similar to: https://unix.stackexchange.com/questions/20171/indirect-return-of-all-elements-in-an-array but I need to avoid using eval to protect from the nasty effects the script would have if the environment was "hacked" just at the right time...
This should work
my_array_name='my_array[#]'
echo "${!my_array_name}"
After comment : you have to create a string with the name of the array and '[#]', another example
my_array_name="$1"'[#]'
echo "${!my_array_name}"
After comment : test in a function
display_elem() {
local arrname
arrname="$1[#]"
printf "%s\n" "${!arrname}"
}
display_elem my_array
The problem is my_array_name=my_array. You need to retrieve all values of my_array. Try this instead:
my_array_name=${my_array[#]}
echo "${my_array_name[#]}"

Properly Specify an Array and Element through Variables in a Shell Script

Consider the following nonsense array:
# KIND[ID]=NAME
MONKEYS[1]="Oo Oo"
MONKEYS[2]="Aa Aa"
MONKEYS[3]="Ba Nana"
LIONS[5]="Mister Mufasa"
LIONS[7]="Cocoa Puff"
LIONS[8]="Lala Leo"
TIGERS[13]="Ben Gal"
TIGERS[15]="Tee Eye Double Guh Err"
TIGERS[22]="Oh Esex Diez Punto Cuatro"
With a given KIND and ID, I'm attempting to build a string that resembles $NAME[$ID] to get the associated name.
When explicitly stating an array name, the command behaves as expected echo "${LIONS[5]}"=>"Mister Mufasa"). However, whenever a variable is used, the shell responds with the given character in the string.
$LIONS[5] => 'e' # The fifth letter in "Mister Mufasa"
In other cases, I can't find a way to control interpolation to get the NAME
KIND="LIONS"
ID="5"
# Attempt to return value of `LIONS` when `KIND=LIONS`
echo $"${KIND}"; echo "\$${KIND}" #=> "$LIONS"
echo "$${KIND}" #=> "57800{KIND}" Interpolates "$$"
echo "\$\${KIND}"; "\$\${KIND}" #=> "$${KIND}"
I found the following works albeit "ugly"...
eval echo `echo \\$${KIND}`
However when introducing the ID things break once again:
eval echo `echo \\$${KIND}[$ID]`
#> title:5: no matches found: $LIONS[5]
#> no matches found: $LIONS[5]
I feel like I'm missing something very simple. I have a hunch I'm forgetting to escape something, but I'm not quite sure what.
Also, what "less redundant" alternatives to eval echo `echo... or eval echo `print... exist?
In bash, use indirect addressing:
REF="$KIND[$ID]" # Sets REF to "LIONS[5]"
echo "${!REF}" # Prints "Mister Mufasa"
EDIT: In zsh, use nested expansion instead:
echo "${(P)${KIND}[ID]}"

Resources