Bash loop ping successful - bash

I'm thinking that this needs to be changed to a while clause, at the moment it'll wait till all 10000 pings are done, I need it to return when the ping is successful. The program "say" is on OSX it makes the computer speak.
#!/bin/bash
echo begin ping
if ping -c 100000 8.8.8.8 | grep timeout;
then echo `say timeout`;
else echo `say the internet is back up`;
fi
OK I don't have rights to answer my own question so here's my answer for it after playing around:
Thanks, yeah I didn't know about $? until now. Anyway now I've gone and made this. I like that yours doesn't go forever but in my situation I didn't need it to stop until it's finished.
#!/bin/bash
intertube=0
echo "begin ping"
while [ $intertube -ne 1 ]; do
ping -c 3 google.com
if [ $? -eq 0 ]; then
echo "ping success";
say success
intertube=1;
else
echo "fail ping"
fi
done
echo "fin script"

You probably shouldn't rely on textual output of a command to decide this, especially when the ping command gives you a perfectly good return value:
The ping utility returns an exit status of zero if at least one response was heard from the specified host; a status of two if the transmission was successful but no responses were received; or another value from <sysexits.h> if an error occurred.
In other words, use something like:
((count = 60)) # Maximum number to try.
while [[ $count -ne 0 ]] ; do
ping -c 1 8.8.8.8 # Try once.
rc=$?
if [[ $rc -eq 0 ]] ; then
((count = 1)) # If okay, flag loop exit.
else
sleep 1 # Minimise network storm.
fi
((count = count - 1)) # So we don't go forever.
done
if [[ $rc -eq 0 ]] ; then # Make final determination.
echo `say The internet is back up.`
else
echo `say Timeout.`
fi

You don't need to use echo or grep. You could do this:
ping -oc 100000 8.8.8.8 > /dev/null && say "up" || say "down"

This can also be done with a timeout:
# Ping until timeout or 1 successful packet
ping -w (timeout) -c 1

I use this Bash script to test the internet status every minute on OSX
#address=192.168.1.99 # forced bad address for testing/debugging
address=23.208.224.170 # www.cisco.com
internet=1 # default to internet is up
while true;
do
# %a Day of Week, textual
# %b Month, textual, abbreviated
# %d Day, numeric
# %r Timestamp AM/PM
echo -n $(date +"%a, %b %d, %r") "-- "
ping -c 1 ${address} > /tmp/ping.$
if [[ $? -ne 0 ]]; then
if [[ ${internet} -eq 1 ]]; then # edge trigger -- was up now down
echo -n $(say "Internet down") # OSX Text-to-Speech
echo -n "Internet DOWN"
else
echo -n "... still down"
fi
internet=0
else
if [[ ${internet} -eq 0 ]]; then # edge trigger -- was down now up
echo -n $(say "Internet back up") # OSX Text-To-Speech
fi
internet=1
fi
cat /tmp/ping.$ | head -2 | tail -1
sleep 60 ; # sleep 60 seconds =1 min
done

If you use the -o option, BSD ping (which is also on macOS) will exit after receiving one reply packet.
Further reading: https://www.freebsd.org/cgi/man.cgi?query=ping
EDIT: paxdiablo makes a very good point about using ping’s exit status to your advantage. I would do something like:
#!/usr/bin/env bash
echo 'Begin ping'
if ping -oc 100000 8.8.8.8 > /dev/null; then
echo $(say 'timeout')
else
echo $(say 'the Internet is back up')
fi
ping will send up to 100,000 packets and then exit with a failure status—unless it receives one reply packet, in which case it exits with a success status. The if will then execute the appropriate statement.

