Bash: Breaking out of IF loop in FOR loop - arrays

I am trying to combine a FOR loop (that iterates over IP addresses) and an IF loop (that uses nc to check for a successful ssh connection before moving on).
I have an array ${INSTANCE_IPS[#]} with the IP addresses in it (at the moment it contains 2 IP Addresses). Here is the code:
while [ $ITERATION -le 30 ]
do
for instance in ${INSTANCE_IPS[#]}
do
nc -w 2 $instance 22 > /dev/null
if [ $? -eq 0 ]
then echo "connection succeeded to $instance"
else
ITERATION=$((ITERATION+1))
echo ITERATION=$ITERATION
echo "[info] connection to $instance unsuccessful. trying again. iteration=$ITERATION"
sleep 20
fi
done
done
The 'else' statement in the IF loop works fine. It is the 'then' statement I am having problems with... I don't know how to break out of the IF loop once the connections are successful. Here's an example output when I run the above:
connection succeeded to 10.11.143.171
connection succeeded to 10.11.143.170
connection succeeded to 10.11.143.171
connection succeeded to 10.11.143.170
connection succeeded to 10.11.143.171
connection succeeded to 10.11.143.170
If I use break after then echo "connection succeeded to $instance then it only iterates through 1 IP address and never breaks out:
connection succeeded to 10.11.143.171
connection succeeded to 10.11.143.171
connection succeeded to 10.11.143.171
Ideally I think the best thing to do would be to query the number of elements in the array, then perform a netcat connection an increment some value by 1 until it equals the number of elements in the array, but I'm really not sure how to dot that.
Any help is appreciated :) Please let me know if you need any more information.
Cheers

Reformulate your logic. You can't break if something succeeds, because you don't know whether another item might fail.
Instead, keep a flag saying whether you've successfully gone through all of them, and set it to false if something fails. At this point, you can also break and wait.
ITERATION=0
all_succeeded=false
while [ "$all_succeeded" = "false" -a $ITERATION -le 30 ]
do
all_succeeded=true
for instance in ${INSTANCE_IPS[#]}
do
nc -w 2 $instance 22 > /dev/null
if [ $? -eq 0 ]
then
echo "connection succeeded to $instance"
else
all_succeeded=false
echo "[info] connection to $instance unsuccessful."
sleep 20
break
fi
done
let ITERATION++
done
if [ "$all_succeeded" = "true" ]
then
echo "It worked"
else
echo "Giving up"
fi

Related

Passing variables (array index) from remote ssh to global function

BASH
I'm writing a script to check services on a selection of hosts for a selection of users. The selected users are stored in an array. The script connects via ssh to each selected host and would go looping through the array logging in to each selected user. However the script fails to read the index of array given by for cycle during ssh - it takes only the 1st (0) index of the array. I've tried almost every possible quoting, escaping, etc... When i escape the $j variable in the echo or "checking" step, i get a syntax error: operand expected (error token is ā€œ$jā€) error. Any ideas on how to make it work? Is it even possible?
usr1=("user1" "user2")
function tusr {
declare -n tmpp="$1";
echo "${tmpp[$j]}"
}
function testchk {
echo "Logging in as $myuser to $1"
ssh -tq $myuser#$1.bar.com "
for j in ${!usr1[#]}; do
echo \$j
echo ${usr1[$j]}
echo "Checking $(tusr usr1 "\$j"):"
done
"
}
srv="foo"
testchk "$srv"
When echoing the escaped $j, it prints out the correct value.
My output:
0
user1
Checking user1:
1
user1
Checking user1:
Expected output:
0
user1
Checking user1:
1
user2
Checking user2:
The remote shell doesn't know of your local variables or functions, so you'll need to pass their declarations.
ssh -tq $myuser#$1.bar.com "
$(declare -p usr1)
$(declare -f tusr)
for j in \${!usr1[#]}
do
echo \"\$j\"
echo \"\${usr1[\$j]}\"
echo \"Checking \$(tusr usr1 \$j):\"
done
"
But what you're trying to do is a real mix of things and it's confusing. Are you trying to make the remote user to execute the function tusr locally ??

Check database connectivity

I'm writing a unix script to check for database connectivity in a server. When my database connection gets errored out or when there is delay observed in connecting to the database, I want the output as "Not connected". In case it gets connected, my output should be "Connected". It is a Oracle databse.
When there is delay in database connectivity, my code is not working and my script gets hung. What changes should I make in my code so that it is able to handle both the conditions(when I get an error connecting to the database and when there is delay observed in connecting to the database)??
if sqlplus $DB_USER/$DB_PASS#$DB_INSTANCE< /dev/null | grep 'Connected to'; then
echo "Connectivity is OK"
else
echo "No Connectivity"
fi
The first thing to add to your code is a timeout. Checking database connectivity is not easy and there can be all kinds of problems in the various layers that your connection passes. A timeout gives you the option to break out of a hanging session and continue the task with reporting that the connection failed.
googleFu gave me a few nice examples:
Timeout a command in bash without unnecessary delay
If you are using Linux, you can use the timeout command to do what you want. So the following will have three outcomes, setting the variable RC as follows:
"Connected to" successful: RC set to 0
"Connected to" not found: RC set to 1
sqlplus command timed out after 5 minutes: RC set to 124
WAIT_MINUTES=5
SP_OUTPUT=$(timeout ${WAIT_MINUTES}m sqlplus $DB_USER/$DB_PASS#$DB_INSTANCE < /dev/null )
CMD_RC=$?
if [ $CMD_RC -eq 124 ]
then
ERR_MSG="Connection attempt timed out after $WAIT_MINUES minutes"
RC=$CMD_RC
else
echo $SP_OUTPUT | grep -q 'Connected to'
GREP_RC=$?
if [ $GREP_RC -eq 0 ]
then
echo "Connectivity is OK"
RC=0
else
ERR_MSG="Connectivity or user information is bad"
RC=1
fi
fi
if [ $RC -gt 0 ]
then
# Add code to send email with subject of $ERR_MSG and body of $SP_OUTPUT
echo Need to email someone about $ERR_MSG
fi
exit $RC
I'm sure there are several improvements to this, but this will get you started.
Briefly, we use the timeout command to wait the specified time for the sqlplus command to run. I separated out the grep as a separate command to allow the use of timeout and to allow more flexibility in checking additional text messages.
There are several examples on StackOverflow on sending email from a Linux script.

How can I do a function that outputs the not of another function in bash shell?

I have an existing function is_active_instance, which determines if a database instance is running (true) or not. I am working in a new function called is_inactive_instance which needs to return true if is_active_instance returns false.
How can I call is_active_instance from is_inactive_instance and negate its return to return True to main program?
I already tried to call is_instance_active with ! to change the result of the original function.
is_active_instance(){
dbservice=""
if is_mysql_db
then
dbservice="mysqld"
elif is_mariadb_db
then
dbservice="mysqld"
elif is_postgre_db
then
dbservice='postgresql'
fi
[ $(ps -ef | grep -v grep | grep $dbservice* | wc -l) > 0 ]
}
is_inactive_instance(){
[ [ ! is_active_instance ] ]
}
if is_active_instance
then
echo "Is active instance"
elif is_inactive_instance
then
echo "Is inactive instance"
else
echo "Other result"
fi
In Main body I will need to be able to detect if the instance is running, stopped or other for my purposes.
Don't use any [s:
is_inactive_instance(){
! is_active_instance
}
Also see Comparing numbers in Bash for how to make your is_active_instance work.
Here is an example of how to do this in BASH. Your code is on the right track but needs syntax changes to work in BASH.
Instead of checking for a NOT you would check for "Yes" or "No", and you may change the outputs to zeroes and ones if you wish and test for those.
Copy the code between CODE STARTS and CODE ENDS into ./active_instance.sh.
Type the line below and press RETURN.
chmod 755 ./active_instance.sh
CODE STARTS HERE ==================
#!/usr/bin/env bash
for some_db in mariadb mysqld postgres oracle sybase
do
echo -n "Checking ${some_db}..."
set `ps -ef|grep -v grep|grep ${some_db}|wc`
if test ${1} -gt 0
then
echo "Yes"
else
echo "No"
fi
done
CODE ENDS HERE ==================
To run, type the line below and press RETURN.
./active_instance.sh
Sample execution:
./active_instance.sh
Checking mariadb...Yes
Checking mysqld...Yes
Checking postgres...Yes
Checking oracle...No
Checking sybase...No

How can I include a loop to validate a file exists on a remote host until the statement is true in KSH?

so I am trying to validate if there is a file that exists on a remote host, if so, say valid. If it doesn't, then say it's invalid. I've got that to work in a function. However, I can't seem to come up with a loop that would confirm this statement is true to move on with the script. Or, after messing around, it does move on with the script but it doesn't correctly validate the file to repeat asking a question. After many failed attempts, this is what I've come up with.
validate_d()
{
valid="file exists"
invalid="file doesn't exist"
ssh -q *host* [[ -f $userpath]] && echo "valid" || echo "invalid";
}
while (*this is my script asking the user if they input the correct path, if not, keep asking for correct path*)
done
validate_d
until [[ this statement is confirmed that the file exists ]]; do
print $invalid
read userpath
done
In the until statement, I can't figure out how to validate the file exists and the condition is true. Should I be using an until or while with if? Any kind of feedback would be a big help. Thank you!
There are going to be quite a few ways to a) test for the existence of a remote file and b) how to pass back a valid/invalid code.
Here's a bare bones example:
while true
do
printf "Enter fullpath of file to check for: "
read userpath
ssh -q hostname [ -f "${userpath:-undefined}" ]
[ $? -eq 0 ] && echo 'valid' && break
echo 'invalid'
done
The [ -f ${userpath} ] is executed remotely on the host named 'remotehost'.
If the file represented by ${userpath} is found then a 0 (true) is returned else a 1 (false) is returned.
The $? contains our return code from the ssh/-f ${userpath} construct.
If the file is found ($?=0) we echo 'valid' and break out of the loop, else we print 'invalid' and perform the next iteration of the loop.
Assume the files /tmp/test1 and /tmp/test2 do not exist on the remotehost, but the file /tmp/test3 does exist, running the above code snippet looks like:
$ while true
do
printf "Enter fullpath of file to check for: "
read userpath
ssh -q hostname [ -f "${userpath:-undefined}" ]
[ $? -eq 0 ] && echo 'valid' && break
echo 'invalid'
done
Enter fullpath of file to check for: /tmp/test1
invalid
Enter fullpath of file to check for: /tmp/test2
invalid
Enter fullpath of file to check for: /tmp/test3
valid
$

repeat pipe command until first command succeeds and second command fails

I am trying to figure out how to get my bash script to work. I have a the following command:
curl http://192.168.1.2/api/queue | grep -q test
I need it to repeat until the first command in the pipline succeeds (meaning the server responds) and the second command fails (meaning the pattern is not found or the queue is empty). I've tried a number of combinations but just can't seem to get it. I looked at using $PIPESTATUS but can't get it to function in a loop how I want. I've tried all kind of variations but can't get it to work. This is what I am currently trying:
while [[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 1 ]]
do curl http://192.168.1.2 | grep -q regular
echo "The exit status of first command ${PIPESTATUS[0]}, and the second command ${PIPESTATUS[1]}"
sleep 5
done
Although it's not really clear what kind of output is returned by the curl call, maybe you are looking for something like this:
curl --silent http://192.168.1.2 |while read line; do
echo $line |grep -q regular || { err="true"; break }
done
if [ -z "$err" ]; then
echo "..All lines OK"
else
echo "..Abend on line: '$line'" >&2
fi
Figured it out. Just had to re-conceptualize it. I couldn't figure it out strictly with a while or until loop but creating an infinite loop and breaking out of it when the condition is met worked.
while true
do curl http://192.168.1.2/api/queue | grep -q test
case ${PIPESTATUS[*]} in
"0 1")
echo "Item is no longer in the queue."
break;;
"0 0")
echo "Item is still in the queue. Trying again in 5 minutes."
sleep 5m;;
"7 1")
echo "Server is unreachable. Trying again in 5 minutes."
sleep 5m;;
esac
done

Resources