Binary tree of directories UNIX - bash

I have a task to create a binary tree of directories in bash shell, the depth is given as a first argument of the script. Every directory has to be named with the second argument + the depth of the tree which the directory is in.
Example: ./tree.sh 3 name should create the following structure:
name11
/ \
name21 name22
/ \ / \
name31 name32 name33 name34
I don't really have an idea how to do this, Can't even start. It is harder than anything i have done in bash up until now.. Any help will be very much appreciated.
Thanks in advance.

With recursion:
#!/bin/bash
level=$1
current_level=$2; current_level=${current_level:=1}
last_number=$3; last_number=${last_number:=1}
prefix="name"
# test to stop recursion
[[ $level -eq $(($current_level-1)) ]] && exit
# first node
new_number=$(($current_level*10+$last_number*2-1))
mkdir "$prefix$new_number"
(
cd "$prefix$new_number"
$0 $level $(($current_level+1)) $(($last_number*2-1)) &
)
# second node, not in level 1
if [[ $current_level -ne 1 ]]; then
new_number=$(($current_level*10+$last_number*2))
mkdir "$prefix$new_number"
cd "$prefix$new_number"
$0 $level $(($current_level+1)) $(($last_number*2)) &
fi
Test with ./tree.sh 3

Even though other languages are more suitable in implementing a link list, I don't know why this post got a negative vote.
Here's this expert, shared something good for searching, take a look:
https://gist.github.com/iestynpryce/4153007
NOTE: An implementation of a Binary Sort Tree in Bash. Object-like behaviour has been faked using eval. Remember that eval in shell scripting can be evil. BT and BST have difference, you can google it.
#!/bin/bash
#
# Binary search tree is of the form:
# 10
# / \
# / \
# 4 16
# / \ /
# 1 7 12
#
# Print the binary search tree by doing a recursive call on each node.
# Call the left node, print the value of the current node, call the right node.
# Cost is O(N), where N is the number of elements in the tree, as we have to
# visit each node once.
print_binary_search_tree() {
local node="$*";
# Test is the node id is blank, if so return
if [ "${node}xxx" == "xxx" ]; then
return;
fi
print_binary_search_tree $(eval ${node}.getLeftChild)
echo $(${node}.getValue)
print_binary_search_tree $(eval ${node}.getRightChild)
}
### Utility functions to generate a BST ###
# Define set 'methods'
set_node_left() {
eval "${1}.getLeftChild() { echo "$2"; }"
}
set_node_right() {
eval "${1}.getRightChild() { echo "$2"; }"
}
set_node_value() {
eval "${1}.getValue() { echo "$2"; }"
}
# Generate unique id:
gen_uid() {
# prefix 'id' to the uid generated to guarentee
# it starts with chars, and hence will work as a
# bash variable
echo "id$(uuidgen|tr -d '-')";
}
# Generates a new node 'object'
new_node() {
local node_id="$1";
local value="$2";
local left="$3";
local right="$4";
eval "${node_id}set='set'";
eval "set_node_value $node_id $value";
eval "set_node_left $node_id $right";
eval "set_node_right $node_id $right";
}
# Inserts a value into a tree with a root node with identifier '$id'.
# If the node, hence the tree does not exist it creates it.
# If the root node is at the either end of the list you'll reach the
# worst case complexity of O(N), where N is the number of elements in
# the tree. (Average case will be 0(logN).)
tree_insert() {
local id="$1"
local value="$2";
# If id does not exist, create it
if [ -z "$(eval "echo \$${id}set")" ]; then
eval "new_node $id $value";
# If id exists and the value inserted is less than or equal to
# the id's node's value.
# - Go down the left branch
elif [[ $value -le $(${id}.getValue) ]]; then
# Go down to an existing left node if it exists, otherwise
# create it.
if [ "$(eval ${id}.getLeftChild)xxx" != "xxx" ]; then
tree_insert $(eval ${id}.getLeftChild) $value
else
local uid=$(gen_uid);
tree_insert $uid $value;
set_node_left $id $uid;
fi
# Else go down the right branch as the value inserted is larger
# than the id node's value.
else
# Go down the right node if it exists, else create it
if [ "$(eval ${id}.getRightChild)xxx" != "xxx" ]; then
tree_insert $(eval ${id}.getRightChild) $value
else
local uid=$(gen_uid);
tree_insert $uid $value;
set_node_right $id $uid;
fi
fi
}
# Insert an unsorted list of numbers into a binary search tree
for i in 10 4 16 1 7 12; do
tree_insert bst $i;
done
# Print the binary search tree out in order
print_binary_search_tree bst
Actually, I think, it's super easy to implement aa BST in BASH.
How:
Just create a :) damn .txt :) FILE for maintaining the BST.
Here, I'm not going to show how you can implement the CRUD operation for inserting/populating or deleting/updating a BST nodes if implemented using a simple .txt file, but it works as far as printing values. I'll work on it and share the solution soon.
Here is my solution: Just FYSA In BASH, I used a .txt file approach and tried for printing the same from any root node here: https://stackoverflow.com/a/67341334/1499296