Here's my one-liner solution:
screen -S internet-check -d -m -- bash -c 'while ! ping -c 1 google.com; do echo -; done; echo Google responding to ping | mail -s internet-back my-email#example.com'
This runs an infinite ping in a new screen session until there is a response, at which point it sends an e-mail to my-email#example.com. Useful in the age of e-mail sent to phones.
(You might want to check that mail is configured correctly by just running echo test | mail -s test my-email#example.com first. Of course you can do whatever you want from done; onwards, sound a bell, start a web browser, use your imagination.)

I liked paxdiablo's script, but wanted a version that ran indefinitely. This version runs ping until a connection is established and then prints a message saying so.
echo "Testing..."
PING_CMD="ping -t 3 -c 1 google.com > /dev/null 2>&1"
eval $PING_CMD
if [[ $? -eq 0 ]]; then
echo "Already connected."
else
echo -n "Waiting for connection..."
while true; do
eval $PING_CMD
if [[ $? -eq 0 ]]; then
echo
echo Connected.
break
else
sleep 0.5
echo -n .
fi
done
fi
I also have a Gist of this script which I'll update with fixes and improvements as needed.

Related

Bash elif not being used it seems

Just started looking at bash scripts yesterday and wanted to make a script for work where in I ping different addresses on a network and using ssh keys I login and shutdown mikrotiks/junipers/computers in order. I came up with this and it seems to work for the 'mikrotik' array but not for the 'other' elif statement. I'm testing this by just changing an IP address from the MikroTik array to the 'other' array to trigger the elif but it doesn't seem to do anything. Just goes straight to the else statement.
This is one of my first proper bash scripts and assembled this using other examples, stuck here for some reason.
#!/bin/bash
#first array to make the script turn off the nodes in the order I want.
targets=(
192.168.10.10
192.168.10.11
192.168.10.2
192.168.10.1
192.168.10.3
192.168.50.21
192.168.50.3
192.168.50.20
192.168.50.2
192.168.50.1
192.168.50.22
)
mikrotik=(192.168.10.1 192.168.10.2 192.158.50.1 192.168.50.2 192.168.10.5) #Mikrotik addresses
other=(192.168.50.3 192.168.10.3 192.168.10.10 192.168.10.11 192.168.50.21 192.168.50.20 192.168.50.22) #other addresses
for target in "${targets[#]}" #For each index of targets do...
do
ping -c1 $target > /dev/null #ping each ip address
if [[ ($? -eq 0) && (${mikrotik[*]} =~ "$target") ]] #if ping successful and target is within mikrotik array
then
echo "$target mikrotik has replied and will now shutdown"
ssh $target "system shutdown"
echo "..................................."
elif [[ ($? -eq 0) && (${other[*]} =~ "$target") ]] #if ping successful and target within the juniper or computer array
then
echo "$target device has replied and will now shutdown"
ssh $target "shutdown now"
echo "..................................."
else
echo "$target didn't reply moving onto next target"
echo "..................................."
fi
done
$? contains the last command executed. So you do:
command1
if command2 ... $? ...; then # this command has it's own exit status
# and it's nonzero because the body is not executed!
...
elif command3 ... $? ...; then # the __LAST__ command here is command2
# $? has the exit status of command2, __not__ command1
...
fi
The $? inside elif has the exit status of [[ executed in the first if. Because first if body was not executed, the [[ exited with nonzero exit status - so $? in the elif clause will always be nonzero. Generally, using $? like doing command; if (($?)); then is an antipattern - don't use it. Check the exit value of the command instead - if command; then. You want to check the exit status of ping, so test it with if:
if ping -c1 $target > /dev/null; then
# those if's could be refactored too to extract common code
if [[ ${mikrotik[*]} =~ "$target" ]]; then
name=mikrotik
command=(system shutdown)
elif [[ ${other[*]} =~ "$target" ]]; then
name=device
command=(shutdown now)
else
handle error
fi
echo "$target $name has replied and will now shutdown"
ssh "$target" "${command[#]}"
else
echo "$target didn't reply moving onto next target"
fi
echo "..................................."
If you really want to store the exit status of command for later use, save it in a temporary variable right after using it, then use that temporary variable.
ping ...
pingret=$?
if ((pingret == 0)); then something; fi

Why range of my variable is limited into "tail -f scope"?

I have application which register OnLine or Offline status which is stored in my test.log file. Status can be changed every second or minute or at all during many hours. Once per 15 minutes I need to send actual status to external machine [my.ip.address]. In below example let's assume that I need to just echo actual status.
I wrote below script which is watching my test.log and stores actual status in FLAG variable. However I cannot send it (or echo) to my external machine [my.ip.address] cause FLAG is not saved properly. Do you have any idea what's wrong in below example?
#!/usr/bin/env bash
FLAG="OffLine"
FLAG_tmp=$FLAG
tail -f /my/path/test.log | while read line
do
if [[ $line == *"OnLine"* ]]; then
FLAG_tmp="OnLine"
fi
if [[ $line == *"OffLine"* ]]; then
FLAG_tmp="OffLine"
fi
if [ "$FLAG" != "$FLAG_tmp" ];then
FLAG=$FLAG_tmp
echo $FLAG # it works, now FLAG stores actual true status
fi
done &
# till this line I suppose that everything went well but here (I mean out of
# tail -f scope) $FLAG stores only OffLine - even if I change it to OnLine 4 lines before.
while :
do
#(echo $FLAG > /dev/udp/[my.ip.address]/[port])
echo "$FLAG" # for debug purpose - just echo actual status.
# However it is always OffLine! WHY?
#sleep 15*60 # wait 15 minutes
sleep 2 # for debug, wait only 2 sec
done
EDIT:
Thanks guys for your answers, but I still don't get a solution.
#123: I corrected my code basing on your example, but it seems to not working.
#!/usr/bin/env bash
FLAG="OffLine"
FLAG_tmp=$FLAG
while read line
do
if [[ $line == *"OnLine"* ]]; then
FLAG_tmp="OnLine"
fi
if [[ $line == *"OffLine"* ]]; then
FLAG_tmp="OffLine"
fi
if [ "$FLAG" != "$FLAG_tmp" ];then
FLAG=$FLAG_tmp
#echo $FLAG
fi
done & < <(tail -f /c/vagrant_data/iso/rpos/log/rpos.log)
while :
do
echo "$FLAG"
sleep 2
done
#chepner: Do you have some exact proposals how can I solve this problem?
I think you are making it overly complicated. If you just want to send yourself the last state of OffLine or OnLine you might try something like this:
#!/bin/bash
while :
do
FLAG="$(egrep 'OffLine|OnLine' test.log | tail -1)"
if [ $(echo "$FLAG" | grep OffLine) ]
then
FLAG=OffLine
else
FLAG=OnLine
fi
echo $FLAG
sleep 2
done
Or, if you really want to keep the two processes,
#!/bin/bash
echo OffLine > status
tail -f test.log | while read line
do
if [[ "$line" =~ "OffLine" ]]
then
echo OffLine > status
elif [[ "$line" =~ "OnLine" ]]
then
echo OnLine > status
fi
done &
while :
do
cat status > /dev/udp/[my.ip.address]/[port])
sleep 15*60
done

How to read and run a command 10 lines at a time in bash?

I have a bash script that connects to servers via SSH to run a command. The script gets the IP address from a file.
Problem: if I have 500 IPs in the file, I don't want to simultaneously open or try to open 500 connections. I want to do, lets say, 10 at a time in order to save resources.
How do I run the command via SSH 10 servers at a time?
Here is my script:
#/bin/bash
nodes="big_list_of_nodes.txt"
while read node; do
# Running in background
(uptime=`ssh -o ConnectTimeout=5 $node uptime 2>&1`
if [ $? -eq 0 ]; then
echo "$node uptime: $uptime"
else
echo "Connection timeout for node $node"
fi) &
done < $nodes
# Wait for all jobs to finish
wait
You want to write a function to do all the work for you that takes an IP address as an argument. Then use parallel to read in the file and distribute work to the function:
function get_uptime()
{
node=$1
uptime=`ssh -o ConnectTimeout=5 $node uptime 2>&1`
if [ $? -eq 0 ]; then
echo "$node uptime: $uptime"
else
echo "Connection timeout for node $node"
fi
}
export -f get_uptime
parallel -j 10 --will-cite -a big_list_of_nodes.txt get_uptime
The -j argument tells parallel how many jobs can be active at a time.
I was able to figure it out and make it work.
I add N lines to an array, then I process everything in that array. Then the array is empty and the process is repeated.
This way, you can have a file with hundreds of hostnames or IP address and process in N chunks.
#/bin/bash
nodes=`cat big_list_of_nodes.txt`
for node in $nodes
do
array+=($node)
if [ ${#array[#]} -gt 10 ]; then
for n in ${array[#]}
do
(uptime=`ssh -o ConnectTimeout=5 $n uptime 2>&1`
if [ $? -eq 0 ]; then
echo "$n uptime: $uptime"
else
echo "Connection timeout for node $n"
fi) &
done
wait
array=()
fi
done
if [ ${#array[#]} -gt 0 ]; then
for n in ${array[#]}
do
(uptime=`ssh -o ConnectTimeout=5 $n uptime 2>&1`
if [ $? -eq 0 ]; then
echo "$n uptime: $uptime"
else
echo "Connection timeout for node $n"
fi) &
done
wait
fi

Checking host availability by using ping in bash scripts

I want to write a script, that would keep checking if any of the devices in network, that should be online all day long, are really online. I tried to use ping, but
if [ "`ping -c 1 some_ip_here`" ]
then
echo 1
else
echo 0
fi
gives 1 no matter if I enter valid or invalid ip address. How can I check if a specific address (or better any of devices from list of ip addresses) went offline?
Ping returns different exit codes depending on the type of error.
ping 256.256.256.256 ; echo $?
# 68
ping -c 1 127.0.0.1 ; echo $?
# 0
ping -c 1 192.168.1.5 ; echo $?
# 2
0 means host reachable
2 means unreachable
You don't need the backticks in the if statement. You can use this check
if ping -c 1 some_ip_here &> /dev/null
then
echo "success"
else
echo "error"
fi
The if command checks the exit code of the following command (the ping). If the exit code is zero (which means that the command exited successfully) the then block will be executed. If it return a non-zero exit code, then the else block will be executed.
I can think of a one liner like this to run
ping -c 1 127.0.0.1 &> /dev/null && echo success || echo fail
Replace 127.0.0.1 with IP or hostname, replace echo commands with what needs to be done in either case.
Code above will succeed, maybe try with an IP or hostname you know that is not accessible.
Like this:
ping -c 1 google.com &> /dev/null && echo success || echo fail
and this
ping -c 1 lolcatz.ninja &> /dev/null && echo success || echo fail
There is advanced version of ping - "fping", which gives possibility to define the timeout in milliseconds.
#!/bin/bash
IP='192.168.1.1'
fping -c1 -t300 $IP 2>/dev/null 1>/dev/null
if [ "$?" = 0 ]
then
echo "Host found"
else
echo "Host not found"
fi
This is a complete bash script which pings target every 5 seconds and logs errors to a file.
Enjoy!
#!/bin/bash
FILE=errors.txt
TARGET=192.168.0.1
touch $FILE
while true;
do
DATE=$(date '+%d/%m/%Y %H:%M:%S')
ping -c 1 $TARGET &> /dev/null
if [[ $? -ne 0 ]]; then
echo "ERROR "$DATE
echo $DATE >> $FILE
else
echo "OK "$DATE
fi
sleep 5
done
FYI,
I just did some test using the method above and if we use multi ping (10 requests)
ping -c10 8.8.8.8 &> /dev/null ; echo $?
the result of multi ping command will be "0" if at least one of ping result reachable,
and "1" in case where all ping requests are unreachable.
up=`fping -r 1 $1 `
if [ -z "${up}" ]; then
printf "Host $1 not responding to ping \n"
else
printf "Host $1 responding to ping \n"
fi
for i in `cat Hostlist`
do
ping -c1 -w2 $i | grep "PING" | awk '{print $2,$3}'
done
This seems to work moderately well in a terminal emulator window. It loops until there's a connection then stops.
#!/bin/bash
# ping in a loop until the net is up
declare -i s=0
declare -i m=0
while ! ping -c1 -w2 8.8.8.8 &> /dev/null ;
do
echo "down" $m:$s
sleep 10
s=s+10
if test $s -ge 60; then
s=0
m=m+1;
fi
done
echo -e "--------->> UP! (connect a speaker) <<--------" \\a
The \a at the end is trying to get a bel char on connect. I've been trying to do this in LXDE/lxpanel but everything halts until I have a network connection again. Having a time started out as a progress indicator because if you look at a window with just "down" on every line you can't even tell it's moving.
I liked the idea of checking a list like:
for i in `cat Hostlist`
do
ping -c1 -w2 $i | grep "PING" | awk '{print $2,$3}'
done
but that snippet doesn't care if a host is unreachable, so is not a great answer IMHO.
I ran with it and wrote
for i in `cat Hostlist`
do
ping -c1 -w2 $i >/dev/null 2>&1 ; echo $i $?
done
And I can then handle each accordingly.
check host every one second and send message when host is reach
while :;do ping -c 1 -w 1 -q 8.8.8.8 &>/dev/null && /root/telegram-send.sh "Host reacheble now" && break || sleep 1;done

bash: running cURLs in parallel slower than one after another

We have to cache quite a big database of data after each upload, so we created a bash script that should handle it for us. The script should start 4 paralel curls to the site and once they're done, start the next one from the URL list we store in the file.
In theory everything works ok, and the concept works if we run the run 4 processes from our local machines to the target site.
If i set the MAX_NPROC=1 the curl takes as long as it would if the browser hits the URL
i.e. 20s
If I set the MAX_NPROC=2 the time request took, triples.
Am I missing something? Is that an apache setting that is slowing us down? or is this a secret cURL setting that I'm missing?
Any help will be appreciated. Please find the bash script below
#!/bin/bash
if [[ -z $2 ]]; then
MAX_NPROC=4 # default
else
MAX_NPROC=$2
fi
if [[ -z $1 ]]; then
echo "File with URLs is missing"
exit
fi;
NUM=0
QUEUE=""
DATA=""
URL=""
declare -a URL_ARRAY
declare -a TIME_ARRAY
ERROR_LOG=""
function queue {
QUEUE="$QUEUE $1"
NUM=$(($NUM+1))
}
function regeneratequeue {
OLDREQUEUE=$QUEUE
echo "OLDREQUEUE:$OLDREQUEUE"
QUEUE=""
NUM=0
for PID in $OLDREQUEUE
do
process_count=`ps ax | awk '{print $1 }' | grep -c "^${PID}$"`
if [ $process_count -eq 1 ] ; then
QUEUE="$QUEUE $PID"
NUM=$(($NUM+1))
fi
done
}
function checkqueue {
OLDCHQUEUE=$QUEUE
for PID in $OLDCHQUEUE
do
process_count=`ps ax | awk '{print $1 }' | grep -c "^${PID}$"`
if [ $process_count -eq 0 ] ; then
wait $PID
my_status=$?
if [[ $my_status -ne 0 ]]
then
echo "`date` $my_status ${URL_ARRAY[$PID]}" >> $ERROR_LOG
fi
current_time=`date +%s`
old_time=${TIME_ARRAY[$PID]}
time_difference=$(expr $current_time - $old_time)
echo "`date` ${URL_ARRAY[$PID]} END ($time_difference seconds)" >> $REVERSE_LOG
#unset TIME_ARRAY[$PID]
#unset URL_ARRAY[$PID]
regeneratequeue # at least one PID has finished
break
fi
done
}
REVERSE_LOG="$1.rvrs"
ERROR_LOG="$1.error"
echo "Cache STARTED at `date`" > $REVERSE_LOG
echo "" > ERROR_LOG
while read line; do
# create the command to be run
DATA="username=user#server.com&password=password"
URL=$line
CMD=$(curl --data "${DATA}" -s -o /dev/null --url "${URL}")
echo "Command: ${CMD}"
# Run the command
$CMD &
# Get PID for process
PID=$!
queue $PID;
URL_ARRAY[$PID]=$URL;
TIME_ARRAY[$PID]=`date +%s`
while [ $NUM -ge $MAX_NPROC ]; do
checkqueue
sleep 0.4
done
done < $1
echo "Cache FINISHED at `date`" >> $REVERSE_LOG
exit
The network is almost always the bottleneck. Spawning more connections usually makes it slower.
You can try to see if parallel'izing it will do you any good by spawning several
time curl ...... &

Resources