25

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"
edumike
  • 3,149
  • 7
  • 27
  • 33
  • This has nothing to do with `ping`, but what are you trying to accomplish by `echo`ing “say”? Your introductory paragraph implies that you're trying to execute the `say` command, which isn’t going to happen if you just `echo` the word. – Lawrence Velázquez May 25 '11 at 02:59
  • 1
    @Lawrence, those are _backticks,_ not quotes. They will execute the `say` command and echo its output. – paxdiablo May 25 '11 at 03:01
  • 1
    Oops, missed that pretty badly; my apologies. Although I’m still not sure what’s being accomplished there; `say` never outputs anything to stdout. – Lawrence Velázquez May 25 '11 at 03:16
  • 1
    There's no need for $? in your solution; if ping... works fine. Also, you can avoid the extra variable by using break: while :; do if ping; then break; fi; done – Mark Edgar Mar 06 '13 at 22:34
  • See also: [ServerFault: How to ping in linux until host is known?](https://serverfault.com/questions/42021/how-to-ping-in-linux-until-host-is-known). This appears to have some very promising and short answers which work with Linux too (which is missing the convenient `-o` option), not just with MacOs which has that option. – Gabriel Staples Nov 18 '21 at 06:08

7 Answers7

40

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
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 2
    The count = 1 line should be ((count = 1)) to deal with the spaces. – MSumulong Jan 23 '14 at 21:14
  • 2
    @MSumulong, correct, it's hard to believe I (and everyone else) has missed that for the last three-odd years. Fixed up, and made all assignments to `count` consistent as well. – paxdiablo Jan 24 '14 at 00:07
  • Using ping loops without wait/sleep by a non-admin user triggers flood prevention mechanism that causes inconsistent results. If you really intend to flood the network, then you might need to add `-A` – Payam Aug 30 '21 at 17:31
14

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"
Rob Davis
  • 15,597
  • 5
  • 45
  • 49
  • 9
    This worked for me: ping -c 2 8.8.8.8 > /dev/null && echo "up" || echo "down" – Katie Apr 22 '13 at 22:13
  • 2
    The flag `-c 100000` means try `100000` times. With out this flag it will retry endlessly (you would need to interrupt it). Still because of `-o`it will return on the first successful ping. – luckydonald Jul 11 '17 at 16:22
  • 2
    This only works on OSX. Ping doesn't have the -o flag on windows or Linux. – Dumle29 Sep 21 '17 at 13:04
  • Yes, tthe original question and this answer both assume macOS. Note that the point it to use the Mac's `say` command to report a successful ping. In the first comment under this answer, you'll see a variant for using `ps` and `echo` on linux. – Rob Davis Sep 21 '17 at 18:58
  • Linux Ubuntu 18.04's `ping` doesn't have the `-o` option. Is there an equivalent for me? – Gabriel Staples Nov 18 '21 at 05:56
  • I just asked here: [Unix & Linux: Equivalent of `ping -o` on Linux](https://unix.stackexchange.com/q/678045/114401) – Gabriel Staples Nov 18 '21 at 06:00
12

This can also be done with a timeout:

# Ping until timeout or 1 successful packet
ping -w (timeout) -c 1
MikeTwo
  • 1,228
  • 1
  • 14
  • 13
3

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
Michaelangel007
  • 2,798
  • 1
  • 25
  • 23
3

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.

EDIT 2: Chris points out that my logic is reversed, which is just embarrassing. Additionally, the echo commands (inherited from the question) serve no purpose, and there's no need to require bash specifically. I'm disappointed in 12-years-ago me. Today I would write:

#!/bin/sh
if ping -oc 100000 8.8.8.8 >/dev/null; then
    say 'the Internet is back up'
else
    say timeout
fi
  • 1
    Linux Ubuntu 18.04's `ping` doesn't have the `-o` option. Is there an equivalent for me? – Gabriel Staples Nov 18 '21 at 05:55
  • 2
    You have your code the wrong way round. This will say "timeout" when there is a connection (ping returns true) and "internet back up" when there is no connection (ping returns false). – Chris Jan 09 '23 at 18:20
  • @Chris Thanks, you're right. I don't have the slightest idea what I was thinking 12 years ago (!). My best guess is that I overlooked the fact that removing `grep` from the OP's first code snippet flips the exit status logic (and didn't bother testing ). – Lawrence Velázquez May 02 '23 at 06:50
1

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.)

pwaller
  • 601
  • 6
  • 10
0

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.

John Karahalis
  • 3,369
  • 2
  • 15
  • 20