Unable to manipulate arrays using "unset" - bash

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]

Related

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[#]}"

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

shell decrement variable from external ifle

I'm trying do decrement a variable that i got from a .txt file and change it in that same file for the next time i run it!
#!/bin/bash
value=$(<cred.txt)
echo "Credits: $value"
if [ value -ge 1 ]
then
DATE:'date +%Y%n%d'
touch date.txt"
value =$((value-1))
else
echo "not enough credits"
fi
I've tried value =$((value-1)) but i doesn't seem to change anything.
So, the value of the variable is 200, the next time i run it i want it to be 199 and so on.

Bash - How to for-loop last digit of a variable

I am trying to automate creation of folders. Each folder should have a number at beginning of file name (increased by the for-loop [$i]. Also the rest of the folder name should be build up by variables that are constructed as [folder_x] where [x] is supposed to be stepped up also by the loop.
To be more specific. How do you build a string that is combined by the for-loops [$i] and a variable that is called, but should at the end also use the [$i]?
For more details see below:
#!/bin/bash
# Variables to be used
folder_1=1-folderOne
folder_2=2-folderTwo
folder_3=3-folderThree
# This is the folder names that should be created:
# mkdir /tmp2/1-folderOne
# mkdir /tmp2/2-folderTwo
# mkdir /tmp2/3-folderThree
# The for loop should combine the [$i] and above [folder_x],
# where the [x] should also be increased by the loop.
# Below is what i have right now:
# Note! The text "created-by-forloop" is just dummy text,
# and should be replace by the real solution.
for i in 1 2 3
do
if [ ! -d /tmp2/$i-created-by-forloop ]; then
mkdir -p /tmp2/$i-created-by-forloop;
fi
done
Use an array of names instead of distinct variables for each:
numbers=(One Two Three)
for i in "${!numbers[#]}" ; do
mkdir /tmp2/$((i+1))-folder"${numbers[i]}"
done
The loop iterates $i over the indices of the array. We need to add 1 to the index as arrays are zero based, but we want our files to be numbered from 1, not 0.

Binary tree of directories UNIX

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

Resources