How can I read out the values of dynamically declared bash variables? - bash

I have some trouble with a bash script, maybe someone can help me.
Inside my script, I defined variables dynamically using a loop like this:
somecolors="red yellow green blue" # read out of a file, may vary
for color in $(echo $somecolors); do
# Actually, here is more code that generates the value I
# want to set for this variable, that is being written
# into "$value"
declare use_color_$color=$value
done
The result is that four variables have been defined:
use_color_red=1
use_color_yellow=1
use_color_green=1
use_color_blue=1
So far so good. But how can I dynamically read these? I thought of suing a "for" loop. For example:
for color in $(echo $colors); do
echo $use_color_${color}
done
But this does not work.
How can I compose two variable names to a single one?
Thanks in advance!

Better use indexed and associative arrays instead. Referencing and dereferencing variable variables that way is really wrong.
somecolors=(red yellow green blue)
declare -A use_color
for color in "${colors[#]}"; do
use_color[$color]=$value ## Or do you mean use_color[$color]=$color?
done
Granting $value == 1, when you do echo "${use_color[red]}" you'd get 1.
One variation:
declare colors=(red yellow green blue)
declare -A use_color
use_color[red]=1
use_color[yellow]=1
use_color[green]=1
use_color[blue]=1
for color in "${colors[#]}"; do
echo "use_color[$color]=${use_color[$color]}"
done
Output:
use_color[red]=1
use_color[yellow]=1
use_color[green]=1
use_color[blue]=1
Similarly:
declare -A use_color=([red]=1 [yellow]=1 [green]=1 [blue]=1)
for color in "${!use_color[#]}"; do
echo "use_color[$color]=${use_color[$color]}"
done
Output:
use_color[yellow]=1
use_color[red]=1
use_color[blue]=1
use_color[green]=1

