Improving a menu - bash

I wrote a menu in bash to access for network equipments but now there are a lot of them for showing in one screen to permit selecting one without scroll up or down. I need improving it, attached my actual menu. (https://dl.dropboxusercontent.com/u/33222611/menu.txt)
The option that I´ve been thinking is to have two file:
The list of each equipment. Inside this file put the headers and a ID to discriminate if equipment used ssh or telnet
The code to do all the work. Allowing enter into two modes: one where each equipment of list would be printed with a number to allow select any and the other mode that allows entering a search mode
I need your help to make it happen but I accept other suggestions. Thanks a lot.

Assuming you have bash version 4, this is a good case for an associative array:
declare -A cmd=(
[ESP_R7609S_MTSO]=ssh
[ESP_R7609_RIGUERO]=ssh
[ESP_R7609_SUBASTA]=telnet
[ESP_R7606_BOLONIA]=ssh
[ESP_R7609_LINDAVISTA]=ssh
# etc etc
)
while :; do
read -p "Enter a hostname: " hostname
if [[ ${cmd[$hostname]} ]]; then
"${cmd[$hostname]}" "$hostname"
break
else
echo "unknown hostname. The known hosts are:"
printf "%s\n" "${!cmd[#]}" | sort | paste - - - - - | column -t | less -E
fi
done
Old bash: use "parallel" indexed arrays
typeset -a hosts cmds
typeset -i i=0
hosts[i]=ESP_R7609S_MTSO; cmds[i]=ssh; i=i+1
hosts[i]=ESP_R7609_RIGUERO; cmds[i]=ssh; i=i+1
hosts[i]=ESP_R7609_SUBASTA; cmds[i]=telnet; i=i+1
hosts[i]=ESP_R7606_BOLONIA; cmds[i]=ssh; i=i+1
hosts[i]=ESP_R7609_LINDAVISTA; cmds[i]=ssh; i=i+1
# ...
while :; do
read -p "Enter a hostname: " hostname
i=0
while [[ $i -lt ${#hosts[#]} ]]; do
if [[ $hostname == ${hosts[i]} ]]; then
"${cmds[i]}" "$hostname"
break 2
fi
i=i+1
done
echo "unknown hostname. The known hosts are:"
printf "%s\n" "${hosts[#]}" | sort | pr -t -w $(tput cols) -4 | less -E
done
The typeset -i i sets the variable i with the "integer" attribute, so we can do "bare" arithmetic like i=i+1

Related

Difficulty in mentioning multiple calls in a single line of echo

The problem I have is with echo cannot echo e.g: "$prefix_$suffix". This is a assignment for a class in school if that changes things.
I've tried e.g "$prefix _$suffix" but that creates a space between the prefix and suffix
#!bin/bash
read -p "Username prefix: " prefix
read -p "Amount of users: " amount
read -p "Name of file to store, include extension (e.g test.txt): " filename
touch "$filename"
new="$amount"
suffix=0
state=true
while [ state=true ] ; do
#in this function i reverse the user input amount of users so it appears as user 1,2,3 (and so on) in the first line of the text file that is also user input.
if [ "$new" -ge 1 ] ; then
newpass="$(gpg --gen-random --armor 1 12)"
#reversing process, making the suffix start at 1 so user 1 gets assigned suffix 1 for the username and i decrease the "new" variable that gets set to "$amount" so the while loop isn't indefinite
new=`expr "$new" - 1`
suffix=`expr "$suffix" + 1`
echo -n "$prefix" >> "$filename"
echo -n "_$suffix" >> "$filename"
echo -n " " >> "$filename"
echo "$newpass" >> "$filename"
echo -e >> "$filename"
elif [ "$new" -eq 0 ] ; then
break
fi
done
a run of this bash results in 5 lines e.g:
re_1 UlrZW3jB5L9zt6Nf
and so on, depending how many users you choose at the input
however the next task is to create users with the username, in this example re_1 with the password: UlrZW3jB5L9zt6Nf. This is where the clunky echo stuff I've done doesn't work. I tried doing useradd -u "$prefix_$suffix" and "$prefix $suffix" , none of these work since "$prefix$suffix" is treated as one call instead of two and the "$prefix _$suffix" adds one space in between the prefix and suffix which is not acceptable.
Even if this looks very introverted to you, hence i added comments to make it understandable, help is very appreciated.
Feel free to ask question if you do not understand and want to help!
This will do what you want:
echo "${prefix}_${suffix}"

bash script: use command output to dynamically create menu and arrays?

I'm trying to create a script to run a command and take that output and use it to create a menu dynamically. I also need to access parts of each output line for specific values.
I am using the command:
lsblk --nodeps -no name,serial,size | grep "sd"
output:
sda 600XXXXXXXXXXXXXXXXXXXXXXXXXX872 512G
sdb 600XXXXXXXXXXXXXXXXXXXXXXXXXXf34 127G
I need to create a menu that looks like:
Available Drives:
1) sda 600XXXXXXXXXXXXXXXXXXXXXXXXXX872 512G
2) sdb 600XXXXXXXXXXXXXXXXXXXXXXXXXXf34 127G
Please select a drive:
(note: there can be any number of drives, this menu would be constructed dynamically from the available drives array)
When the user selects the menu number I need to be able to access the drive id (sdb) and drive serial number (600XXXXXXXXXXXXXXXXXXXXXXXXXXf34) for the selected drive.
Any assistance would be greatly appreciated.
Please let me know if any clarification is needed.
#!/usr/bin/env bash
# Read command output line by line into array ${lines [#]}
# Bash 3.x: use the following instead:
# IFS=$'\n' read -d '' -ra lines < <(lsblk --nodeps -no name,serial,size | grep "sd")
readarray -t lines < <(lsblk --nodeps -no name,serial,size | grep "sd")
# Prompt the user to select one of the lines.
echo "Please select a drive:"
select choice in "${lines[#]}"; do
[[ -n $choice ]] || { echo "Invalid choice. Please try again." >&2; continue; }
break # valid choice was made; exit prompt.
done
# Split the chosen line into ID and serial number.
read -r id sn unused <<<"$choice"
echo "id: [$id]; s/n: [$sn]"
As for what you tried: using an unquoted command substitution ($(...)) inside an array constructor (( ... )) makes the tokens in the command's output subject to word splitting and globbing, which means that, by default, each whitespace-separated token becomes its own array element, and may expand to matching filenames.
Filling arrays in this manner is fragile, and even though you can fix that by setting IFS and turning off globbing (set -f), the better approach is to use readarray -t (Bash v4+) or IFS=$'\n' read -d '' -ra (Bash v3.x) with a process substitution to fill an array with the (unmodified) lines output by a command.
I managed to untangle the issue in an elegant way:
#!/bin/bash
# Dynamic Menu Function
createmenu () {
select selected_option; do # in "$#" is the default
if [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#)) ]; then
break;
else
echo "Please make a vaild selection (1-$#)."
fi
done
}
declare -a drives=();
# Load Menu by Line of Returned Command
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
# Display Menu and Prompt for Input
echo "Available Drives (Please select one):";
createmenu "${drives[#]}"
# Split Selected Option into Array and Display
drive=($(echo "${selected_option}"));
echo "Drive Id: ${drive[0]}";
echo "Serial Number: ${drive[1]}";
How about something like the following
#!/bin/bash
# define an array
declare -a obj
# capture the current IFS
cIFS=$IFS
# change IFS to something else
IFS=:
# assign & format output from lsblk
obj=( $(lsblk --nodeps --no name,serial,size) )
# generate a menu system
select item from ${obj[#]}; do
if [ -n ${item} ]; then
echo "Invalid selection"
continue
else
selection=${item}
break
fi
done
# reset the IFS
IFS=${cIFS}
That should be a bit more portable with less dependencies such as readarray which isn't available on some systems

progress indicator for bash script without dialog or pv

I've written a bash script to truncate through a server list and perform various commands. I would like to incorporate a progress indicator like a percent complete while the script is running. I found a script online to perform this but it's not working properly and I am unsure how to utilize it.
The dialog command is not an option here as I am working with nsh
#!/bin/bash
i=0
while [[ $i -lt 11 ]]; do
##
## \r = carriage return
## \c = suppress linefeed
##
echo -en "\r$i%\c\b"
(( i=i+1 ))
sleep 1
done
echo
exit 0
For testing purposes I am only connecting to each server and echoing the hostname.
for i in $(cat serverlist.txt)
do
nexec -i hostname
done
How can I utilize the first code snipped to show the progress while going through the list of servers in the code above?
To keep track of your progress, you'll want to store the list of servers in an array, so you know how many there are:
mapfile -t servers <serverlist.txt # servers is an array
n=${#servers[#]} # how many
i=0
for server in "${servers[#]}"; do
((i++))
progress=$(( i * 100 / n ))
nexec ...
echo "you are ${progress}% complete"
done
write_status() {
done=0
total=$1
columns=${COLUMNS:-100}
# print initial, empty progress bar
for (( i=0; i<columns; i++ )); do printf '-'; done; printf '\r'
# every time we read a line, print a progress bar with one more item complete
while read -r; do
done=$(( done + 1 ))
pct=$(( done * columns / total ))
for ((i=0; i<pct; i++)); do
printf '+'
done
for ((i=pct; i<columns; i++)); do
printf '-'
done
printf '\r'
if (( done == total )); then break; fi
done
}
# read server names into an array; the below is bash 4.x syntax
readarray -t servers <serverlist.txt
# direct FD 4 to the a subshell running the write_status function
exec 4> >(write_status "${#servers[#]}")
for hostname in "${servers[#]}"; do
nexec -i "$hostname" # actually run the command
echo >&4 # ...and notify write_status that it completed
done
If you wanted to parallelize the remote commands, you can do that by replacing the for loop at the bottom with the following:
for hostname in "${servers[#]}"; do
(nexec -i "$hostname"; echo >&4) &
done
wait
If your shell is a version of bash prior to 4.0, then the readarray -t servers <serverlist can be replaced with the following:
servers=( )
while IFS= read -r server; do servers+=( "$server" ); done <serverlist.txt

Matching a list of ip addresses with another file with ip addresses [duplicate]

This question already has answers here:
How to find a list of ip addresses in another file
(3 answers)
Closed 8 years ago.
I was given the task to retrieve a long list of ip addresses from another file with ip addresses. I created this bash script but it does not work very well. After executing the script I check the file called "found" there is nothing, and when I check the file called "notfound" there are about 60 ip addresses. In both files there has to be a total of 1500 ip addresses. There are two files; 1. list of ip addresses to retrieve(findtheseips.txt), 2. list of ip addresses to retrieve from(listips.txt). Can anybody please help me to make it work. Thank you very much. I run the script this way: ./script findtheseips.txt
#!/bin/bash
declare -a ARRAY
exec 10<&0
exec < $1
let count=0
while read LINE; do
ARRAY[$count]=$LINE
if egrep "$LINE" listips.txt; then
echo "$LINE" >> found
else
echo "$LINE" >> notfound
fi
done
There's no need to try to use exec or to create an array.
You could read from the script's first argument $1.
There shouldn't be a need to use egrep unless you're trying to do extended regular expression matching.
#!/bin/bash
while read LINE; do
if grep "$LINE" listips.txt; then
echo "$LINE" >> found
else
echo "$LINE" >> notfound
fi
done < $1
Here is an all BASH solution.
#!/bin/bash
while read l1; do
n=0
while read l2; do
if [[ $l1 == $l2 ]]; then
echo "$l1" >> found
((n++))
fi
done < ips2
if [ $n -eq 0 ]; then
echo "$l1" >> notfound
fi
done < $1

How to resize progress bar according to available space?

I am looking to get an effect where the length of my progress bar resizes accordingly to my PuTTY window. This effect is accomplished with wget's progress bar.
Here is my program I use in my bash scripts to create a progress bar:
_progress_bar
#!/bin/bash
maxwidth=50 # line length (in characters)
filled_char="#"
blank_char="."
current=0 max=0 i=0
current=${1:-0}
max=${2:-100}
if (( $current > $max ))
then
echo >&2 "current value must be smaller max. value"
exit 1
fi
percent=`awk 'BEGIN{printf("%5.2f", '$current' / '$max' * 100)}'`
chars=($current*$maxwidth)/$max
echo -ne " ["
while (( $i < $maxwidth ))
do
if (( $i <= $chars ));then
echo -ne $filled_char
else
echo -ne $blank_char
fi
i=($i+1)
done
echo -ne "] $percent%\r"
if (( $current == $max )); then
echo -ne "\r"
echo
fi
Here is an example of how I use it, this example finds all Tor Onion proxies Exit nodes and bans the IP under a custom chain:
#!/bin/bash
IPTABLES_TARGET="DROP"
IPTABLES_CHAINNAME="TOR"
WORKING_DIR="/tmp/"
# get IP address of eth0 network interface
IP_ADDRESS=$(ifconfig eth0 | awk '/inet addr/ {split ($2,A,":"); print A[2]}')
if ! iptables -L "$IPTABLES_CHAINNAME" -n >/dev/null 2>&1 ; then #If chain doesn't exist
iptables -N "$IPTABLES_CHAINNAME" >/dev/null 2>&1 #Create it
fi
cd $WORKING_DIR
wget -q -O - http://proxy.org/tor_blacklist.txt -U NoSuchBrowser/1.0 > temp_tor_list1
sed -i 's|RewriteCond %{REMOTE_ADDR} \^||g' temp_tor_list1
sed -i 's|\$.*$||g' temp_tor_list1
sed -i 's|\\||g' temp_tor_list1
sed -i 's|Rewrite.*$||g' temp_tor_list1
wget -q -O - "https://check.torproject.org/cgi-bin/TorBulkExitList.py?ip=$IP_ADDRESS&port=80" -U NoSuchBrowser/1.0 > temp_tor_list2
wget -q -O - "https://check.torproject.org/cgi-bin/TorBulkExitList.py?ip=$IP_ADDRESS&port=9998" -U NoSuchBrowser/1.0 >> temp_tor_list2
sed -i 's|^#.*$||g' temp_tor_list2
iptables -F "$IPTABLES_CHAINNAME"
CMD=$(cat temp_tor_list1 temp_tor_list2 | uniq | sort)
UBOUND=$(echo "$CMD" | grep -cve '^\s*$')
for IP in $CMD; do
let COUNT=COUNT+1
_progress_bar $COUNT $UBOUND
iptables -A "$IPTABLES_CHAINNAME" -s $IP -j $IPTABLES_TARGET
done
iptables -A "$IPTABLES_CHAINNAME" -j RETURN
rm temp_tor*
Edit:
I realized that first example people may not want to use so here is a more simple concept:
#!/bin/bash
for i in {1..100}; do
_progress_bar $i 100
done
I made a few changes to your script:
Converted it to a function. If you want to keep it in a separate file so it's available to multiple scripts, just source the file in each of your scripts. Doing this eliminates the overhead of repeatedly calling an external script.
Eliminated the while loop (which should have been a for ((i=0; $i < $maxwidth; i++)) loop anyway) for a drastic speed-up.
Changed your arithmetic expressions so they evaluate immediately instead of setting them to strings for later evaluation.
Removed dollar signs from variable names where they appear in arithmetic contexts.
Changed echo -en to printf.
Made a few other changes
Changed the AWK output so "100.00%" is decimal aligned with smaller values.
Changed the AWK command to use variable passing instead of "inside-out: quoting.
Here is the result:
_progress_bar () {
local maxwidth=50 # line length (in characters)
local filled_char="#"
local blank_char="."
local current=0 max=0 i=0
local complete remain
current=${1:-0}
max=${2:-100}
if (( current > max ))
then
echo >&2 "current value must be smaller than max. value"
return 1
fi
percent=$(awk -v "c=$current" -v "m=$max" 'BEGIN{printf("%6.2f", c / m * 100)}')
(( chars = current * maxwidth / max))
# sprintf n zeros into the var named as the arg to -v
printf -v complete '%0*.*d' '' "$chars" ''
printf -v remain '%0*.*d' '' "$((maxwidth - chars))" ''
# replace the zeros with the desired char
complete=${complete//0/"$filled_char"}
remain=${remain//0/"$blank_char"}
printf ' [%s%s] %s%%\r' "$complete" "$remain" "$percent"
}
What was the question? Oh, see BashFAQ/091. Use tput or bash -i and $COLUMNS. If you use bash -i, however, be aware that it will have the overhead of processing your startup files
After some google searching I did find the following:
tput cols will return the amount of columns, much like Sdaz's suggested COLUMNS var.
Therefore I am going with:
maxwidth=$(tput cols) unless someone else has a more bulletproof way without requiring tput
Bash exports the LINES and COLUMNS envvars to the window rows and column counts, respectively. Furthermore, when you resize the putty window, via the SSH or telnet protocols, there is sufficient logic in the protocol to send a WINCH signal to the active shell, which then resets these values to the new window dimensions.
In your bash script, use the COLUMNS variable to set the current dimensions, and divide 100 / progbarlen (progbarlen based on a portion of the COLUMNS variable) to get how many percentage points make up one character, and advance them as your progress moves along. To handle the resizing dynamically, add a handler for SIGWINCH (via trap) and have it reread the COLUMNS envvar, and redraw the progress bar using the new dimensions.
(I haven't tested this in a shell script, and there may be some additional logic required, but this is how bash detects/handles resizing.)

Resources