Assiging a number to a module - bash

I need to be able to assign a number to a module, my current code is below. Gap is where it needs to be assigned.
assignmodules(){
#Assign first module
x=1
modules=$(ls ./modules)
for module in modules; do
echo "$module assigned to slot $x"
x=$(x+1)
done
}
A few things:
Module title has to be printed in a different function, so i cant print it within the for loop.
Module needs to be run this way:
case $choice in
1) module1
2) module2
3) module3
etc.
Needs to be printed as following (If i find a good a solution that doesn't do it right, ill probably still use that.):
[1] Module1 [2] Module2
[3] Module3 [4] Module4
etc.
I tried this in the blank:
[{x}]module=$module
(i don't think this is exactly what i tried, i believe it was slightly different but i cant remember)
I wanted it to do as described above, but i don't think it will.

If you're using Bash, just use an array and some globbing and special parameter expansion.
assignmodules() {
set -- ./modules/*
modules=(0 "${#/#./modules/}")
unset 'modules[0]'
declare -p modules # Optionally show result
}
Somewhere you can print the modules list using
for i in "${!modules[#]}"; do
echo "[$i] ${modules[i]}"
done
Or
for i in "${!modules[#]}"; do
echo -n "[$i] ${modules[i]} "
(( i % 2 == 0 )) && echo
done
(( i % 2 )) && echo
It's also recommended to add shopt -s nullglob at the beginning of the script, especially if
modules directory can sometimes be empty.
Lastly, if you're wanting to ask user for choices, look at the select command. You may not need to display them manually. Run help select.

Just put your modules in an array: Numerically-indexed arrays (the default kind) have indices that are numbered by nature (note that these numbers start at 0, not 1).
assignmodules() {
declare -ga modules # explicitly declare global array (optional)
modules=( modules/* ) # put all modules directory entries in an array
modules=( "${modules[#]#*/}" ) # strip modules/ prefix off each entry
}
assignmodules # calling that function leaves modules assigned
# to print your list of modules in the specific format requested might look like:
print_args=( ) # generate format string argument array
for modules_idx in "${!modules[#]}"; do # iterate over indices in array
module=${modules[$module_idx]} # retrieve corresponding module name
print_args+=( "[$module_idx]" "$module" ) # append to list of stuff to print
done
# format string is repeated until all arguments are consumed
# so this way we get two columns as you requested
printf '%s %s\t%s %s\n' "${print_args[#]}" # actually print our list

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
...

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

Variable in variablenames

i have a a couple of variables with a number in its names. e.g
SERVER_IP48_SUBNET
..
SERVER_IP60_SUBNET
And an additional variable
SERVER_IP
Im trying to expand/concatenate them in the following way:
ALLIPs=${SERVER_IP}
for i in {48..64}; do
ALLIPs=${ALLIPs},${SERVER_IP${i}_SUBNET}
done
as you can imagine this script fails saying:
Wrong substitution
Does anybody of you know a good solution for this problem?
Thanks so far
Use a nameref with bash version 4.3 +
ALLIPs=${SERVER_IP}
for i in {48..64}; do
declare -n tmp="SERVER_IP${i}_SUBNET"
ALLIPs+=",$tmp"
done
But you should really be using an array in the first place:
server_ip=0.0.0.0
subnet_ip=(
[48]=1.1.1.1
[49]=2.2.2.2
# ...
[64]=16.16.16.16
)
all_ips=( "$server_ip" )
for i in {48..64}; do
all_ips+=( "${subnet_ip[i]}" )
done
(
IFS=,
echo "ALLIPs = ${all_ips[*]}"
)
Get out of the habit of using ALLCAPS variable names, leave those as
reserved by the shell. One day you'll write PATH=something and then
wonder why
your script is broken.
I just noticed, if you just want a to join the IP addresses with commas, and you're using an array, you don't need a loop at all:
all_ips=$(
IFS=,
set -- "$server_ip" "${subnet_ip[#]}"
echo "$*"
)
You can use ${!varprefix#} or ${!varprefix*} to expand to all variables with that common prefix (the difference is the same as $# and $*):
SERVER_IP48_SUBNET=48sub
SERVER_IP49_SUBNET=49sub
SERVER_IP50_SUBNET=50sub
SERVER_IP=1.2.3.4
# set this as empty since !SERVER_IP# also matches SERVER_IP
ALLIPS=""
for var in "${!SERVER_IP#}"; do
ALLIPS=$ALLIPS,${!var}
done
This would probably be more practical if you could invert the names like this, since we can only match prefixes:
SERVER_IP_SUBNET_48=48sub
SERVER_IP_SUBNET_49=49sub
SERVER_IP_SUBNET_50=50sub
SERVER_IP=1.2.3.4
ALLIPS=$SERVER_IP
for var in "${!SERVER_IP_SUBNET_#}"; do
ALLIPS=$ALLIPS,${!var}
done
More info on this feature in the bash manual.
One idea:
SERVER_IP48_SUBNET=48sub
SERVER_IP49_SUBNET=49sub
SERVER_IP50_SUBNET=50sub
SERVER_IP=1.2.3.4
ALLIPs=${SERVER_IP}
for i in {48..50}
do
tmpvar="SERVER_IP${i}_SUBNET" # build the variable name
ALLIPs="${ALLIPs},${!tmpvar}" # indirect variable reference via tmpvar
done
echo "ALLIPs = $ALLIPs}"
This generates:
ALLIPs = 1.2.3.4,48sub,49sub,50sub

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.

In shell, How do I efficiently process arguments passed to a function to invoke other programs without using too many conditional statements?

I don't have much practical programming experience.
I wrote a function in shell script to take parameters based on whose values an external program is invoked with other parameters. (the program name is passed as a parameter to the function, $1 in this case).
Somehow I find this code shitty and would like to reduce the lines and make it more efficient. I feel like there are too many conditions here and there might be an easier way to do this. Any ideas? There has to be a better way to do this. Thanks.
Code is here
You can build the argument list as an array, with separate conditionals for each variable part of the list (rather than nested conditionals as you have). This is very similar to Roland Illig's answer, except that using an array rather than a text variable avoids possible problems with funny characters (like spaces) in arguments (see BashFAQ #050) -- it's probably not needed here, but I prefer to do it the safe way to avoid surprises later. Also, note that I've assumed the arguments must be in a certain order -- if -disp can go before `refresh, then the "all versions" parts can all be put together at the beginning.
# Build the argument list, depending on a variety of conditions
arg_list=("-res" "$2" "$3") # all versions start this way
if [ "$7" = "allresolutions" ]; then
# allresolutions include the refresh rate parameter to be passed
arg_list+=("-refresh" "$4")
fi
arg_list+=("-disp" "$5") # all versions need the disp value
if [ "$6" = "nooptions" ]; then
: # nothing to add
elif [ "$6" = "vcaa" ]; then
arg_list+=("-vcaa" "5")
elif [ "$6" = "fsaa" ]; then
arg_list+=("-fsaa" "4")
elif [ "$6" = "vcfsaa" ]; then
arg_list+=("-vcaa" "5" "-fsaa" "4")
fi
if [ "$1" != "gears" ]; then
# ctree has time-to-run passed as parameter to account for suitable time for
# logging fps score. If bubble fps capture strategy is different from ctree,
# then this if statement has to be modified accordingly
arg_list+=("-sec" "$8")
fi
# Now actually run it
./"$1" "${arg_list[#]}" &>> ~/fps.log
Instead of the repeated inner "if … elif … fi" you can do this part of the argument processing once and save it in a variable (here: options).
case $6 in
nooptions) options="";;
vcaa) options="-vcaa 5";;
fsaa) options="-fsaa 4";;
vcfsaa) options="-vcaa 5 -fsaa 4";;
esac
...
./$1 -res $2 $3 -refresh $4 -disp $5 $options -sec $8
You could use getopts (see example) if you process that large number of arguments.

Resources