-2

I can't figure out how to write a simple ping log. I only need either the ms value or the words "no connection".

I have myping=$(ping -c 1 10.0.10.1). I only need the value between "time=" and " ms", like 3.151 from "time=3.151 ms".

I could use awk, but then I get a lot of blank lines and I don't know how to get rid of them:

$ awk -F 'time=| ms' '{print $2}' <<< "$myping"

3.151

And then, how can I insert "no connection", when the ping result doesn't contain a ms value?

Thanks a lot!

3
  • Parsing fping might be easier, if that's an option
    – ilkkachu
    Commented Aug 22, 2024 at 5:41
  • no fping available Commented Aug 22, 2024 at 6:10
  • 2
    Please edit your question and i) add an example ping output and ii) tell us your operating system so we know what tools and what versions of tools you have available.
    – terdon
    Commented Aug 22, 2024 at 8:18

4 Answers 4

0

If using ping from iputils / Linux, which for ping -c1 localhost outputs something like this:

PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.026 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.026/0.026/0.026/0.000 ms

The second line will contain ms at the end only if the destination was reachable.

So, probably not the most elegant solution, but using sed:

sed -En '2{s/.*=([0-9.]+) ms$/\1/p; t; s/.*/no connection/p}'
  • -E: turn on ERE support
  • -n: and don't autoprint the pattern space;
  • 2{[...]}: if we're processing line #2
  • s/.*=([0-9.]+) ms$/\1/p and the line matches this pattern, replace the whole line with first captured group and print the line;
  • t: if a substitution was performed, start the new cycle,
  • s/.*/no connection/p}: otherwise substitute the whole line with no connection.
% ping -c1 localhost | sed -En '2{s/.*=([0-9.]+) ms$/\1/p; t; s/.*/no connection/p}'  
0.022
% ping -c1 192.168.1.127 | sed -En '2{s/.*=([0-9.]+) ms$/\1/p; t; s/.*/no connection/p}'
no connection
5
  • Using this in OSX/bash, I get the following error: ´sed: 1: "2{s/.*=([0-9.]+) ms$/\1 ...": unexpected EOF (pending }'s)´ Commented Aug 22, 2024 at 11:29
  • @GaryU.U.Unixuser Granted, this answer, assumes ping from iputils and GNU sed, and I don't even know what OSX is using (certainly not GNU sed), but are you sure you copy-pasted the command correctly?
    – kos
    Commented Aug 22, 2024 at 12:04
  • $ ping -c1 localhost | sed -En '2{s/.*=([0-9.]+) ms$/\1/p; t; s/.*/no connection/p}' sed: 1: "2{s/.*=([0-9.]+) ms$/\1 ...": unexpected EOF (pending }'s) Commented Aug 22, 2024 at 12:05
  • @GaryU.U.Unixuser Ah I see, it's BSD sed complaining for POSIX-uncompliance. See here. You should be able to fix that by replacing semicolons with newlines, making it a multi-line command. If you end up trying that please let me know if it works ok so I can add it to the answer, I don't have a BSD version of sed to try it on myself
    – kos
    Commented Aug 22, 2024 at 12:11
  • I have tried to replace the ; with \n or with actual new lines, but I keep getting this error message: sed: 3: "2{s/.*=([0-9.]+) ms$/\1 ...": bad flag in substitute command: '}' · But never mind, the solution from terdon works fine for me and I’m glad that the problem is solved. Commented Aug 23, 2024 at 6:18
0

Assuming you're on Linux, and your ping output looks something like this:

$ cat > ping-output.txt
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=59 time=1.30 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=59 time=0.850 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=59 time=1.25 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2027ms
rtt min/avg/max/mdev = 0.850/1.134/1.304/0.202 ms

You could make both = signs and spaces split fields and then pick the right field. And check at the end if you got a value.

On the line of an individual ping, that would be field 10.

% cat ping-output.txt | 
  awk -F"[= ]" '/time=/ { rtt=$10 } 
                END { if (rtt) print rtt; else print "no connection" }'
1.25

Alternatively, you could read the summary numbers from the last line, to e.g. pick the minimum rtt.

With / or space as field separators, the minimum rtt is field 7 on the last line.

$ cat ping-output.txt |
  awk -F"[/ ]" '/^rtt/ { rtt=$7 }
                END { if (rtt) print rtt; else print "no connection" }'
