I have setup a script I can run to connect to my servers, which are contained in a json string variable.
While the script works just fine, I am finding that I do not really care too much for the select output as it shows me the entire row. What I would like to do is simply display the server yet still return the ip and user.
Is this possible? If so, how? Here is my code:
#!/bin/bash
_key="/home/MyUser/Documents/Keys/MyKey.pem"
# our server json
_servers='[{"server":"server1","ip":"10.0.0.1","user":"root"},{"server":"server2","ip":"10.0.0.2","user":"ubuntu"},{"server":"server3","ip":"192.168.4.112","user":"ec2-user"}]';
# loop over the items and display the server
echo "- Select a server to connect to: "
select _t in $(echo ${_servers} | jq -c '.[]'); do
# will keep asking until valid value given
[ -n "$_t" ] && break
done
# parse the selected row
_server=$(echo $_t | jq '.server');
_ip=$(echo $_t | jq '.ip');
_user=$(echo $_t | jq '.user');
echo "Connnecting to: ${_server:1:-1}";
sleep 2
#echo ${_ip:1:-1}
#echo ${_user:1:-1}
# make the connection
ssh -i $_key ${_user:1:-1}#${_ip:1:-1}
the select currently shows me:
me#MyMachine:~/Desktop# bash test
- Select a server to connect to:
1) {"server":"server1","ip":"10.0.0.1","user":"root"}
2) {"server":"server2","ip":"10.0.0.2","user":"ubuntu"}
3) {"server":"server3","ip":"192.168.4.112","user":"ec2-user"}
#?
And what I would like it to show instead:
me#MyMachine:~/Desktop# bash test
- Select a server to connect to:
1) server1
2) server2
3) server3
#?
You need an associative array that maps a server name to the JSON object representing that server.
declare -A servers
while read -r name data; do
servers[$name]=$data
done < <(jq -rc '.[] | "\(.server) \(.)"' <<< "$_servers")
Then you'll select a server name, and process the associate data after the select statement.
select _t in "${!servers[#]}"; do
[ -n "$_t" ] && break
done
read _server _ip _user < <(jq -rc '[.server, .ip, .user][]' <<< "${servers[$_t]}"
Alternatively, process the JSON once and store the IP addresses and user names in separate arrays.
declare -A ips users
while read -r _server _ip _user; do
ips[$_server]=$_ip
users[$_server]=$_user
done < <(jq -rc '.[] | "\(.server) \(.ip) \(.user)"' <<< "$_servers")
select _server in "${!ips[#]}"; do # could use "${!users[#]}" as well
[ -n "$_server" ] && break
done
_ip=${ips[$_server]}
_user=${users[$_user]}
Related
would like to get an opinion on how best to do this in bash, thank you
for x number of servers, each has it's own list of replication agreements and their status.. it's easy to run a few commands and get this data, ex;
get servers, output (setting/variable in/from a local config file);
. ./ldap-config ; echo "$MASTER $REPLICAS"
dc1-server1 dc1-server2 dc2-server1 dc2-server2 dc3...
for dc1-server1, get agreements, output;
ipa-replica-manage -p $(cat ~/.dspw) list -v $SERVER.$DOMAIN | grep ': replica' | sed 's/: replica//'
dc2-server1
dc3-server1
dc4-server1
for dc1-server1, get agreement status codes, output;
ipa-replica-manage -p $(cat ~/.dspw) list -v $SERVER.$DOMAIN | grep 'status: Error (' | sed -e 's/.*status: Error (//' -e 's/).*//'
0
0
18
so output would be several columns based on the 'get servers' list with each 'replica: status' under each server, for that server
looking to achieve something like;
dc2-server1: 0 dc2-server2: 0 dc1-server1: 0 ...
dc3-server1: 0 dc3-server2: 18 dc3-server1: 13 ...
dc4-server1: 18 dc4-server2: 0 dc4-server1: 0 ...
Generally eval is considered evil. Nevertheless, I'm going to use it.
paste is handy for printing files side-by-side.
Bash process substitutions can be used where you'd use a filename.
So, I'm going to dynamically build up a paste command and then eval it
I'm going to use get.sh as a placeholder for your mystery commands.
cmd="paste"
while read -ra servers; do
for server in "${servers[#]}"; do
cmd+=" <(./get.sh \"$server\" agreements | sed 's/\$/:/')"
cmd+=" <(./get.sh \"$server\" status)"
done
done < <(./get.sh servers)
eval "$cmd" | column -t
RD_OPTION_AZWEBAPPNAME="01-SM1,02-SM1Touch,03-Data"
for i in $(echo $RD_OPTION_AZWEBAPPNAME | sed "s/,/ /g");
do
/bin/az group deployment create --name Template2020 --RD_OPTION_AZWEBAPPNAME=$i
With this command I create 3 APP with 01-SM1 02-SM1Touch, 03-Data but I need to insert a piece of this array in another parameter in order to have SM1 SM1Touch Data withot the number and the "-" before the APP name INSIDE a for cicle, like below
RD_OPTION_AZWEBAPPNAME="01-SM1,02-SM1Touch,03-Data"
for i in $(echo $RD_OPTION_AZWEBAPPNAME | sed "s/,/ /g");
do
/bin/az group deployment create --name Template2020 --RD_OPTION_AZWEBAPPNAME=$i --webappconf=$WEBAPPNAMEWITHOUTNUMBERANDMINUSBEFORE
You should use an array for RD_OPTION_AZWEBAPPNAME instead of a string.
Then you can iterate over it instead of parsing it with sed.
Something like this :
RD_OPTIONS=(
"SM1"
"SM1Touch"
"Data"
)
for number in `seq -f "%02g" 1 ${#RD_OPTIONS[#]}`
do
name=${RD_OPTIONS[$number-1]}
full="$number-$name"
echo "number: $number"
echo "name: $name"
echo "full: $full"
done
will print
number: 01
name: SM1
full: 01-SM1
number: 02
name: SM1Touch
full: 02-SM1Touch
number: 03
name: Data
full: 03-Data
So you could do this :
RD_OPTIONS=(
"SM1"
"SM1Touch"
"Data"
)
for number in `seq -f "%02g" 1 ${#RD_OPTIONS[#]}`
do
name=${RD_OPTIONS[$number-1]}
full="$number-$name"
/bin/az group deployment create --name Template2020 --RD_OPTION_AZWEBAPPNAME=$full --webappconf=$name
done
Consider this
RD_OPTION_AZWEBAPPNAME="01-SM1,02-SM1Touch,03-Data"
arr1=( ${RD_OPTION_AZWEBAPPNAME//,/' '} ) # conver your var to an array
arr2=( ${arr1[#]//*-/} ) # create second array witn names SM1, SM1Touch, Data
arr3=( ${arr1[#]} ${arr2[#]} ) # create mega) array
for name in ${arr3[#]}; { your_code; } # loop through mega array with your code
for i in $(echo $RD_OPTION_AZWEBAPPNAME | sed "s/,/ /g");
do
echo $i
export AZWEBAPPNAMENONUMBER=`echo "$i" | cut -c 4-`
This is the way I decided.
I have a function that gives me a list of IPs and for each IP in my list, I want to run a query. The problem I'm having is its only looping through (1) of the results and not the rest.
getPartition ()
{
_knife=$(which knife);
_grep=$(which grep);
_awk=$(which awk);
cd ~/home/foo/.chef
local result=$(${_knife} search "chef_environment:dev AND role:myapp AND ec2_region:us-east-1" | ${_grep} IP | ${_awk} '{ print $2 }');
read -a servers <<< $result;
echo "Checking ${#servers[#]} servers";
for i in ${servers[#]};
do
local host='10.1.2.123'
local db='mystate'
_mongo=$(which mongo);
echo -n "$i";
local exp="db.foobarcluster_servers.find(
{\"node_host\":\"${i}\",\"node_type\":\"PROCESS\",\"region\":\"us-east-1\",\"status\":\"ACTIVE\"},{\"partition_range_start\":1,\"partition_range_end\":1, _id:0}).pretty();";
${_mongo} ${host}/${db} --eval "$exp" | grep -o -e "{[^}]*}";
done
}
So, I tried using for, but its only running the query for (1) of the (5) hosts listed.
I can see in my output for result that the list of IPs look like this:
+ local 'result=10.8.3.34
10.8.2.161
10.8.3.514
10.8.4.130
10.8.2.173'
So, I'm just returning results for (1) of the IPs it should be (5) of them because I have 5 IPs:
+ read -a servers
+ echo 'Checking 1 servers'
Checking 1 servers
+ for i in ${servers[#]}
+ local host=10.1.2.130
+ local db=mystate
++ which mongo
+ _mongo=/usr/local/bin/mongo
+ echo -n 10.8.3.34
10.8.3.34+ local 'exp=db.foobarcluster_servers.find(
{"node_host":"10.8.3.34","node_type":"PROCESS","region":"us-east-1","status":"ACTIVE"},{"partition_range_start":1,"partition_range_end":1, _id:0}).pretty();'
+ /usr/local/bin/mongo 10.8.3.34/mystate --eval 'db.foobarcluster_servers.find(
{"node_host":"10.8.3.34","node_type":"PROCESS","region":"us-east-1","status":"ACTIVE"},{"partition_range_start":1,"partition_range_end":1, _id:0}).pretty();'
+ grep -o -e '{[^}]*}'
{ "partition_range_start" : 31, "partition_range_end" : 31 }
+ set +x
Results:
{ "partition_range_start" : 31, "partition_range_end" : 31 }
I'm expecting:
{ "partition_range_start" : 31, "partition_range_end" : 31 }
{ "partition_range_start" : 32, "partition_range_end" : 32 }
{ "partition_range_start" : 33, "partition_range_end" : 33 }
{ "partition_range_start" : 34, "partition_range_end" : 34 }
{ "partition_range_start" : 35, "partition_range_end" : 35 }
How do I effectively loop through my IPs? Did I set up result properly as a variable to hold that list of IPs?
Good idea using set -x - another good debugging tactic (that also makes reading set -x easier) would be to comment out parts that aren't relevant to the issue (e.g. make the for loop simply print its iterations, hard-code the value of result, etc.) to try to narrow down the issue.
If I try to replicate what you're doing myself:
demo() {
local result='10.8.3.34
10.8.2.161
10.8.3.514
10.8.4.130
10.8.2.173'
read -a servers <<< $result
echo "Checking ${#servers[#]} servers"
for i in ${servers[#]}; do
echo "$i"
done
}
Which outputs (with set -x):
$ demo
+ demo
+ local 'result=10.8.3.34
10.8.2.161
10.8.3.514
10.8.4.130
10.8.2.173'
+ read -a servers
+ echo 'Checking 5 servers'
Checking 5 servers
+ for i in '${servers[#]}'
+ echo 10.8.3.34
10.8.3.34
+ for i in '${servers[#]}'
+ echo 10.8.2.161
10.8.2.161
+ for i in '${servers[#]}'
+ echo 10.8.3.514
10.8.3.514
+ for i in '${servers[#]}'
+ echo 10.8.4.130
10.8.4.130
+ for i in '${servers[#]}'
+ echo 10.8.2.173
10.8.2.173
In other words, the code you shared appears to be working as expected. Perhaps there's a typo you corrected while transcribing?
A key thing to note (per help read) is that read "Reads a single line from the standard input ... the line is split into fields as with word splitting". In other words, a multi-line input does not all get read by a call to read, only the first line does. We can test this by tweaking the demo function above to use:
read -a servers <<< "$result"
Which causes the output you describe:
$ demo
+ demo
+ local 'result=10.8.3.34
10.8.2.161
10.8.3.514
10.8.4.130
10.8.2.173'
+ read -a servers
+ echo 'Checking 1 servers'
Checking 1 servers
+ for i in '${servers[#]}'
+ echo 10.8.3.34
10.8.3.34
So that's likely the source of your issue - by quoting $result (which generally is a good idea) read respects the newlines separating the elements, and stops reading after it sees the first one.
Instead use the readarray command, which has more sane behavior for tasks like this. It will "read lines from a file into an array variable", rather than stopping after the first line.
You can then skip the indirection of writing to result, as well, and just pipe directly into readarray:
readarray -t servers < <(
${_knife} search "chef_environment:dev AND role:myapp AND ec2_region:us-east-1"
| ${_grep} IP | ${_awk} '{ print $2 }')
it's because read reads only until first input line delimiter "\n"
adding option -d '', reads until end of input
result=serv1$'\n'serv2$'\n'serv3
read -a servers <<< $result
printf "<%s>\n" "${servers[#]}"
read -d '' -a servers <<< $result
printf "<%s>\n" "${servers[#]}"
There's also readarray builtin which can be used to read an array
readarray -t servers <<< $result
printf "<%s>\n" "${servers[#]}"
-t to remove the newlines for each element of the array
The problem is that…
read -a servers <<< $result
is only reading the first line into the array
Change that line to…
servers=( $result )
This converts every whitespace-delimited value in $result into an array element. Effectively servers=( <ip> <ip> <ip> <ip> <ip> )
i have a problem with script.
At my work i often have to check PTR of domain, MX records and similar data. I did the most of the script, but the problem appears when i do something likethis is subdomain
e-learning.go4progress.pl
this is domain but second level?
simple.com.pl
Now in my script there is something like: if i don't put www, it adds and does
dig www.e-learning.go4progress.pl
dig e-learning.go4progress.pl
dig go4progress.pl
he counts dots and subtracts 1, but problem is when domain looks like
simple.com.pl
because script make dig too for
com.pl
I don't check many domain which contains com.pl, co.uk, gov.pl. I've got an idea to make array and compare this what i put in script with array, when it finds in string component of array he subtracts 2 instead 1 ;)
I paste a part of script to better understand me why he substracks 1.
url="`echo $1 | sed 's~http[s]*://~~g' | sed 's./$..' | awk '!/^www/ {$0="www." $0}1'`"
ii=1
dots="`echo $url | grep -o "\." | wc -l`"
while [ $ii -le $dots] ; do
cut="`echo $url | cut -d "." -f$ii-9`"
ip="`dig +short $cut`"
host="`dig -x $ip +short`"
if [[ -z "$host" ]]; then
host="No PTR"
fi
echo "strona: $cut Host: $host"
ii=$[ii + 1]
Maybe you have diffrent idea how to help with my problem.
Second question is how to distinguish subdomain from domain.
I need compare mx records of subdomain(when the url contains subdomain) and mx records top level domain.
I waiting for your response ;)
My solution to find the domain as registered with the registrar:
wget https://raw.githubusercontent.com/gavingmiller/second-level-domains/master/SLDs.csv
DOMAIN="www.e-learning.go4progress.co.uk";
KEEPPARTS=2;
TWOLEVELS=$( /bin/echo "${DOMAIN}" | /usr/bin/rev | /usr/bin/cut -d "." --output-delimiter=".\\" -f 1-2 | /usr/bin/rev );
if /bin/grep -P ",\.${TWOLEVELS}" SLDs.csv >/dev/null; then
KEEPPARTS=3;
fi
DOMAIN=$( /bin/echo "${DOMAIN}" | /usr/bin/rev | /usr/bin/cut -d "." -f "1-${KEEPPARTS}" | /usr/bin/rev );
echo "${DOMAIN}"
Thanks to https://github.com/gavingmiller/second-level-domains and https://github.com/medialize/URI.js/issues/17#issuecomment-3976617
I'm having an issue when i try to port my bash script to nagios.The scripts works fine when I run on console, but when I run it from Nagios i get the msg "(null)" - In the nagios debug log I see that it parse the script well but it returns the error msg..
I'm not very good at scripting so i guess i'll need some help
The objective of the script is to check *.ears version from some servers, md5 them and compare the output to see if the version matches or not.
To do that, i have a json on these servers that prints the name of the *.ear and his md5.
so.. The first part of the script gets that info from the json with curl and stores just the md5 number on a .tempfile , then it compares both temp files and if they match i got the $STATE_OK msg. If they dont , it creates a .datetmp file with the date ( the objective of this is to print a message after 48hs of inconsistence). Then, i make a diff of the .datetmp file and the days i wanna check if the result is less than 48hrs it prints the $STATE_WAR, if the result is more than 48 hrs it Prints the $STATE_CRI
The sintaxis of the script is " $ sh script.sh nameoftheear.ear server1 server2 "
Thanks in advance
#/bin/bash
#Variables For Nagios
cont=$1
bas1=$2
bas2=$3
## Here you set the servers hostname
svr1= curl -s "http://$bas1.domain.com:7877/apps.json" | grep -Po '"EAR File":.*? [^\\]",' | grep $cont | awk '{ print $5 }' > .$cont-tmpsvr1
svr2= curl -s "http://$bas2.domain.com:7877/apps.json" | grep -Po '"EAR File":.*? [^\\]",' | grep $cont | awk '{ print $5 }' > .$cont-tmpsvr2
file1=.$cont-tmpsvr1
file2=.$cont-tmpsvr2
md51=$(head -n 1 .$cont-tmpsvr1)
md52=$(head -n 1 .$cont-tmpsvr2)
datenow=$(date +%s)
#Error Msg
ERR_WAR="Not updated $bas1: $cont $md51 --- $bas2: $cont $md52 "
ERR_CRI="48 hs un-updated $bas1: $cont $md51 --- $bas2: $cont $md52 "
OK_MSG="Is up to date $bas1: $cont $md51 --- $bas2: $cont $md52 "
STATE_OK=0
STATE_WARNING=1
STATE_CRITICAL=2
##Matching md5 Files
if cmp -s "$file1" "$file2"
then
echo $STATE_OK
echo $OK_MSG
# I do the rm to delete the date tmp file so i can get the $STATE_OK or $STATE_WARNING
rm .$cont-datetmp
exit 0
elif
echo $datenow >> .$cont-datetmp
#Vars to set modification date
datetmp=$(head -n 1 .$cont-datetmp)
diffdate=$(( ($datenow - $datetmp) /60 ))
#This var is to set the time of the critical ERR
days=$((48*60))
[ $diffdate -lt $days ]
then
echo $STATE_WARNING
echo $ERR_WAR
exit 1
else
echo $STATE_CRITICAL
echo $ERR_CRI
exit 2
fi
I am guessing some kind of permission problem - more specifically I don't think the nagios user can write to it's own home directory. You either fix those permissions or write to a file in /tmp (and consider using mktemp?).
...but ideally you'd skip writing all those files, as far as I can see all of those comparisons etc could be kept in memory.
UPDATE
Looked at your script again - I see some obvious errors you can look into:
You are printing out the exit value before you print the message.
You print the exit value rather than exit with the exit value.
...so this:
echo $STATE_WARNING
echo $ERR_WAR
exit 1
Should rather be:
echo $ERR_WAR
exit $STATE_WARNING
Also I am wondering if this is really the script or if you missed something when pasting. There seems to be missing an 'if' and also a superfluous line break in your last piece of code? Should rather be:
if [ $diffdate -lt $days ]
then
...
else
...
fi