User input to designate how many variables are in a script, then assign a value to each variable - bash

I am trying to allow a user to input the number of ip addresses that they want to interface with, then enter each ip address and have it assigned to a variable. The script will ultimately write and execute a second script. The reason for the second script is that I am ssh-ing into an AP in order to cluster x number of AP's together, and once SSH occurs, bash/python variables are no longer passed through(the AP has it's own language), so they must be translated to plain text before the ssh script is run. The code below functions but allows only 2 ip addresses(I couldn't figure out how to use the $cvar to create multiple variables), and does not allow me to decide how many ip addresses to enter:
#!/bin/bash
echo -e "How many AP's do you want to Cluster?:"
#this is the variable to define how many ips to ask for
read cvar
echo -e "Enter last 2 digits of AP #1:"
read ip1
echo -e "Enter last 2 digits of AP #2:"
read ip2
#I want this to continue for x number of ip addresses(defined by $cvar)
echo -e "Enter a name for your Cluster:"
read cname
#code below is executed within the AP terminal(commands are unique to that shell)
echo "#!/bin/bash
ssh -T admin#192.168.0.$ip1 <<'eof'
configure
cluster
add $cname
add-member 192.168.0.$ip1 user ***** password ********
save
add-member 192.168.0.$ip2 user ***** password ********
save
exit
operate $cname
save
exit
" > ./2ndScript.sh
chmod a+x ./2ndScript.sh
/bin/bash ./2ndScript.sh

Without rewriting the entire script, here is a snippet.
#!/bin/bash
# IP is an array
IP=()
# Read number of IP Addresses to be read in
read -p "How many AP's do you want to Cluster?: " cvar
loop=1
while [ $loop -le $cvar ]
do
read -p "Enter last 2 digits of AP #${loop}: " IP[$loop]
loop=$((loop+1))
done

Arrays are your friend here. Take the following;
echo -e "Enter last 2 digits of AP #1:"
read ip1
echo -e "Enter last 2 digits of AP #2:"
read ip2
#I want this to continue for x number of ip addresses(defined by $cvar)
We can make a for loop, and then add an element to an array for each address. In this for loop, $i will tell us which cycle we're on, starting with 0. Since it auto-increments, we can just use it to specify what index of the array to update.
for (( i=0; i<$cvar; i++ )); do
echo -e "Enter last 2 digits of AP #$((i+1)):"
read #With no arguments, read assigns the output to $REPLY
#optional; this allows the user to enter "done" to end prematurely
#if [[ $REPLY == "done" ]]; then break; fi
ip[$i]=$REPLY #ip is the name of the array, and $i points to the index
done
If you use that optional code snipit, you don't even have to ask how many addresses the user wants. You could instead replace the for loop with while true; do, and just instruct the user to enter "done" (or any other exit command) to end address collection (though you'll need to define i=0 somewhere and then increment it at the end of the loop if you swap to while).
Now you have a list of values ordered from ${ip[0]} to ${ip[n]} of all the address that the user entered. You can extract them using another for loop later;
for ((i=0;i<${#ip[*]};i++)); do
#Insert code here. For example:
#echo "${ip[$i]} #echos the currently selected value of the array
#echo "${ip[$i]}" >> file.txt #appends file.txt with the current value
done

Related

How to split blocks of terminal output into array elements that can be looped over

Just looking for the best practice method to split a multi line output such as you might get from a command or a curl request into an array of entries using Bash or Zsh
For example, if a command or printing a file gave an output of
"User A:
Name: Bob
Age: 36
User B:
Name: Jeff
Age: 42"
How would you create an array from that where each user were an entry in the array?
Or if you had an output of say devices similar to
"Computer A:
Name Bob's Computer
Serial 123456
Uptime 12hr
Computer B:
Name Jeff's Computer
Serial 789101
Uptime 8hr"
How would you split that into an array of computers, so that you could do things like see how many computers there were by the number of elements in the array, or pass them one by one to another command, etc?
I've been looking for ways to split strings and output, all the answers I find seem to target splitting a single line with a single character deliminator. I figure the way to do it is either to split by using "User" or "Computer" as the deliminator in the above examples, or to use those as a pattern to read from and to, but I'm not sure how to do that in Bash?
Thanks in advance
Assuming:
You want to split the lines into e.g. "Computer A" block and "Computer B" block then store the computer (or User) names into an array.
(May be optional) You want to parse the lines of attributes such as "Name", "Serial" ... and store them in array of arrays.
Then would you please try the following:
#!/bin/bash
str="Computer A:
Name Bob's Computer
Serial 123456
Uptime 12hr
Computer B:
Name Jeff's Computer
Serial 789101
Uptime 8hr"
keyword="Computer" # the keyword to split the lines into array
declare -A hash # create associative array
i=0 # index of the "real" array name
readarray -t a <<< "$str" # read the string splitting on newlines
for line in "${a[#]}"; do # loop over lines
if [[ $line =~ ^${keyword}[[:blank:]]+([^:]+): ]]; then
# if the line starts with the "keyword"
name="${BASH_REMATCH[1]}" # name is assigned to "A" or "B" ...
hash[$name]="array$i" # define "real" array name and assign the hash value to it
declare -A "array$i" # create a new associative array with the name above
declare -n ref="array$i" # "ref" is a reference to the newly created associative array
(( i++ )) # increment the index for new entry
else
read -r key val <<< "$line" # split on the 1st blank character
key=${key%:} # remove traiking ":"
key=${key## } # remove leading whitespace(s)
ref[$key]=$val # store the "key" and "val" pair
fi
done
print number of elements of the array
echo "The array has ${#hash[#]} elements"
# print the values of each array of the array
for i in "${!hash[#]}"; do # loop over "A" and "B"
echo "$i" # print it
declare -n ref=${hash[$i]} # assign ref to array name "array0" or "array1" ... then make it an indirect reference
for j in "${!ref[#]}"; do # loop over "${array0[#]}" or "${array1[#]}" ...
echo " $j => ${ref[$j]}"
done
done
Output:
The array has 2 elements
A
Name => Bob's Computer
Uptime => 12hr
Serial => 123456
B
Name => Jeff's Computer
Uptime => 8hr
Serial => 789101
Please note bash does not natively support array of arrays and we need to make use of declare -n statement to create a reference to a variable, which makes the code less readable. If Python is your option, please let me know. It will be much more suitable for this kind of task.
This will do the trick in Zsh:
split-blocks() {
local MATCH MBEGIN MEND
reply=(
${(0)1//(#m)$'\n'[^[:space:]]## [^[:space:]]##:[[:space:]]##/$'\0'$MATCH}
)
}
What this does:
Find each occurrence of <newline><text> <text>:<whitespace>.
Insert a null byte before each match.
Split the result on null bytes.
Assign the resulting elements to array $reply.

Unix looping from text file

I have 2 text files. I want to loop in the first file to get a list, then using that list, loop from the second file to search for matching fields.
The first loop was fine, but when the second loop comes in, the variable $CLIENT_ABBREV cannot be read in the second loop, it's reading as blank. Output looks like does not match DOG where there's a blank before does.
while IFS=',' read CLIENT_ID NAME SERVER_NAME CLIENT_ABBREV
do
echo "\n------------"
echo Configuration in effect for this run
echo CLIENT_ID=$CLIENT_ID
echo NAME=$NAME
echo SERVER_NAME=$SERVER_NAME
echo CLIENT_ABBREV=$CLIENT_ABBREV
while IFS=',' read JOB_NAME CLIENT_ABBREV_FROMCOMMAND JOBTYPE JOBVER
do
if [ "$CLIENT_ABBREV" == "$CLIENT_ABBREV_FROMCOMMAND" ]; then
# do something
else
echo $CLIENT_ABBREV does not match $CLIENT_ABBREV_FROMCOMMAND
done <"$COMMAND_LIST"
done <"$CLIENT_LIST"
Is there a file with the name COMMAND_LIST ?
Or, actually do you want to use $COMMAND_LIST instead of COMMAND_LIST ?

Improving knowledge in Bash

This is more directed to learning about BASH rather than creating a specific code.
---Problem: Write a Bash script that takes a list of login names on your computer system as command line arguments, and displays these login names, full names and user-ids of the users who own these logins (as contained in the /etc/passwd file), one per line. If a login name is invalid (not found in the /etc/passwd file), display the login name and an appropriate error message. ---
If I needed to create a code to fulfill this problem could I do it using a "choice" list like this:
read -p "Enter choice: " ch
if [ "$ch" = "1" ]; then
function_1
else
if [ "$ch" = "2" ]; then
function_2
else
if [ "$ch" = "3" ]; then
function_3
else
if [ "$ch" = "4" ]; then
function_4
else
if [ "$ch" = "5" ]; then
function_5
fi x5
or would it have to be completed using a grep and test method where by the user read input must be taken into variables Name1 Name2.... NameN and are tested to the ect/passwd file via grep command.
#!/bin/bash
# pgroup -- first version
# Fetch the GID from /etc/group
gid=$(grep "$̂1:" /etc/group | cut -d: -f3)
# Search users with that GID in /etc/passwd
grep "^[^:]*:[^:]*:[^:]*:$gid:" /etc/passwd | cut -d: -f1`enter code here`
Please explain the best you can with some generic code. I am still learning the basics and testing different ideas. I apologize if these are very vague concepts, I am still getting the hang of BASH.
You would:
Accept (read) from the user the username,
Check if the username exists by retrieving the record using grep,
If positive result (i.e. you got data), display it as needed,
Otherwise, display an error message (or whatever you need).
You are almost there (got how to get input, how to search using grep). What you need is to get the result of the grep into a variable that you can check. You may try something like:
Result=$(grep....)
and then check the contents of Result.
To me it looks like you're missing an important part of the problem: the names are passed as arguments to the script:
./script.sh name1 name2 name3
If this is the case, then a loop would be the most appropriate thing to use:
for login; do
printf '%s: %s\n' "$login" "$(grep "^$login" /etc/passwd)"
done
Note that a for loop iterates over the list of arguments passed to the script $# by default.

Choose more than one option in script shell?

I want to create a menu with a script with multi choice .
like :
1)
2)
3)
4)
5)
I can choose 1 , and 3 , and 5 in the same time .
bash's select compound command doesn't directly support multiple choices, but you can still base your solution on it, taking advantage of the fact that whatever the user enters is recorded in the special $REPLY variable:
#!/usr/bin/env bash
choices=( 'one' 'two' 'three' 'four' 'five' ) # sample choices
select dummy in "${choices[#]}"; do # present numbered choices to user
# Parse ,-separated numbers entered into an array.
# Variable $REPLY contains whatever the user entered.
IFS=', ' read -ra selChoices <<<"$REPLY"
# Loop over all numbers entered.
for choice in "${selChoices[#]}"; do
# Validate the number entered.
(( choice >= 1 && choice <= ${#choices[#]} )) || { echo "Invalid choice: $choice. Try again." >&2; continue 2; }
# If valid, echo the choice and its number.
echo "Choice #$(( ++i )): ${choices[choice-1]} ($choice)"
done
# All choices are valid, exit the prompt.
break
done
echo "Done."
As for how the select command normally works, with a single selection:
Run man bash and look under the heading 'Compound Commands'
For an annotated example, see this answer.
This answer implements custom logic as follows:
The designated target variable of the select command, dummy, is ignored, and the $REPLY variable is used instead, because Bash sets it to whatever the user entered (unvalidated).
IFS=', ' read -ra selChoices <<<"$REPLY" tokenizes the user-entered value:
It is fed via a here-string (<<<) to the read command
using instance of commas and space (,<space>) as the [Internal] Field Separator (IFS=...)
Note that, as a side effect, the user could use spaces only to separate their choices.
and the resulting tokens are stored as elements of array (-a) selChoices; -r simply turns off interpretation of \ chars. in the input
for choice in "${selChoices[#]}"; do loops over all tokens, i.e., the individual numbers the user chose.
(( choice >= 1 && choice <= ${#choices[#]} )) || { echo "Invalid choice: $choice. Try again." >&2; continue 2; } ensures that each token is valid, i.e., that it is a number between 1 and the count of choices presented.
echo "Choice #$(( ++i )): ${choices[choice-1]} ($choice)" outputs each choice and choice number
prefixed with a running index (i), which is incremented (++i) using an arithmetic expansion ($((...))) - since a variable defaults to 0 in an arithmetic context, the first index output will be 1;
followed by ${choices[choice-1]}, i.e., the choice string indicated by the number entered, decremented by 1, because Bash arrays are 0-based; note how choice needs no $ prefix in the array subscript, because a subscript is evaluated in an arithmetic context (as if inside $(( ... ))), as above.
terminated with ($choice), the chosen number in parentheses.
break is needed to exit the prompt; by default, select will keep prompting.

Creating files in bash from user input and a while loop

I am creating a script that will take user input for a file name, and number of files to be created.
The script will then create the number of files required, and use the name given but increment the file name by one each iteration.
So lets say I want 3 files, named boogey. The output in the home folder would be; boogey1.txt, boogey2.txt and boogey3.txt.
Here is the code I have so far.
#!/bin/bash
var1=1
echo -n "Enter the number of your files to create: [ENTER]: "
read file_number
var2=file_number
echo -n "Enter the name of your file: [ENTER]: "
read file_name
var3=file_name
while [ "$var1" -le $file_number]
do
cat $file_name.txt
#var1=$(( var1+1 ))
done
Your script is almost working. You just need to:
uncomment that var1 assignment line
add a space between $file_number and ] on the while loop line
change cat to touch (or any of a number of other possibilities) since cat doesn't create files it read them.
Use $file_name$var1.txt in the cat (now touch) line to actually use the incremented index instead of the bare filename.
That being said this script could be dramatically improved. See below for an example.
#!/bin/bash
read -p 'Enter the number of your files to create: [ENTER]: ' file_number
read -p 'Enter the name of your file: [ENTER]: ' file_name
for ((i = 1; i < file_number; i++); do
: > "$file_name$i.txt"
done

Resources