create multiple global variables from a loop in bash [duplicate] - bash

This question already has answers here:
Indirect variable assignment in bash
(7 answers)
Dynamic variable names in Bash
(19 answers)
Closed 4 years ago.
So I have a loop that basically goes through all of the disks installed on the system, then it assigns a variable to a disk name. however, I cannot use those variables if it's not inside the loop, how do I make them available to be used in other functions or other parts of the script?
Here is the code
#!/bin/bash
dev=1
for disk in $(fdisk -l | grep -o '/dev/sd[a-z]'); do
set "DISK$dev=$disk"
dev=$((dev+1))
done
So if I do echo $DISK1 for example, it doesn't display anything.
But if I do echo $DISK1 INSIDE the loop, then ir displays the fist variable assignment. Can I export them and make them available outside of the loop?

set is not at all the right command to use here. You could pull this off with eval where you have set; but the proper way to solve this is simply to assign the values to an array.
disks=($(fdisk -l | grep -o '/dev/sd[a-z]'))
You can loop over the individual entries with ${disk[0]} through ${disk[n]} or retrieve the entire array at once with "${disk[#]}". The expression ${#disk[#]} evaluates to the number of elements in the array; though because array indexing is zero-based, the last index is one less than this value.
Of course, very often, you don't really need to keep the results in a variable explicitly. If you don't need random access (do you need to recall what you know about the first disk when processing the fifth? Really?) you should probably just loop over the values directly.
for disk in $(fdisk -l | grep -o '/dev/sd[a-z]'); do
: whatever you need to do with the current "$disk"
done

You cannot directly access the parent scope. Also with "export" you will be able to access the exported variable to sub-shell but not the parent one.
A work around for this can be put all these set into a file (appending the new sets) and execute it with after the loop:
dev=1
for disk in $(fdisk -l | grep -o '/dev/sd[a-z]'); do
echo "export \"DISK$dev=$disk\";" >> my_script_full_of_sets.sh
dev=$((dev+1))
done
. my_script_full_of_sets.sh

Related

how to use variable variable names in bash script [duplicate]

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 6 years ago.
I'm trying to get the values of some php variables into a bash script in order to set other bash variables. Basically I need to be able to access the value of a variable - variable name.
the script can find & read the file, find the php variables, but when I try to set them it just hangs. Here is what I have:
variables=(database_user database_password dbase);
paths=(modx_core_path modx_connectors_path modx_manager_path modx_base_path);
for index in "${variables[#]}"; do
index=\$$index
echo $index;
$index="$(grep -oE '\$${!index} = .*;' $config | tail -1 | sed 's/$${!index} = //g;s/;//g')";
done
not sure what I am doing wrong here...
You are trying to perform an indirect assignment.
You should get rid of these two lines :
index=\$$index
echo $index;
By simply writing :
echo "${!index}"
Which does an indirect expansion cleanly (gives you the value of the variable whose name is contained in variable index).
Next, the problematic line is this one:
$index="$(grep -oE '\$${!index} = .*;' ... (rest omitted)
In Bash, an assignment cannot begin with a $.
One way you can perform an indirect assignment is this (after removing the index re-assignment as suggested above) :
printf -v "$index" "$(grep -oE '\$${!index} = .*;' ... (rest omitted)
This uses the -v option of printf, which causes the value passed as the final argument to be assigned to a variable, the name of which is passed to the -v option.
There are other ways of handling indirect assignment/expansions, some with code injection risks, as they use (potentially uncontrolled) data as code. This is something you may want to research further.
Please note I am assuming the actual grep command substitution works (I have not tested).

bash: accessing global variables from a command pipeline [duplicate]

This question already has answers here:
A variable modified inside a while loop is not remembered
(8 answers)
Closed 6 years ago.
I want to fill an associative array in bash in somewhat non-trivial setup. I have a pipeline of commands to produce the required input for the array.
Here is a minimal/toy example:
declare -A mapping
seq 10 | while read i; do
key="key_$i"
val="val_$i"
echo "mapping[$key]=$val"
mapping["${key}"]="${val}"
done
echo "${mapping["key_1"]}"
echo "${mapping["key_2"]}"
In this example mapping is changed inside while, but these changes do not propagate into the global namespace. I think this is because while works inside a separate subshell, thus namespaces have diverged.
In order to avoid (what I suggest) the problem with subshells, I came up with the following:
declare -A mapping
while read i; do
key="key_$i"
val="val_$i"
echo "mapping[$key]=$val"
mapping["${key}"]="${val}"
done < <(seq 10)
echo "${mapping["key_1"]}"
echo "${mapping["key_2"]}"
Thus, the generation part explicitly goes into a subshell, while the while loop left at the top-level alone. The construction works.
My questions are: is there any better way to accomplish my goal? And, is my suggestion about subshells correct? If so, why bash uses a subshell in the first case, but not in the second?
EDIT: after little more digging, the question mostly is a duplicate of this one. A good list of options to handle the issue could be found at http://mywiki.wooledge.org/BashFAQ/024
Not sure if this is a better way than your second code snippet, but a way to solve the first one is to use sub shell { ... } right after the pipe:
declare -A mapping
seq 10 | {
while read i; do
key="key_$i"
val="val_$i"
echo "mapping[$key]=$val"
mapping["${key}"]="${val}"
done
echo "${mapping["key_1"]}"
echo "${mapping["key_2"]}"
}

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.

How to define dynamic variable in bash? [duplicate]

This question already has answers here:
How to create a bash variable like $RANDOM
(3 answers)
Closed 5 years ago.
I would like to have a shell variable that could be dynamically run every time it is refered, for example, i would like to have a variable $countPwd which could return the count of files/dirs in the current directory, it could be defined as:
countPwd=`ls | wc -l`
and if I do echo $countPwd it would only show the value when I define the variable, but it won't update automatically when I change my current directory. So how do I define such a variable in bash that the value of it get updated/calculated on the fly?
Update:
The $PWD is a perfect example of a variable get evaluated in the real time. You don't need to use $() or backticks `` to evaluate it. How is it defined in bash?
Make a function:
countPwd() {
ls | wc -l
}
Then call the function like any other command:
echo "There are $(countPwd) files in the current directory."
Another option: store the command in a variable, and evaluate it when required:
countPwd='ls | wc -l'
echo $(eval "$countPwd")
You'll need to write some C code: write a bash's loadable buitins to do the heavy lifting in defining variables with dynamic values like $SECONDS or $RANDOM ($PWD is just set by cd).
More details in my answer here (a duplicate of this question).

Resources