Related

bash recursion automatically ends after single level

Why is this make_request function ending just after a single traversal?
make_request(){
path="${1//' '/'%20'}"
echo $path
mkdir -p $HOME/"$1"
$(curl --output $HOME/"$1"/$FILE_NAME -v -X GET $BASE_URL"/"$API_METHOD"/"$path &> /dev/null)
# sample response from curl
# {
# "count":2,
# "items": [
# {"path": "somepath1", "type": "folder"},
# {"path": "somepath2", "type": "folder"},
# ]
# }
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
for (( c=0; c<$count; c++ ))
do
child=$(jq -r ".items[$c] .path" $HOME/"$1"/$FILE_NAME);
fileType=$(jq -r ".items[$c] .type" $HOME/"$1"/$FILE_NAME);
if [ "$fileType" == "folder" ]; then
make_request "$child"
fi
done
}
make_request "/"
make_request "/" should give the following output:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
/folder/foler2-1
/folder/folder2-1/folder2-2
/folder/folder2-1/folder2-3 ...
but I am getting the following:
/folder
/folder/folder1-1
/folder/folder1-1/folder1-2
You are using global variables everywhere. Therefore, the inner call changes the loop variables c and count of the outer call, resulting in bogus.
Minimal example:
f() {
this_is_global=$1
echo "enter $1: $this_is_global"
((RANDOM%2)) && f "$(($1+1))"
echo "exit $1: $this_is_global"
}
Running f 1 prints something like
enter 1: 1
enter 2: 2
enter 3: 3
exit 3: 3
exit 2: 3
exit 1: 3
Solution: Make the variables local by writing local count=$(...) and so on. For your loop, you have to put an additional statement local c above the for.
As currently written all variables have global scope; this means that all function calls are overwriting and referencing the same set of variables, this in turn means that when a child function returns to the parent the parent will find its variables have been overwritten by the child, this in turn leads to the type of behavior seen here.
In this particular case the loop variable c leaves the last child process with a value of c=$count and all parent loops now see c=$count and thus exit; it actually gets a bit more interesting because count is also changing with each function call. The previous comment to add set -x (aka enable debug mode) before the first function call should show what's going on with each variable at each function call.
What OP wants to do is insure each function is working with a local copy of a variable. The easiest approach is to add a local <variable_list> at the top of the function, making sure to list all variables that should be treated as 'local', eg:
local path count c child fileType
change variables to have local scope instead of global.
...
local count; # <------ VARIABLE MADE LOCAL
count=$(jq ".count" $HOME/"$1"/$FILE_NAME)
local c; # <------ VARIABLE MADE LOCAL
for (( c=0; c<$count; c++ ))
do
....
done
...

shell - How to define a variable in a C-type for loop?