0.850

(The ping on my mac has a slightly different output, the main difference here being round-trip instead of rtt on the last line. If yours is different, you'll have to tune the patterns and field numbers accordingly.)


But instead of using a special string in the output to mark a remote not answering, it might be better to the command's exit status. (Which is what ping likely also does itself.)

So, add an exit 1 to an appropriate place in the script, and then you can do something like this:

remote=1.1.1.1
if rtt=$(ping -c1 "$remote" | awk -F"[/ ]" '/^rtt/ { rtt=$7 }
                END { if (rtt) print rtt; else exit 1 }'; then
    echo "'$remote' is alive, rtt=$rtt"
else
    echo "'$remote' did not answer"
fi
3
  • Because I’m storing the ping output in a variable, all \n are removed and I need to use rtt=$16, and then it works. Commented Aug 22, 2024 at 11:23
  • 2
    @GaryU.U.Unixuser, if you do var=$(ping ...), the newlines will be there in the variable. It's just that if you then run echo $var without quotes, they'll disappear due to word splitting and echo printing its args joined with spaces. You'll see them if you run echo "$var" instead. (Or rather printf "%s" "$var", but anyway.)
    – ilkkachu
    Commented Aug 22, 2024 at 11:27
  • but you don't need to save the raw output from ping if you just want the rtt. Just do something like rtt=$(ping ... | awk ...). Even better, have the script return a different exit status if there's no answer, so you can put it in a conditional directly.
    – ilkkachu
    Commented Aug 22, 2024 at 11:36
0

A little variant on the (right) answer by @terdon: allow you to do more than 1 ping to average it and give maybe a more accurate result:

$ PS1="$ "; PS2=""
$ cat <<EOF | awk -F 'time=| ms' '($2){k++; sum+= $2}END{ if(!k){ 
print "no connection"} else { print ( sum * 1.0 / k ) }}'
...
... time=30.5 ms
...
... time=40 ms
...
... time=20 ms
...
EOF
30.1667
# and of course: replace the : 
#  cat <<EOF
# with:
#  ping -c 3 the_ip

or even simpler: using the last line with statistics (min/avg/max/mdev) :

ping -c 3 ip | awk -F'/| = ' '/^rtt/{avg=$3} END{print (!avg) ? "KO" : avg }'
-1

On my Arch system, ping results for a host that can and for a host that cannot be reached look like this:

$ ping -c1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=39.8 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 39.846/39.846/39.846/0.000 ms

and

$ ping -c 1 10.0.10.1
PING 10.0.10.1 (10.0.10.1) 56(84) bytes of data.

--- 10.0.10.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

Assuming your output is identical, and assuming that the failed connection's output is saved in $badPing an the successful one's in $goodPing, you can do:

$ awk -F= '/time=/{k=sub(/ms/,""); l=$NF}END{print k ? l : "no connection" }' <<< "$badPing"
no connection
$ awk -F= '/time=/{k=sub(/ms/,""); l=$NF}END{print k ? l : "no connection" }' <<< "$goodPing"
39.8 

The command above uses = as the field separator. Then, on every line that contains the string time=, it replaces the first occurrence of the string ms on that line (note that this assumes only one occurrence of ms on the relevant line; if that isn't the case for you, use sub(/ms/,"",$NF) instead) which leaves us with just the numerical value as the last field, and saves the number of replacements made in k and the last field as l.

Then, after we have processed the entire input, if k is set, so if a replacement occurred, we print out the value of l and if not, we print out no connection.

Alternatively, you can set the value of l at the beginning and then just print it:

$ awk -F= -v l="no connection" '/time=/ && sub(/ms/,""){l=$NF}END{print l}' <<< "$badPing"
no connection
$ awk -F= -v l="no connection" '/time=/ && sub(/ms/,""){l=$NF}END{print l}' <<< "$goodPing"
39.8 

To make your original approach work, just add having a second field as a conditional and set a variable based on it. Then you can print the second field when it exist, and print no connection at the end if the variable isn't set. Like this:

$ awk -F 'time=| ms' '($2){print $2;k=1}END{ if(!k) print "no connection"}' <<< "$goodPing"
39.8

$ awk -F 'time=| ms' '($2){print $2;k=1}END{ if(!k) print "no connection"}' <<< "$badPing"
no connection
1

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.