Create dynamic variable name bash and get value - bash

I have a function in bash that get param a string, for example:
MYSQL_DATABASE then I want in my file to create a var named VAR_MYSQL_DATABASE but I can`t get the value.
create_var()
{
read -p "Enter $1 : " VAR_$1
printf VAR_$1 // print VAR_MYSQL_DATABASE_NAME instead what I typed, so how do I get the value?
if [[ -z VAR_$1 ]]; then
printf '%s\n' "No input entered. Enter $1"
create_entry $1
fi
}
create_var "MYSQL_DATABASE_NAME"

Use the declare built-in in bash,
name="MYSQL_DATABASE"
declare VAR_${name}="Some junk string"
printf "%s\n" "${VAR_MYSQL_DATABASE}"
Some junk string
With the above logic, you can modify how your name variable is controlled, either present locally. If it is passed as argument from a function/command-line do
declare VAR_${1}="Your string value here"
Perhaps you want to achieve something like this,
create_var()
{
read -p "Enter value: " value
declare -g VAR_$1="$value"
dynamVar="VAR_$1"
if [[ -z "${!dynamVar}" ]]; then
printf '%s\n' "No input entered. Enter $1"
create_entry $1
fi
}
So, here we are creating the dynamic variable using the declare built-in and with the dynamic part VAR_$1 cannot be referenced directly as normal variables, hence using indirect expansion {!variable} to see if the created variable is available.

Try the following :
#!/bin/bash
create_var() {
declare -g VAR_$1
ref=VAR_$1
read -p "Enter $1: " "VAR_$1"
echo "inside : ${!ref}"
}
create_var "MYSQL_DATABASE_NAME"
echo "output : $VAR_MYSQL_DATABASE_NAME"
declare -g will make sure the variable exists outside of the function scope, and "VAR_$1" is used to dynamically create the variable names.
Output :
Enter MYSQL_DATABASE_NAME: Apple
inside : Apple
output : Apple

You can use the printf function with the -v option to "print" to a variable name.
create_var()
{
while : ; do
IFS= read -r -p "Enter $1 : " value
if [[ -n $value ]]; then
printf -v "VAR_$1" '%s' "$value"
return
fi
printf 'No input entered. Enter %s\n' "$1"
done
}
(I've rewritten your function with a loop to avoid what appeared to be an attempt at recursion.)

Related

Bash - check if argument is string

I am attempting to check if an argument is an array with the following code:
if [[ $(declare -p $1) ]] != *-a*;
Here $1 is a string with the value "123". I get the following error message from bash:
`arrays.bash: line 23: declare: 123: not found
This code works if I pass an array as an argument but not a string. I want to verify that the argument is either an array or an associative array. I have no concern with the contents at this point, I only want the type. Any ideas on how to do this?
After all, why worry about the types, if you are relying on it perhaps your approach is wrong or you may need a strong-typed language
% v=1
% declare -p v
declare -- v="1"
% echo $v
1
% echo ${v[#]}
1
% v[1]=2
% declare -p v
declare -a v=([0]="1" [1]="2")
% echo ${v[#]}
1 2
The Error In The Question
You called yourfunction "$a" instead of yourfunction a, when a=123. Don't do that: You need to pass the name of the variable, not its value.
General Solution: Bash 5.x+
Bash 5 has a new feature called parameter transformation, whereby ${parameter#operator} can perform a variety of actions; one of these is checking the type of the parameter.
myfunc() {
[[ -v "$1" ]] || { echo "No variable named $1 exists" >&2; return 1; }
case ${!1#a} in
*a*) echo "Array";;
*A*) echo "Associative array";;
*i*) echo "Integer";;
"") echo "Default string";;
*) echo "Other/unknown flag set: ${!1#a}";;
esac
}
Older Solution
myfunc() {
local typedesc
typedesc=$(declare -p "$1" 2>/dev/null) || {
echo "No variable named $1 is set" >&2
return 1
}
case $typedesc in
"declare -a"*) echo "Array";;
"declare -A"*) echo "Associative array";;
"declare -i"*) echo "Integer";;
"declare --"*) echo "Regular (default) string variable";;
*) echo "Other/unrecognized type";;
esac
}

Bash check if array of variables have values or not