I want to iterate over an array of file names stored in files_arr to create a terminal-based file manager in the POSIX shell.
A reduced version of the functions list_directory looks like this:
# Presents the user with the files in the directory
list_directory() {
# Iterate over each element in array `files_arr` by index, not by filename!
# And outputs the file name one on each line
for file in "${!files_arr[#]}"; do
echo "${files_arr[file]}"
done
}
I want to implement a way to exclude the first n files from the array files_arr.
n is defined by how often the user scrolls past the current terminal window size to create an effect of scrolling through the files, highlighting the file where the cursor is currently on.
On a directory (e.g. the home directory) that looks like this:
To Implement this I try to create an C-like for loop like so:
for ((file=$first_file; file<=${!files_arr[#]}; file=$((file+1))); do
or as the whole function:
# Presents the user with the files in the directory
list_directory() {
# Iterate over each element in array `files_arr` by index, not by filename!
#for file in "${!files_arr[#]}"; do
for ((file=$first_file; file<=${!files_arr[#]}; file=$((file+1))); do
# Highlighted file is echoed with background color
if [ $file -eq $highlight_index ]; then
echo "${BG_BLUE}${files_arr[file]}${BG_NC}"
# Colorize output based on filetype (directory, executable,...)
else
if [ -d "${files_arr[file]}" ]; then
echo "$FG_DIRECTORY${files_arr[file]}$FG_NC"
elif [ -x "${files_arr[file]}" ]; then
echo "$FG_EXECUTABLE${files_arr[file]}$FG_NC"
else
echo "${files_arr[file]}"
fi
fi
# $LINES is the terminal height (e.g. 23 lines)
if [ "$file" = "$LINES"]; then
break
fi
done
}
which returns the error:
./scroll.sh: line 137: syntax error near `;'
./scroll.sh: line 137: ` for ((file=$first_file; $file<=${!files_arr[#]}; file=$((file+1))); do'
How can I iterate over the array files_arr, defining the start-index of $file?
You can iterate over the array with:
for (( i = $first_file; i < ${#files_arr[#]}; i++ )); do
echo ${files_arr[i]}
done
but it seems cleaner to use:
for file in ${files_arr[#]:$first_file}; do
echo "$file"
done

Unable to manipulate arrays using "unset"

I have an array of docker containers, arr=(testfoler1 testfoler2 testfoler3 testfoler1)
i know testfoler1 has file notify.txt at location /tmp/, i.e. /tmp/notify.txt.
testfoler2 and testfoler3, are empty.
Now my requirement is once this file is found i will stop the container and will remove that container from arr.
So the flow should be like this.
step 1: /tmp/notify.txt is found in testfoler1 and the new array will be,
arr=(testfoler2 testfoler3 testfoler1)
step 2: it will search for testfolder2 and testfolder3, but as no file is there no action is performed.
step 3: as it reaches to testfoler1 which is at the 2nd index, it will find the notify.txt file and it should remove it from array.
And my last expected array would be (testfoler2 testfoler3) and the loop should keep on running until the file is found or i stop the script.
My script runs successfully til it iterate to (testfoler1 testfoler2
testfoler3). Issue start coming when my array becomes (testfoler2
testfoler3 testfoler1). Here it work fine for testfolder2 and
testfolder3 as file is not found but when it reaches to testfolder1,
instead of removing the testfolder1, it removes testfolder2 and array becomes
(testfoler3 testfoler1) instead of (testfoler2 testfoler3) And then
keep iterating and then it removes testfolder3 and then
testfoler1.However it should have deleted testfolder1 because it had
the file and should have kept running for testfolder2 and testfolder3.
Please refer the code i have tried with:
FILE=/tmp/notify.txt
arr=(testfoler1 testfoler2 testfoler3 testfoler1)
sizeOfArray="${#arr[#]}"
index=0
while [ ! $sizeOfArray -eq 0 ]
do
sizeOfArray="${#arr[#]}"
test=`sudo docker container diff ${arr[index]}|grep $FILE|wc -l`
if [ $test = 1 ]; then
echo "notify.txt is found in container ${arr[index]}"
##Get array length
sizeOfArray="${#arr[#]}"
sudo docker stop ${arr[index]}
sudo docker container ls -a|grep ${arr[index]}
###################Issue seems to be here
unset arr[${arr[index]}]
arr=( "${arr[#]}" )
##################Need some help on code above
echo "When file is FOUND, name of all array elements ${arr[*]}"
echo "Size of array after deletion *********** "${#arr[#]}""
sizeOfArray="${#arr[#]}"
index=$((index + 1))
if [ $index -gt $sizeOfArray ] ; then
index=0
fi
continue
else
echo "notify file is not created in ${arr[index]}"
echo "When file is NOT found, name of all array elements ${arr[*]}"
index=$((index + 1))
if [ $index -ge $sizeOfArray ]; then
echo "Index value is greater/equal size of suites"
index=0
fi
fi
done
This does not delete element index of arr:
unset arr[${arr[index]}]
If that's what you intended to do, you should use:
unset arr[index]

Recursively create directories for all letters

I would like to create a folder structure based on a brace expansion such as {a-z}. Each string generated by the brace-expansion should be a new folder. Furthermore, each of these folders should contain the same set of subfolders similarly generated. And this up to a given level.
An example for the range a-z and depth 16
a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/
a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/b/
a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/c/
...
d/a/h/r/y/d/s/b/e/y/k/f/o/o/q/c/
...
z/z/z/z/z/z/z/z/z/z/z/z/z/z/z/y/
z/z/z/z/z/z/z/z/z/z/z/z/z/z/z/z/
The following code allows me to go upto depth 2:
for x in {a..z} ; do mkdir -p $x/{a..z} ; done
But how do I go further?
A recursive solution. Job is called with 2 params: max_depth and base_path
#!/bin/bash
function job()
{
local depth=$(($1-1))
local path=$2
local x
for x in a b c # reduced for test
do
mkdir -p "$path/$x"
((depth>0)) && job $depth "$path/$x"
done
}
job 3 ./test
Proof it with:
find test -type d
The simplest form would be to use any of the following lines:
mkdir -p {a..c}/{a..c} # depth 2
mkdir -p {a..c}/{a..c}/{a..c} # depth 3
mkdir -p {a..c}/{a..c}/{a..c}/{a..c} # depth 4
...
The brace-expansion will make all combinations and mkdir -p will take care of the rest.
Of course, you do not want to type this over and over for various sets. So you could generate the full brace-expansion-string with bash and use exec to process the brace-expansion-string before passing it to mkdir -p:
depth=3
set={a..c}
dirs=$(printf "/${set}%.0s" $(seq $depth))
mkdir -p $(eval echo .${dirs})
Be aware however that if your set has length m, and you want a depth of n, you are creating m^n directories. This number could conflict with the number of arguments you can pass on to a program.
Related information:
What is the maximum allowed depth of sub-folders?
https://www.in-ulm.de/~mascheck/various/argmax/
A recursive funcion may solve your problem. Take care with inodes generation when using high directory levels...
#!/bin/bash
function createDir {
mkdir -p $1 && cd $1;
for x in {a..z} ; do
local i=$(($2-1))
[ $i -lt 0 ] && continue;
createDir $x $i
done
cd ..
}
createDir $1 $2
Save into a file, like mkdir.sh, and call it: ./mkdir.sh <main_folder> <level>.

bash-command to invert white-list of file paths to generate black-list

I want to convert a white-list of file paths into a black-list (minimum length) of file paths for a given directory.
I have a list of directories, that I want to keep with all subfolders and files in it.
I want to invert this information is the sense of getting: the minimum number of "delete directory with subfolders" commands so that the result is keeping the folders of the whitelist including subfolders and files. (Just to explain. I do not want to delete anything. )
Example:
data structure:
/A1/
A2/
B2/
A3
B3
C2/
/B1/
A2/
A3
B2
/C1/
A2
B2
/D1
whitelist.txt
/A1/B2 # also keep subdirs!
/C1/B2
wanted blacklist.txt
/A1/A2
/A1/C2
/B1 # no subdirs in whitelist -> no individual items here!
/C1/A2
/D1
I want to generate blacklist.txt
Is there a simple way to do it with bash commands?
Otherwise I would like to try it with python.
A bash script version
whitelist=( "./A1/B2" "./C1/B2" )
function run_path {
local cpath cdir wdir allb bdirs
cpath=${1:-.}
cpath=${cpath%/}
for wdir in "${whitelist[#]}"; do
if [[ $wdir = $cpath ]]; then
# break the recursive call
# exit_status <>0 => false
return 1
fi
done
allb=1
bdirs=()
for cdir in "$cpath"/*/; do
if [[ -d $cdir ]]; then
# exit_status <>0 => false : found whitelist item
if ! run_path "$cdir"; then
allb=0
else
bdirs+=("$cdir")
fi
fi
done
if [[ $allb = 0 ]]; then
for cdir in "${bdirs[#]}"; do
echo "$cdir"
done
return 1
fi
}
run_path "${cur_path}"

Resources