Creating a list of variables and calling them later bash - bash

I want to create a list of variables,so later i can do a prompts for users and if their input is matching any variable within that list, I want to use that variable to execute it within a command, for example:
var1=a
var2=b
...
read input
(user chooses var1) command $var1,rest of the command
The biggest problem is that this list will be huge, what would be the best solution? Thanks!

You are looking for the associative array feature in bash 4 or later.
declare -A foo
foo[us]="United States"
foo[uk]="Great Britain"
read -p "Region? " region
echo "${foo[$region]}"
If the value of $region is not a defined key, then ${foo[$region]} will be treated like any unset variable.

Related

Use a set of variables that start with the same string in bash

I know something like this is possible with DOS but I am not sure how to do it within bash.
I am writing a script that takes some configuration data: source, name, and destination. There will be a variable number of these in the configuration. I need to iterate over each set.
So, for example:
#!/bin/bash
FOLDER_1_SOURCE="/path/one"
FOLDER_1_NAME="one"
FOLDER_1_DESTINATION="one"
FOLDER_2_SOURCE="/path/two two"
FOLDER_2_NAME="two"
FOLDER_2_DESTINATION="here"
FOLDER_3_SOURCE="/something/random"
FOLDER_3_NAME="bravo"
FOLDER_3_DESTINATION="there"
FOLDER_..._SOURCE="/something/random"
FOLDER_..._NAME="bravo"
FOLDER_..._DESTINATION=""
FOLDER_X_SOURCE="/something/random"
FOLDER_X_NAME="bravo"
FOLDER_X_DESTINATION=""
Then I want to iterate over each set and get the SOURCE and NAME values for each set.
I am not stuck on this format. I just don't know how else to do this. The end goal is that I have 1 or more set of variables with source, name, and destination and then I need to iterate over them.
The answer to this type of question is nearly always "use arrays".
declare -a folder_source folder_name folder_dest
folder_source[1]="/path/one"
folder_name[1]="one"
folder_dest[1]="one"
folder_source[2]="/path/two two"
folder_name[2]="two"
folder_dest[2]="here"
folder_source[3]="/something/random"
folder_name[3]="bravo"
folder_dest[3]="there"
folder_source[4]="/something/random"
folder_name[4]="bravo"
folder_dest[4]=""
for((i=1; i<=${#folder_source[#]}; ++i)); do
echo "$i source:" "${folder_source[$i]}"
echo "$i name:" "${folder_name[$i]}"
echo "$i destination:" "${folder_dest[$i]}"
done
Demo: https://ideone.com/gZn0wH
Bash array indices are zero-based, but we just leave the zeroth slot unused here for convenience.
Tangentially, avoid upper case for your private variables.
AFIK bash does not have a facility to list all variables. A workaround - which also would mimic what is going on in DOS - is to use environment variables and restrict your search to those. In this case, you could do a
printenv|grep ^FOLDER||cut -d = -f 1
This is the same as doing in Windows CMD shell a
SET FOLDER

Convert for loop results into array in bash

I have a table with column called "names". Can i store each name into different variable?
Right now I am using a for loop and storing the results into single variable.
for j in `psql database_name -U admin -c "select names from table;"`
; do
done
When i do echo$j it prints below result
tom
Harry
steve
How do i store each values(names) in different variables?
Creating one variable per value is impractical. Think about it: How would you access these variables? What you want is an array.
You can define the array as follows:
mapfile -t names < <(psql database_name -U admin -c 'select names from table;')
This will add every line printed by psql as an entry of the array names. To access the entries use ${names[0]}, ${names[1]}, and so on. The size of the array is ${#names[#]}.
Since the question is called »Convert for loop results into array in bash« here's another way to build up the array. However, I wouldn't use a for ... in $(...) loop here as names with spaces will be split and symbols like * will expand.
names=()
for name in $(psql database_name -U admin -c 'select names from table;'); do
names+=("$name")
done
If you want the loop to run some additional commands use the first approach in this answer and loop over the array – this is safer:
mapfile ... # see above
for name in "${names[#]}"; do
# do something with $name
done

Read input, save it to a dynamically-named variable and check if given input was empty

Consider a generic ask() function that asks the user a question, reads the input and saves it in a variable named according to one of the function's arguments.
ask() {
local question="$1"
local varname="$2"
echo "$question"
read $varname
}
Suppose I want to ask the user what is his favourite pet and store the answer in a variable named $pet. Usage would be as follows:
ask "What is your favourite pet?" pet
What I want to do and need help with is check if the user's input was empty, and in that case set the user's input to some string. I would be able to do this easily if the name of the variable the user's input is stored in was constant, like so:
if [ -z "$pet" ]; then
pet="foo"
fi
However the name of the variable I want to check whether or not is empty is whatever I pass in as the second argument. How can I check if the variable (named as per the value of $varname) containing the user's input is empty? The solution should be as portable and standard as possible, and must work under bash and zsh specifically.
In bash, ${!varname} gives you the value of the variable whose name is the value of $varname, but as far as I know, this syntax is not supported by zsh. If you want something that works in both bash and zsh, you may have to use the oldfashioned eval value=\${$varname} and then check $value. You should only use this if you know in advance that the value of $varname is a legal variable name; otherwise this is unsafe.
maybe:
ask() {
name=$1;shift
read -r -p "$# >" var
eval "$name='$var'"
}
ask pet "What is your favourite pet?"
pet=${pet:-foo}
echo "PET: $pet"
Based on the input thus far I managed to get a satisfying solution.
eval varname_tmp=\$$varname
if [ -z "$varname_tmp" ]; then
eval "$varname=foo"
fi

Open file in bash script

I've got a bash script accepting several files as input which are mixed with various script's options, for example:
bristat -p log1.log -m lo2.log log3.log -u
I created an array where i save all the index where i can find files in the script's call, so in this case it would be an arrat of 3 elements where
arr_pos[0] = 2
arr_pos[1] = 4
arr_pos[3] = 5
Later in the script I must call "head" and "grep" in those files and i tried this way
head -n 1 ${arr_pos[0]}
but i get this error non runtime
head: cannot open `2' for reading: No such file or directory
I tried various parenthesis combinations, but I can't find which one is correct.
The problem here is that ${arr_pos[0]} stores the index in which you have the file name, not the file name itself -- so you can't simply head it. The array storing your arguments is given by $#.
A possible way to access the data you want is:
#! /bin/bash
declare -a arr_pos=(2 4 5)
echo ${#:${arr_pos[0]}:1}
Output:
log1.log
The expansion ${#:${arr_pos[0]}:1} means you're taking the values ranging from the index ${arr_pos[0]} in the array $#, to the element of index ${arr_pos[0]} + 1 in the same array $#.
Another way to do so, as pointed by #flaschenpost, is to eval the index preceded by $, so that you'd be accessing the array of arguments. Although it works very well, it may be risky depending on who is going to run your script -- as they may add commands in the argument line.
Anyway, you may should try to loop through the entire array of arguments by the beginning of the script, hashing the values you find, so that you won't be in trouble while trying to fetch each value later. You may loop, using a for + case ... esac, and store the values in associative arrays.
I think eval is what you need.
#!/bin/bash
arr_pos[0]=2;
arr_pos[1]=4;
arr_pos[2]=5;
eval "cat \$${arr_pos[1]}"
For me that works.

Bash script execute shell command with Bash variable as argument

I have one loop that creates a group of variables like DISK1, DISK2... where the number at the end of the variable name gets created by the loop and then loaded with a path to a device name. Now I want to use those variables in another loop to execute a shell command, but the variable doesn't give its contents to the shell command.
for (( counter=1 ; counter<=devcount ; counter++))
do
TEMP="\$DISK$counter"
# $TEMP should hold the variable name of the disk, which holds the device name
# TEMP was only for testing, but still has same problem as $DISK$counter
eval echo $TEMP #This echos correctly
STATD$counter=$(eval "smartctl -H -l error \$DISK$counter" | grep -v "5.41" | grep -v "Joe")
eval echo \$STATD$counter
done
Don't use eval ever, except maybe if there is no other way AND you really know what you are doing.
The STATD$counter=$(...) should give an error. That's not a valid assignment because the string "STATD$counter" is not a valid variable name. What will happen is (using a concrete example, if counter happened to be 3 and your pipeline in the $( ) output "output", bash will only expand that line as far as "STATD3=output" so it will try to find a command named "STATD3=output" and run it. Odds are this is not what you intended.
It sounds like everything you want to do can be accomplished with arrays instead. If you are not familiar with bash arrays take a look at Greg's Wiki, in particular this page or the bash man page to find out how to use them.
For example, in the loop you didn't post in your question: make disk (not DISK: don't use all upper case variable names) an array like so
disk+=( "new value" )
or even
disk[counter]="new value"
Then in the loop in your question, you can make statd an array as well and assign it with values from disk by
statd[counter]="... ${disk[counter]} ..."
It's worth saying again: avoid using eval.

Resources