The cleanest way is using variable substitution. A variable of the form ${!varabc} will match all previously defined variables beginning with varabc. In your case:
#!/bin/bash
use_color_red=1
use_color_yellow=1
use_color_green=1
use_color_blue=1
for i in ${!use_color#}; do
printf " name: %-16s value: %d\n" $i ${!i}
done
exit 0
output:
name: use_color_blue value: 1
name: use_color_green value: 1
name: use_color_red value: 1
name: use_color_yellow value: 1

Related

Double Parameter substitution to get variable value [duplicate]

I'm trying to set up my PS1 prompt variable to dynamically choose a color. To do this, I've defined a bunch of local variables with color names:
$ echo $Green
\033[0;32m
but I was hoping to use those in dynamically assigning variables, but I can't figure out how to expand them properly:
> colorstr="\${$color}"
> echo $colorstr
${Green}
I've tried a dozen combinations of eval, echo, and double-quotes, and none seem to work. The logical way (I thought) to expand the variable results in an error:
> colorstr="${$color}"
-bash: ${$color}: bad substitution
(for clarity I've used > instead of $ for the prompt character, but I am using bash)
How can I expand that variable? i.e., somehow get the word "Green" to the value \033[0;32m? And prefereably, have bash or the terminal parse that \033[0;32m as the color green too.
EDIT: I was mis-using ${!x} and eval echo $x previously, so I've accepted those as solutions. For the (perhaps morbidly) curious, the functions and PS1 variable are on this gist: https://gist.github.com/4383597
Using eval is the classic solution, but bash has a better (more easily controlled, less blunderbuss-like) solution:
${!colour}
The Bash (4.1) reference manual says:
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.
For example:
$ Green=$'\033[32;m'
$ echo "$Green" | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$ colour=Green
$ echo $colour
Green
$ echo ${!colour} | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$
(The odx command is very non-standard but simply dumps its data in a hex format with printable characters shown on the right. Since the plain echo didn't show anything and I needed to see what was being echoed, I used an old friend I wrote about 24 years ago.)
Using eval should do it:
green="\033[0;32m"
colorstr="green"
eval echo -e "\$$colorstr" test # -e = enable backslash escapes
test
The last test is in color green.
Bash supports associative arrays. Don't use indirection when you could use a dict. If you don't have associative arrays, upgrade to bash 4, ksh93, or zsh. Apparently mksh is adding them eventually as well, so there should be plenty of choice.
function colorSet {
typeset -a \
clrs=(black red green orange blue magenta cyan grey darkgrey ltred ltgreen yellow ltblue ltmagenta ltcyan white) \
msc=(sgr0 bold dim smul blink rev invis)
typeset x
while ! ${2:+false}; do
case ${1#--} in
setaf|setab)
for x in "${!clrs[#]}"; do
eval "$2"'[${clrs[x]}]=$(tput "${1#--}" "$x")'
done
;;
misc)
for x in "${msc[#]}"; do
eval "$2"'[$x]=$(tput "$x")'
done
;;
*)
return 1
esac
shift 2
done
}
function main {
typeset -A fgColors bgColors miscEscapes
if colorSet --setaf fgColors --setab bgColors --misc miscEscapes; then
if [[ -n ${1:+${fgColors[$1]:+_}} ]]; then
printf '%s%s%s\n' "${fgColors[${1}]}" "this text is ${1}" "${miscEscapes[sgr0]}"
else
printf '%s, %s\n' "${1:-Empty}" 'no such color.' >&2
return 1
fi
else
echo 'Failed setting color arrays.' >&2
return 1
fi
}
main "$#"
Though we're using eval, it's a different type of indirection for a different reason. Note how all the necessary guarantees are made for making this safe.
See also: http://mywiki.wooledge.org/BashFAQ/006
You will want to write an alias to a function. Check out http://tldp.org/LDP/abs/html/functions.html, decent little tutorial and some examples.
EDIT:
Sorry, looks like I misunderstood the issue. First it looks like your using the variables wrong, check out http://www.thegeekstuff.com/2010/07/bash-string-manipulation/. Also, what is invoking this script? Are you adding this to the .bash_profile or is this a script your users can launch? Using export should make the changes take effect right away without needed relog.
var Green="\[\e[32m\]"
var Red="\[\e41m\]"
export PS1="${Green} welcome ${Red} user>"
Your first result shows the problem:
$ echo $Green
\033[0;32m
The variable Green contains an string of a backlash, a zero, a 3, etc..
It was set by: Green="\033[0;32m". As such it is not a color code.
The text inside the variable needs to be interpreted (using echo -e, printf or $'...').
Let me explain with code:
$ Green="\033[0;32m" ; echo " $Green test "
\033[0;32m test
What you mean to do is:
$ Green="$(echo -e "\033[0;32m" )" ; echo " $Green test "
test
In great color green. This could print the color but will not be useful for PS1:
$ Green="\033[0;32m" ; echo -e " $Green test "
test
As it means that the string has to be interpreted by echo -e before it works.
An easier way (in bash) is :
$ Green=$'\033[0;32m' ; echo " $Green test "
test
Please note the ` $'...' `
Having solved the issue of the variable Green, accesing it indirectly by the value of var colorstr is a second problem that could be solved by either:
$ eval echo \$$colorstr testing colors
testing colors
$ echo ${!colorstr} testing colors
testing colors
Note Please do not work with un-quoted values (as I did here because the values were under my control) in general. Learn to quote correctly, like:
$ eval echo \"\$$colorstr testing colors\"
And with that, you could write an PS1 equivalent to:
export PS1="${Green} welcome ${Red} user>"
with:
Green=$'\033[0;32m' Red=$'\033[0;31m'
color1=Green color2=Red
export PS1="${!color1} welcome ${!color2} user>"

Can IFS be changed locally in a Bash function?

I have a function that needs to change IFS for its logic:
my_func() {
oldIFS=$IFS; IFS=.; var="$1"; arr=($var); IFS=$oldIFS
# more logic here
}
Can I declare IFS as local IFS inside the function so that I don't need to worry about backing its current value and restore later?
It appears to work as you desire.
#!/bin/bash
changeIFSlocal() {
local IFS=.
echo "During local: |$IFS|"
}
changeIFSglobal() {
IFS=.
echo "During global: |$IFS|"
}
echo "Before: |$IFS|"
changeIFSlocal
echo "After local: |$IFS|"
changeIFSglobal
echo "After global: |$IFS|"
This prints:
Before: |
|
During local: |.|
After local: |
|
During global: |.|
After global: |.|
Yes it can be defined!
As long as you define it local, setting of the value in the function does not affect the global IFS value. See the difference between the snippets below
addNumbers () {
local IFS='+'
printf "%s\n" "$(( $* ))"
}
when called in command-line as,
addNumbers 1 2 3 4 5 100
115
and doing
nos=(1 2 3 4 5 100)
echo "${nos[*]}"
from the command line. The hexdump on the above echo output wouldn't show the IFS value defined in the function
echo "${nos[*]}" | hexdump -c
0000000 1 2 3 4 5 1 0 0 \n
000000e
See in one of my answers, how I've used the localized IFS to do arithmetic - How can I add numbers in a bash script
I got confused because I changed the value of IFS to : inside the function (without using local) and then tried to display the value of IFS with this command, after calling the function:
echo $IFS
which displayed an empty line that made me feel the function wasn't changing IFS. After posting the question, I realized that word splitting was at play and I should have used
echo "$IFS"
or
printf '%s\n' "$IFS"
or, even better
set | grep -w IFS=
to accurately display the IFS value.
Coming back to the main topic of local variables, yes, any variable can be declared as local inside a function to limit the scope, except for variables that have been declared readonly (with readonly or declare -r builtin commands). This includes Bash internal variables like BASH_VERSINFO etc.
From help local:
local: local [option] name[=value] ...
Define local variables.
Create a local variable called NAME, and give it VALUE. OPTION can
be any option accepted by `declare'.
Local variables can only be used within a function; they are visible
only to the function where they are defined and its children.
Exit Status:
Returns success unless an invalid option is supplied, a variable
assignment error occurs, or the shell is not executing a function.
You can designate IFS as a local variable; the local version is still used as the field separator string.
Sometimes it is useful to run a function in a completely isolated environment, where no changes are permanent. (For example, if the function needs to change shell options.) This can be achieved by making the function run in a subshell; just change the {} in the function definition to ():
f() (
shopt -s nullglob
IFS=.
# Commands to run in local environment
)

Open file with two columns and dynamically create variables

I'm wondering if anyone can help. I've not managed to find much in the way of examples and I'm not sure where to start coding wise either.
I have a file with the following contents...
VarA=/path/to/a
VarB=/path/to/b
VarC=/path/to/c
VarD=description of program
...
The columns are delimited by the '=' and some of the items in the 2nd column may contain gaps as they aren't just paths.
Ideally I'd love to open this in my script once and store the first column as the variable and the second as the value, for example...
echo $VarA
...
/path/to/a
echo $VarB
...
/path/to/a
Is this possible or am I living in a fairy land?
Thanks
You might be able to use the following loop:
while IFS== read -r name value; do
declare "$name=$value"
done < file.txt
Note, though, that a line like foo="3 5" would include the quotes in the value of the variable foo.
A minus sign or a special character isn't allowed in a variable name in Unix.
You may consider using BASH associative array for storing key and value together:
# declare an associative array
declare -A arr
# read file and populate the associative array
while IFS== read -r k v; do
arr["$k"]="$v"
done < file
# check output of our array
declare -p arr
declare -A arr='([VarA]="/path/to/a" [VarC]="/path/to/c" [VarB]="/path/to/b" [VarD]="description of program" )'
What about source my-file? It won't work with spaces though, but will work for what you've shared. This is an example:
reut#reut-home:~$ cat src
test=123
test2=abc/def
reut#reut-home:~$ echo $test $test2
reut#reut-home:~$ source src
reut#reut-home:~$ echo $test $test2
123 abc/def

read multiple values from a property file using bash shell script

Would like to read multiple values from a property file using a shell script
My properties files looks something like below, the reason I added it following way was to make sure, if in future more students joins I just need to add in in the properties file without changing any thing in the shell script.
student.properties
total_student=6
student_name_1="aaaa"
student_name_2="bbbb"
student_name_3="cccc"
student_name_4="dddd"
student_name_5="eeee"
When I run below script I not getting the desired output, for reading the student names from properties file
student.sh
#!/bin/bash
. /student.properties
i=1
while [ $i -lt $total_student ]
do
{
std_Name=$student_name_$i
echo $std_Name
#****** my logic *******
} || {
echo "ERROR..."
}
i=`expr $i + 1`
done
Output is something like this
1
2
3
4
5
I understand the script is not getting anything for $student_name_ hence only $i value is getting printed.
Hence, wanted to know how to read values from the properties file.
You can do variable name interpolation with ${!foo}. If $foo is "bar", then ${!foo} gives you the value of $bar. In your code that means changing
std_Name=$student_name_$i
to
var=student_name_$i
std_Name=${!var}
Alternatively, you could store the names in an array. Then you wouldn't have to do any parsing.
student.properties
student_names=("aaaa" "bbbb" "cccc" "dddd" "eeee")
student.sh
#!/bin/bash
. /student.properties
for student_name in "${student_names[#]}"; do
...
done
You can use indirect expansion:
std_Name=student_name_$i
echo "${!std_Name}"
the expression ${!var} basically evaluates the variable twice:
first evaluation: student_name_1
second evaluation: foo
Note that this is rarely a good idea and that using an array is almost always preferred.

Bash expand variable in a variable

I'm trying to set up my PS1 prompt variable to dynamically choose a color. To do this, I've defined a bunch of local variables with color names:
$ echo $Green
\033[0;32m
but I was hoping to use those in dynamically assigning variables, but I can't figure out how to expand them properly:
> colorstr="\${$color}"
> echo $colorstr
${Green}
I've tried a dozen combinations of eval, echo, and double-quotes, and none seem to work. The logical way (I thought) to expand the variable results in an error:
> colorstr="${$color}"
-bash: ${$color}: bad substitution
(for clarity I've used > instead of $ for the prompt character, but I am using bash)
How can I expand that variable? i.e., somehow get the word "Green" to the value \033[0;32m? And prefereably, have bash or the terminal parse that \033[0;32m as the color green too.
EDIT: I was mis-using ${!x} and eval echo $x previously, so I've accepted those as solutions. For the (perhaps morbidly) curious, the functions and PS1 variable are on this gist: https://gist.github.com/4383597
Using eval is the classic solution, but bash has a better (more easily controlled, less blunderbuss-like) solution:
${!colour}
The Bash (4.1) reference manual says:
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.
For example:
$ Green=$'\033[32;m'
$ echo "$Green" | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$ colour=Green
$ echo $colour
Green
$ echo ${!colour} | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$
(The odx command is very non-standard but simply dumps its data in a hex format with printable characters shown on the right. Since the plain echo didn't show anything and I needed to see what was being echoed, I used an old friend I wrote about 24 years ago.)
Using eval should do it:
green="\033[0;32m"
colorstr="green"
eval echo -e "\$$colorstr" test # -e = enable backslash escapes
test
The last test is in color green.
Bash supports associative arrays. Don't use indirection when you could use a dict. If you don't have associative arrays, upgrade to bash 4, ksh93, or zsh. Apparently mksh is adding them eventually as well, so there should be plenty of choice.
function colorSet {
typeset -a \
clrs=(black red green orange blue magenta cyan grey darkgrey ltred ltgreen yellow ltblue ltmagenta ltcyan white) \
msc=(sgr0 bold dim smul blink rev invis)
typeset x
while ! ${2:+false}; do
case ${1#--} in
setaf|setab)
for x in "${!clrs[#]}"; do
eval "$2"'[${clrs[x]}]=$(tput "${1#--}" "$x")'
done
;;
misc)
for x in "${msc[#]}"; do
eval "$2"'[$x]=$(tput "$x")'
done
;;
*)
return 1
esac
shift 2
done
}
function main {
typeset -A fgColors bgColors miscEscapes
if colorSet --setaf fgColors --setab bgColors --misc miscEscapes; then
if [[ -n ${1:+${fgColors[$1]:+_}} ]]; then
printf '%s%s%s\n' "${fgColors[${1}]}" "this text is ${1}" "${miscEscapes[sgr0]}"
else
printf '%s, %s\n' "${1:-Empty}" 'no such color.' >&2
return 1
fi
else
echo 'Failed setting color arrays.' >&2
return 1
fi
}
main "$#"
Though we're using eval, it's a different type of indirection for a different reason. Note how all the necessary guarantees are made for making this safe.
See also: http://mywiki.wooledge.org/BashFAQ/006
You will want to write an alias to a function. Check out http://tldp.org/LDP/abs/html/functions.html, decent little tutorial and some examples.
EDIT:
Sorry, looks like I misunderstood the issue. First it looks like your using the variables wrong, check out http://www.thegeekstuff.com/2010/07/bash-string-manipulation/. Also, what is invoking this script? Are you adding this to the .bash_profile or is this a script your users can launch? Using export should make the changes take effect right away without needed relog.
var Green="\[\e[32m\]"
var Red="\[\e41m\]"
export PS1="${Green} welcome ${Red} user>"
Your first result shows the problem:
$ echo $Green
\033[0;32m
The variable Green contains an string of a backlash, a zero, a 3, etc..
It was set by: Green="\033[0;32m". As such it is not a color code.
The text inside the variable needs to be interpreted (using echo -e, printf or $'...').
Let me explain with code:
$ Green="\033[0;32m" ; echo " $Green test "
\033[0;32m test
What you mean to do is:
$ Green="$(echo -e "\033[0;32m" )" ; echo " $Green test "
test
In great color green. This could print the color but will not be useful for PS1:
$ Green="\033[0;32m" ; echo -e " $Green test "
test
As it means that the string has to be interpreted by echo -e before it works.
An easier way (in bash) is :
$ Green=$'\033[0;32m' ; echo " $Green test "
test
Please note the ` $'...' `
Having solved the issue of the variable Green, accesing it indirectly by the value of var colorstr is a second problem that could be solved by either:
$ eval echo \$$colorstr testing colors
testing colors
$ echo ${!colorstr} testing colors
testing colors
Note Please do not work with un-quoted values (as I did here because the values were under my control) in general. Learn to quote correctly, like:
$ eval echo \"\$$colorstr testing colors\"
And with that, you could write an PS1 equivalent to:
export PS1="${Green} welcome ${Red} user>"
with:
Green=$'\033[0;32m' Red=$'\033[0;31m'
color1=Green color2=Red
export PS1="${!color1} welcome ${!color2} user>"

Resources