Bash Recursive Method not Finishing Method Call Stack - bash

I'm new to Bash, and as a project I'm trying to create a shell script that will create a tree of folders. For example, if I tell it to create a tree that is 3 folders deep and 4 wide, then it would create a level of folders labeled 0, 1, and 2; then inside each of those folders it would create folders 0, 1, and 2, and so on until it reached 4 levels deep. (This would create 4^3 folders.)
Here is the code for the method I created:
function createLevel () { #param1 = number of levels of folders, param2 = number of folders per level
numLevels=$1
numPerLevel=$2
if [ $numLevels -eq 1 ];
then
for ((i=0; i < numPerLevel; i++));
do
mkdir $i
done
else
for ((i=0; i < numPerLevel; i++));
do
mkdir $i
cd $i
createLevel $((numLevels - 1)) $numPerLevel
cd ..
done
fi
}
It usually just creates one branch, so for example it will create a 0 folder within a 0 folder within a 0 folder, but it will not trace back out and make the other folders. I feel like it's not finishing the method call stack and instead of going back and finishing the method, it just quits after it calls itself. Any help would be appreciated!

You need to declare your variables local if you call the function recursively:
local i
local numLevels=$1
local numPerLevel=$2
[...]
Otherwise they will be overwritten by the "inner" calls.

Related

Bash 4.3+ - Recursive fibonacci

I am working on my programming language which compiles into bash 4.3+ code. I am in the final stages of my language, but I have a small issue with recursion functions. Here's the bash code which is supposed to return the fibnacci number given an index.
#!/bin/bash
function fib() {
local a=$1
declare -n ret=$2
if (( $a <= 2 )); then
ret=1
return
fi
fib $((a-1)) fib1
fib $((a-2)) fib2
ret=$((fib1+fib2))
echo "fib($((a-1))) + fib($((a-2))) = $ret"
return
}
num=5
fib $num result
echo
echo "fib($num) = $result"
The problem in this code is that the fib(5) is giving 3 which is clearly wrong. What I think the problem is, when I pass fib1 and fib2 as a way to store the return value, they get overwritten by each call which assigns them. If that was the problem, how can I make fib1 and fib2 local to their execution scope.
Please note that I do not want to use a return statement to return the values, I want to try finding a solution using declare -n namerefs.
Thank you
What I think the problem is, when I pass fib1 and fib2 as a way to store the return value, they get overwritten by each call which assigns them.
Yep, and you can see that by printing the value of fib1 between and after the recursive calls:
fib $((a-1)) fib1
echo "fib($a): fib1: $fib1"
fib $((a-2)) fib2
echo "fib($a): fib1: $fib1 fib2: $fib2"
You should see the value of fib1 change during the second call. That's to be expected, since it wasn't declared local and there only is one global copy of fib1.
If you make them local... it doesn't help much.
Assume you start by calling fib 4 result. The first iteration will make fib1 local, and call fib 3 fib1. Now the second iteration will also make fib1 local, but it will also try to assign its return value to a variable of the same name. Since the access is by name, it saves the return value to its own copy of fib1.
This can be seen with a somewhat simpler script too, this tries to return a fixed value up from the bottom of the recursion:
#!/bin/bash
foo() {
declare -n ret=$2
if (( $1 == 0 )); then
echo "foo($1) returning"
ret=end # this is the value that should bubble up
return
fi
local x=initial$1 # use $1 here to track the level the value came from
foo $(($1 - 1)) x
ret=$x
echo "foo($1) = $x"
return
}
foo 3 result
echo "main: $result"
The workaround I can think of is to have a separate global variable for the return value, and to immediately copy it to a local variable:
local fib1 fib2
fib $((a-1)) retval
fib1=$retval
fib $((a-2)) retval
fib2=$retval

Adding up variable within loop

I got a number of files and I need to determine how many of those will fit on a 4Tb drive by just knowing first filename. Name pattern is 001j00_rf_geo_????$seqn with sequential 3-digit number at the end. Say I start with 001j00_rf_geo_????100.
block=4000000000000
shopt -s dotglob
seqn="100"
size=`stat -c%s 001j00_rf_geo_????$seqn`
for (( i=$size ;i < $block ; seqn++ ))
do
((size+=$(stat -c%s 001j00_rf_geo_????$seqn)))
done
echo $size
I am pretty sure the summing up part in for loop is wrong. I just could get my head around how to get a total size of files having the the loop part in code.
Look at your for loop, you are not using 'i' at all -- it is unneeded. If you want to use a C-style for loop, you can simply omit the initializer:
for ((; size < block; seqn++))
do
or use a while loop instead
while ((size < block))
do
...
((seqn++))
done
Of course you can just move your initialization to the for loop as well and get rid of the one above
for ((seqn = 100; size < block; seqn++))
do
Give either a try and let me know if you have further questions.

'for' loop with dynamic array size

