bash multiprocess - get results and assign to array - bash

I want to get results from child processes using multiprocessing,
and then I want to assign these results into array type of variable.
my codes are like this
for (( i=0; i<${#servers[#]}; i++ ));
do
output_strings[$i]=$(ls) &
pids[${i}]=$!
done
for pid in ${pids[*]}; do
wait $pid
done
echo ${#output_strings[#]}
However, results were not assigned into the array.
Actually, if I change the code output_strings[$i]=$(ls) & to echo $(ls) &, it works.
How can I assign these results?

Don't send the assignment to the background. Send the command itself to the background:
output_strings[$i]=$(ls &)

Related

bash when is a variable read during a fucntion or loop

In a bash script lets take the extreme below examples where the call/start of the myFn() is 5 minutes before the echo of $inVar -> $myvar happens. During this time between the function start and the interaction with the $myvar, it is updated.
myvar=$(... //some json
alpha=hello )
myFn(){
local -n inVar=$1
//wait for 5 mins .... :)
echo $inVar
}
myFn "myvar"
if [ -z $var ]
then
//wait 5 mins
//but life goes on and this is non blocking
//so other parts of the script are running
echo $myvar
fi
myvar=$(... // after 2 mins update alpha
alpha=world
)
As the $myvar is passed to myFn(), when is $myvar actually read,
at myFn call time (when the function is called/starts)
at the reference copy time inVar=$1
when the echo $inVar occurs
and is this the same for other types of processes such as while, if etc?
You're setting inVar as a nameref, so the value is not known until the variable is expanded at the echo statement
HOWEVER
In your scenario, myFn is "non blocking", meaning you launch it in the background. In this case, the subshell gets a copy of the current value of myVar -- if myVar gets updated subsequently, that update is happening in the current shell, not the background shell.
To demonstrate:
$ bash -c '
fn() { local -n y=$1; sleep 2; echo "in background function, y=$y"; }
x=5
fn x &
x=10
wait
'
in background function, y=5
TL;DR: namerefs and background processes don't mix well.

Iterate between two arrays within a single loop

I have these variables:
bridge_xa_name_list=( "$br_int0_srxa" "$br_int1_srxa" "$br_int2_srxa" "$br_int6_srxa" "$br_int7_srxa" "$br_wan3_srxa1" "$br_wan3_srxa2" )
bridge_xb_name_list=( "$br_int0_srxb" "$br_int1_srxb" "$br_int2_srxb" "$br_int6_srxb" "$br_int7_srxb" "$br_wan3_srxb1" "$br_wan3_srxb2" )
I am trying to use a single loop to iterate all the elements for each array.
At the moment I have a functioning loop but only by referencing the $bridge_xa_name_list
for a in "${bridge_xa_name_list[#]}"; do
shell_echo_textval_green "Network Bridge Name Detected" "$a"
sleep 1
shell_echo_text "Verifying $a network State"
virsh_net_list=$(virsh net-list | grep active | grep $a)
if [[ ! $virsh_net_list == *"active" ]]
then
shell_echo "[Inactive]"
else
shell_echo "[Active]"
shell_echo_green "$a.xml found. Undefining anyway."
virsh net-undefine $a
fi
shell_echo_text "File $a.xml is at $srxa_fld_path"
if [[ -f ${srxa_fld_path}${a}.xml ]]
then
shell_echo "[Yes]"
else
shell_echo "[Not Found]"
shell_echo_text "Attempting to copy $a.xml template to ~/config/$srxa_nm"
cp $xml_file_path $srxa_fld_path${a}.xml
shell_echo ["Copied"]
#Check if Copy was sucessfull
if [[ -f $srxa_fld_path${a}.xml ]]
then
:
else
shell_echo_red "[Failed]"
shell_echo_red "There was an error when trying to copy ${a}.xml"
shell_echo_error_banner "Script Aborted! 1 error(s)"
exit 1
fi
done
$a in my script is iterating all the elements from the 1st array. However, I would like to include the second array as part of the same loop.
These are indexed arrays so you can iterate over the indexes:
for (( i = 0; i < ${#bridge_xa_name_list[#]}; i++ )); do
echo "${bridge_xa_name_list[i]}"
echo "${bridge_xb_name_list[i]}"
done
$a in my script is iterating all the elements from the 1st array. However, I would like to include the second array as part of the same loop.
I think you mean that you want to execute the loop body once for each element of bridge_xa_name_list and also once, separately, for each element of bridge_xb_name_list, without duplicating the body of the loop. Yes, there are at least two easy ways to do that:
Absolutely easiest would be to just specify the additional elements in the loop header:
for a in "${bridge_xa_name_list[#]}" "${bridge_xb_name_list[#]}"; do
# loop body ...
What you need to understand here is that the for loop syntax has nothing in particular to do with accessing an array. The in list of such a command designates zero or more individual values (shell "words") to iterate over, which in the case of your original code are produced by a parameter expansion involving array-valued parameter bridge_xa_name_list. But this is just a special case of the shell's general procedure of expanding each command (path expansion, parameter expansion, command expansion, etc.) before executing it. You can use that however you like.
OR
Make a function around the loop that executes it once for every function argument. Then call that function once for each array:
my_loop() {
for a in "$#"; do
# loop body
done
}
# ...
my_loop "${bridge_xa_name_list[#]}"
my_loop "${bridge_xb_name_list[#]}"
Note that this still exhibits the same expand-then-execute behavior described in the previous item, which is why you have to pass the expansion of each array (to one word per element). There is no direct way to pass the whole array as a single argument.
Note also that the shell supports a special shortcut for iterating over all the elements of $#. For that particular case, you can omit the in list altogether:
my_loop() {
for a; do
# loop body
done
}
Of course, you can also combine the above, by providing the function and calling it once with the elements of both arrays:
my_loop "${bridge_xa_name_list[#]}" "${bridge_xb_name_list[#]}"

Bash function does not return value since it seems to run in parallel [duplicate]

This question already has answers here:
In bash, how to store a return value in a variable?
(8 answers)
Closed 3 years ago.
In a function, I'm trying to extract the process ID from a line of text. Also, I'm trying to return that process ID:
#!/bin/bash
regex="[a-z]* ([0-9]+) .*"
indexRawRow="sahandz 9040 other stuff comes here"
getPidFromRow() {
if [[ $1 =~ $regex ]]
then
pid=${BASH_REMATCH[1]}
else
pid=""
fi
echo "pid is $pid inside function"
return $pid
}
processId=$(getPidFromRow "$indexRawRow")
echo "pid is $processId outside of function"
The output is:
pid is pid is 9040 inside function outside of function
There are a few problems here:
The two echo statements seem interwoven, indicating that the function and the main scope are running in paralell. Is this supposed to happen? Can it be turned off?
The outside scope never gets the value of the process ID. As you can see, it's only printed once. Through testing, I know that it is only the echo inside of the function that is printing the process ID 9040. The outside scope seems to not have gotten the process ID.
What is the reason for these problems?
Your assumptions are convoluted here, what you are seeing in the variable processId at the output of $(..) is the output of the echo statement and not the return-ed value from function.
You simply can't return strings from a function in bash. Just unsigned integer codes from 0-255. If you were to return only the the matched group from the function, leave out the return statement and just print the matched group
getPidFromRow() {
# Your other code here
# ..
printf '%d\n' "$pid"
}

Is it possible to run two loops at the same time?

So I have a project in my cyber security class to make a bash game. I like to make one of those medieval games where you make farms and mines to get resources. Well I like to make something like that. To do that I have to have two while loops running. Like this
while [ blah ]; do
blah
done
while [ blah ]; do
blah
done
Is it possible to run two while loops at the same time and if I am writing it wrong how do I write it?
If you put a & after each done, like done&, you will create new processes in the background that run the while loops. You will have to be careful to realize what this means though, since the bash script will continue executing commands after creating those new processes even if they are not finished. You might use the wait command to prevent this from happening, but I'm not too used to using that so I cannot vouch for it.
Yes, but you will have to fork a new process for each while loop to be executing in. Technically, they won't both run at the same time (unless you consider multiple cores, but this isn't even garaunteed).
Below is a link to how to fork multiple processes using bash.
Forking / Multi-Threaded Processes | Bash
Since you mention this is a school project, I'll stop here lest I help you "not learn".
R
First things first, wrap the loop into a function and then fork it.
This is done when you want to split a process, for example, if I'm processing a CSV with 160,000+ lines, single process/"thread" will take hours. If you wrap the loop into a function and simply fork it, you will have x amount of processes running, then add wait/kill defunct process loop and you are done. here what you are looking at.
while loop with nested loop:
function jobA() {
while read STR;
do
touch $1_temp
key=$(IFS="|";set -- $STR; echo $1)
for each in ${blah[#]};
do
#echo "$each"
done
done <$1;
}
for i in ${blah[#]};
do
echo "$i"
$(jobRDtemp $i) &
child_pid=$!
parent_pid=$$
PIDS+=($child_pid)
echo "forked process $child_pid with parent $parent_pid"
done
for pid in ${PIDS[#]};
do
wait $pid
done
echo "all jobs done"
sleep 1
Now this is wrapped, here is example of a FORKED loop. this means you will have parallel processes run in the background, WAIT will wait for ALL to complete before proceeding. This is important for some type of scripts.
Also, DO NOT use nested FOR loops written C style like presented above, example:
for (( i = 1; i <= 5; i++ )) ### Outer for loop ###
This is VERY slow. use THIS type:
for each in ${blah[#]};
do
#echo "$each"
if [ "$key" = "$each" ]; then
# echo "less than $keyValNeed..."
echo $STR >> $1_temp
fi
done
You could also use nested for loops
for (( i = 1; i <= 5; i++ )) ### Outer for loop ###
do
for (( j = 1 ; j <= 5; j++ )) ### Inner for loop ###
do
echo -n "$i "
done
echo "" #### print the new line ###
done
EDIT: I thought you meant Nested Loop but reading again you said running both loops "at the same time". I will leave my answer here though.

why doesn't this timer work?

I'm trying to make a script to start a second counter. [but later I want to add minutes too] but so far, it just keeps echoing 0, 0, 0, 0, over and over. :\
#!/bin/bash
seconds=0;
count()
{
export seconds=$[seconds + 1]
sleep 1;
count
}
count&
N=$!
trap "kill $N; exit 0;" 2
while true; do
echo $seconds
sleep 1;
done
The & makes it run in a subshell, which means that it has its own set of environment variables independent of the current script. Find another way (or another language) to do this.
Ignacio's answer explains that your subshell's environment is not visible to your parent process.
One way to create slaves like this is co-processes (with coproc in zsh and newer bash or with special syntax in ksh). Your bash probably doesn't support this yet.
Here's a variation on your idea that uses signals to send the updates to the parent. I've retained your basic structure where it doesn't conflict:
count() {
parent=$1
kill -ALRM $parent
sleep 1
count $parent
}
trap 'seconds=$[$seconds + 1]' ALRM
count $$ &
trap "kill $!; exit 0" INT
while true
do
echo $seconds
done

Resources