I have an array of variables. I want to check if the variables have a value using the for loop.
I am getting the values into loop but the if condition is failing
function check {
arr=("$#")
for var in "${arr[#]}"; do
if [ -z $var ] ; then
echo $var "is not available"
else
echo $var "is available"
fi
done
}
name="abc"
city="xyz"
arr=(name city state country)
check ${arr[#]}
For the above I am getting all as available
Expected output is
name is available
city is available
state is not available
country is not available
This is the correct syntax for your task
if [ -z "${!var}" ] ; then
echo $var "is not available"
else
echo $var "is available"
fi
Explanation, this method uses an indirect variable expansion, this construction ${!var} will expand as value of variable which name is in $var.
Changed check function a bit
check () {
for var in "$#"; do
[[ "${!var}" ]] && not= || not="not "
echo "$var is ${not}available"
done
}
And another variant using declare
check () {
for var in "$#"; do
declare -p $var &> /dev/null && not= || not="not "
echo "$var is ${not}available"
done
}
From declare help
$ declare --help
declare: declare [-aAfFgilnrtux] [-p] [name[=value] ...]
Set variable values and attributes.
Declare variables and give them attributes. If no NAMEs are given,
display the attributes and values of all variables.
...
-p display the attributes and value of each NAME
...
Actually all vars can be checked at once using this
check () {
declare -p $# 2>&1 | sed 's/.* \(.*\)=.*/\1 is available/;s/.*declare: \(.*\):.*/\1 is not available/'
}
While indirection is a possible solution, it is not really recommended to use. A safer way would be to use an associative array:
function check {
eval "declare -A arr="${1#*=}
shift
for var in "$#"; do
if [ -z "${arr[$var]}" ] ; then
echo $var "is not available"
else
echo $var "is available"
fi
done
}
declare -A list
list[name]="abc"
list[city]="xyz"
check "$(declare -p list)" name city state country
This returns:
name is available
city is available
state is not available
country is not available
The following question was used to create this answer:
How to rename an associative array in Bash?

Assign system variables provided by the variable names in an array

There is a bunch of variables to assign value. I was able to do it in a stupid way by copy-pasting the same piece of code, and then change the part that is different.
For example, I want to do the following:
export country="US"
export city="LA"
The stupid way, with a user-input interface, is:
printf "\nPlease assign country$ \n" ;
if [[ $country == nil ]] ; then
printf "Current value is nil\n"
else
printf "Current value is: $country\n"
fi ;
printf "country: " ;
read -e -i $country country_
export country=$country_
And for city, I just search-replace "country" with "city" and past the code, which is stupid, but works.
Now, I want to improve the readability, and also maintainability, of the code, buy putting the variable names in a list and then iterate over this list.
The half-worked-out code, after googling is:
declare -a var_list=("country" "city")
for var in ${var_list[*]}
do
printf "\nPlease assign $var \n" ;
if [[ ${!var} == nil ]] ; then
printf "Current value is nil\n"
else
printf "Current value is: ${!var}\n"
fi ;
printf "${bold}$var: ${normal}" ;
read -e -i ${!var} {$var_}
export $var={$var_}
done
The following 2 lines of codes are still not correct to do what I want them to do:
read -e -i ${!var} {$var_}
export $var={$var_}
I would like to get some help on that.
Make a function from it and pass variable name to it:
get() {
# descriptive variable names
local var previousvalue
var="$1"
previousvalue="${!1}"
# superfluous, servers as a documentation
# The string "$1" should be a global variable
declare -g "$var"
# Asking the real questions:
printf "\nPlease assign $var \n"
printf "Current value is '$previousvalue'\n"
read -e -p "$1: " -i "$previousvalue" "$var"
}
declare -a var_list=("country" "city")
for i in "${var_list[#]}"; do # or just `for i in country city; do`
get "$i"
done
echo
echo "country=$country"
echo "city=$city"
example exeuction:
Please assign country
Current value is ''
country: Poland
Please assign city
Current value is ''
city: Warsaw
country=Poland
city=Warsaw
Notes:
Don't use for var in ${var_list[*]}, it will improperly handle array elements with spaces inside them. Do for var in "${var_list[#]}". The "${...[#]}" will properly quote and pass all variables.
The export $var={$var_} line exports the variable named after expansion $var to the string consisting of { the expansion of var_ variable and }. I guess you don't want to include the { } in the value. I guess you wanted to write "${var}_" or "${var_}" - the { have to be after $.
I suggest you avoid resorting to variable indirection and use a function instead :
display_and_read() {
local item_type="$1" previous_value="$2"
printf "\nPlease assign $item_type$ \n" ;
if [[ $previous_value == nil ]] ; then
printf "Current value is nil\n"
else
printf "Current value is: $previous_value\n"
fi ;
printf "$item_type: " ;
}
display_and_read "country" "$country"
read -e -i $country country_
export country=$country_
display_and_read "city" "$city"
read -e -i $city city_
export city=$city_

Bash dynamic var name assignment and access

In the following bash function I would like a new variable called PASS to be created on the first $2 occurrence and then have the new $PASS variable to be tested in the second $2 occurrence.
function ask() {
while read -s -p "Type your $1 and press enter: " $2 && [[ -z "${$2// }" ]]; do
echoboldred -e "\n${1^} can't be blank."
done
}
ask password PASS
The problem is the ${$2// }.
To perform the // on the variable whose name is in $2,
the correct syntax is ${!2// }.
while read -s -p "Type your $1 and press enter: " "$2" && [[ -z "${!2// }" ]]; do
You can also use nameref declared with local -n instead of parameter indirection, it might make your code more readable:
ask() {
local -n foo=$2
while read -srp "Type your $1 and press enter: " foo && ! [[ $foo ]]; do
printf -- "\n%s can't be blank.\n" "${1^}"
done
}
ask password pass
Don't declare your functions with the function keyword and it's also advisable to use -r option with read in case your password has backslashes in it:
-r do not allow backslashes to escape any characters

how to write a Bash function that confirms the value of an existing variable with a user

I have a large number of configuration variables for which I want users to issue confirmation of the values. So, there could be some variable specifying a run number in existence and I want the script to ask the user if the current value of the variable is ok. If the user responds that the value is not ok, the script requests a new value and assigns it to the variable.
I have made an initial attempt at a function for doing this, but there is some difficulty with its running; it stalls. I would value some assistance in solving the problem and also any criticisms of the approach I'm using. The code is as follows:
confirmVariableValue(){
variableName="${1}"
variableValue="${!variableName}"
while [[ "${userInput}" != "n" && "${userInput}" != "y" ]]; do
echo "variable "${variableName}" value: "${variableValue}""
echo "Is this correct? (y: continue / n: change it / other: exit)"
read userInput
# Make the user input lowercase.
userInput="$(echo "${userInput}" | sed 's/\(.*\)/\L\1/')"
# If the user input is "n", request a new value for the variable. If the
# user input is anything other than "y" or "n", exit. If the user input
# is "y", then the user confirmation loop ends.
if [[ "${userInput}" == "n" ]]; then
echo "enter variable "${variableName}" value:"
read variableValue
elif [[ "${userInput}" != "y" && "${userInput}" != "n" ]]; then
echo "terminating"
exit 0
fi
done
echo "${variableValue}"
}
myVariable="run_2014-09-23T1909"
echo "--------------------------------------------------------------------------------"
echo "initial variable value: "${myVariable}""
myVariable="$(confirmVariableValue "myVariable")"
echo "final variable value: "${myVariable}""
echo "--------------------------------------------------------------------------------"
The problem is here:
myVariable="$(confirmVariableValue "myVariable")"
your questions, like
echo "Is this correct? (y: continue / n: change it / other: exit)"
are going into the myVariable and not to the screen.
Try print questions to STDERR, or any other file-descriptor but STDOUT.
Opinion based comment: I would be unhappy with such config-script. It is way too chatty. For me is better:
print out the description and the default value
and ask Press Enter for confirm or enter a new value or <something> for exit>
You can also, use the following technique:
use the bash readline library for the read command with -e
use the -i value for set the default value for the editing
use the printf -v variable to print into variable, so you don't need to use var=$(...) nor any (potentially) dangerous eval...
example:
err() { echo "$#" >&2; return 1; }
getval() {
while :
do
read -e -i "${!1}" -p "$1>" inp
case "$inp" in
Q|q) err "Quitting...." || return 1 ;;
"") err "Must enter some value" ;;
*)
#validate the input here
#and print the new value into the variable
printf -v "$1" "%s" "$inp"
return 0
;;
esac
done
}
somevariable=val1
anotherone=val2
x=val3
for var in somevariable anotherone x
do
getval "$var" || exit
echo "new value for $var is: =${!var}="
done
I would not have them answer "Yes" then type in the new value. Just have them type in the new value if they want one, or leave it blank to accept the default.
This little function lets you set multiple variables in one call:
function confirm() {
echo "Confirming values for several variables."
for var; do
read -p "$var = ${!var} ... leave blank to accept or enter a new value: "
case $REPLY in
"") # empty use default
;;
*) # not empty, set the variable using printf -v
printf -v "$var" "$REPLY"
;;
esac
done
}
Used like so:
$ foo='foo_default_value'
$ bar='default_for_bar'
$ confirm foo bar
Confirming values for several variables.
foo = foo_default_value ... leave blank to accept or enter a new value: bar
bar = default_for_bar ... leave blank to accept or enter a new value:
foo=[bar], bar=[default_for_bar]
Of course, if blank can be a default, then you would need to account for that, like #jm666 use of read -i.

Resources