I have an array that gets elements added to it when it calls the function findVar. The problem seems to be on the for loop that does not update the number of elements once started running.
When I do echo at the end of the for loop I get the correct number of elements and the last element but it seems not to be checking on the for conditions once started.
for var in "${tempV[#]}"
do
num_words=${#tempV[#]}
let i=i+1
if ! [ $i -gt $num_words ]
then
findVar $objBKP $var
fi
done
Your attempt is looping over every original element in the array and then ensuring that you haven't looped more times than that and calling your function.
That doesn't work because the original expansion of tempV happens once and so the added entries are never seen. But that also doesn't make sense since, by definition, if you are looping over the elements of the array you can't loop more times then there are elements in the array.
What you want to be doing (assuming a non-sparse, integer-indexed array that is only appended to) is looping numerically and checking that you haven't exceeded the array size as the loop condition.
Something like this (untested):
i=0
while [ "$i" -lt "${#tempV[#]}" ]; do
var=${tempV[i]}
findVar "$objBKP" "$var"
i=$((i + 1))
done
You're not using $i for anything other than an iteration counter. It's completely unnecessary in your posted example. Instead, just iterate over the contents of the variable expansion. For example:
for var in "${tempV[#]}"; do
findVar "$objBKP" "$var"
done

Reference group of variables from one file to another in shell

I didn't see anything even close to this use case, so I am unsure how this would look in shell scripting. Essentially I have a bunch of template shell scripts where the only difference in the files is a group of 4 variables. I would like to create another file for these variables like this:
# Group 1
Var 1 = A
Var 2 = B
Var 3 = C
Var 4 = D
# Group 2
Var 1 = F
Var 2 = G
Var 3 = H
Var 4 = I
etc. Then I would want to use one shell script as a template and pass in a parameter. Such as ./script.sh group2. The script would use this parameter, go off to the input file and determine the group of variables to use for the rest of the script. Is this possible? What would be the proper syntax for this?
UPDATE:
An idea I had was something like this:
SITE="$1"
VIP="Source ../conf/${$1.VIP}"
DMN1="Source ../conf/${$1.VAR1}"
DMN2="Source ../conf/${$1.VAR2}"
DMN3="Source ../conf/${$1.VAR3}"
The idea is that when I run the script, I run ./script.sh ABC
From this, the script will use ABC as the site, then from the config file, reference the variable labeled ABC.VIP to ABC.DMN3. In the config file, it would read ABC.VIP="abcvip". Would this not alleviate my problem? I am assuming something like this would work, but I am unsure on the syntax of it all. Mostly the linking to a source variable reference.
You can achieve with a simple function creation, which indirectly works as the above case.
######### Make this as a source file ####
group_1()
{
Var1=A
Var2=B
Var3=C
Var4=D
}
group_2()
{
Var1=F
Var2=G
Var3=H
Var4=I
}
###################
Create the above content in a file and create another file where you can call the particular variables, like below
if [[ target == "group_1" ]]
then
group_1
echo $Var1
else
group_2
echo $Var1
fi
Hope this will solve your problem

Calculating IDs for model runs

I'm running some array jobs on a PBS system (although hopefully no knowledge of PBS systems is needed to answer my question!). I've got 24 runs, but I want to split them up into 5 sub-jobs each, so I need to run my script 120 times.
After giving the PBS option of -t 1-120, I can get the current job-array ID using $PBS_ARRAYID. However, I want to create some output files. It would be best if these output files used the ID that it would have had if there were only 24 runs, together with a sub-run identifier (e.g. output-1a.txt, output-1b.txt ... output-1e.txt, output-2a.txt).
What I therefore need is a way of calculating a way to get the ID (in the range 1-24) together with the sub-run identifier (presumably in a set of if-statements), which can be used in a shell-script. Unfortunately, neither my maths nor my Unix knowledge is quite good enough to figure this out. I assume that I'll need something to do with the quotient/remainder based on the current $PBS_ARRAYID relative to either 120 or 24, but that's as far as I've got...
You just need a little modular division. A quick simulation of this in Ruby would be:
p = Array.new;
(1..120).each {|i| p[i] = "Run #{1+(i/5)}-#{((i%5)+96).chr}" }
What this says is simply that the run should start at 1 and increment after each new section of five, and that the trailing sub-run should be the ascii character represented by 96 plus the position of the sub-run (eg, 97 == 'a').
Here it is in Bash:
#!/bin/bash
chr() {
local tmp
[ ${1} -lt 256 ] || return 1
printf -v tmp '%03o' "$1"
printf \\"$tmp"
}
for ((i = 0; i < ${#ARP[*]}; i++))
do
charcode=$((($i % 5)+97))
charachter=$(chr "$charcode")
echo "Filename: output-$((($i/5)+1))$charachter"
done
I just used ARP as the name of the array, but you can obviously substitute that. Good luck!

Resources