How to get the percent of packets received from Ping in bash? - linux

When pinging a host I want my output just to show the percentage of packets (5 sent) received. I assume I need to use grep somehow but I can't figure out how (I'm new to bash programming). Here is where I am: ping -c 5 -q $host | grep ?. What should go in grep? I think I will have to do some arithmetic to get the percent received but I can deal with that. How can I pull out the info I need from the summary that ping will output?

So far we've got an answer using grep, sed, perl, bc, and bash. Here is one in the flavor of AWK, "an interpreted programming language designed for text processing". This approach is designed for watching/capturing real-time packet loss information using ping.
To see only packet loss information:
Command
$ ping google.com | awk '{ sent=NR-1; received+=/^.*(time=.+ ms).*$/; loss=0; } { if (sent>0) loss=100-((received/sent)*100) } { printf "sent:%d received:%d loss:%d%%\n", sent, received, loss }'
Output
sent:0 received:0 loss:0%
sent:1 received:1 loss:0%
sent:2 received:2 loss:0%
sent:3 received:2 loss:33%
sent:4 received:2 loss:50%
sent:5 received:3 loss:40%
^C
However, I find it useful to see the original input as well. For this you just add print $0; to the last block in the script:
Command
$ ping google.com | awk '{ sent=NR-1; received+=/^.*(time=.+ ms).*$/; loss=0; } { if (sent>0) loss=100-((received/sent)*100) } { print $0; printf "sent:%d received:%d loss:%d%%\n", sent, received, loss; }'
Output
PING google.com (173.194.33.104): 56 data bytes
sent:0 received:0 loss:0%
64 bytes from 173.194.33.46: icmp_seq=0 ttl=55 time=18.314 ms
sent:1 received:1 loss:0%
64 bytes from 173.194.33.46: icmp_seq=1 ttl=55 time=31.477 ms
sent:2 received:2 loss:0%
Request timeout for icmp_seq 2
sent:3 received:2 loss:33%
Request timeout for icmp_seq 3
sent:4 received:2 loss:50%
64 bytes from 173.194.33.46: icmp_seq=4 ttl=55 time=20.397 ms
sent:5 received:3 loss:40%
^C
How does this all work?
You read the command, tried it, and it works! So what exactly is happening?
$ ping google.com | awk '...'
We start by pinging google.com and piping the output into awk, the interpreter. Everything in single quotes defines the logic of our script.
Here it is in a whitespace friendly format:
# Gather Data
{
sent=NR-1;
received+=/^.*(time=.+ ms).*$/;
loss=0;
}
# Calculate Loss
{
if (sent>0) loss=100-((received/sent)*100)
}
# Output
{
print $0; # remove this line if you don't want the original input displayed
printf "sent:%d received:%d loss:%d%%\n", sent, received, loss;
}
We can break it down into three components:
{ gather data } { calculate loss } { output }
Each time ping outputs information, the AWK script will consume it and run this logic against it.
Gather Data
{ sent=NR-1; received+=/^.*(time=.+ ms).*$/; loss=0; }
This one has three actions; defining the sent, received, and loss variables.
sent=NR-1;
NR is an AWK variable for the current number of records. In AWK, a record corresponds to a line. In our case, a single line of output from ping. The first line of output from ping is a header and doesn't represent an actual ICMP request. So we create a variable, sent, and assign it the current line number minus one.
received+=/^.*(time=.+ ms).*$/;
Here we use a Regular Expresssion, ^.*(time=.+ ms).*$, to determine if the ICMP request was successful or not. Since every successful ping returns the length of time it took, we use that as our key.
For those that aren't great with regex patterns, this is what ours means:
^ starting at the beginning of the line
.* match anything until the next rule
(time=.+ ms) match "time=N ms", where N can be one or more of any character
.* match anything until the next rule
$ stop at the end of the line
When the pattern is matched, we increment the received variable.
Calculate Loss
{ if (sent>0) loss=100-((received/sent)*100) }
Now that we know how many ICMP requests were sent and received we can start doing the math to determine packet loss. To avoid a divide by zero error, we make sure a request has been sent before doing any calculations. The calculation itself is pretty simple:
received/sent = percentage of success in decimal format
*100 = convert from decimal to integer format
100- = invert the percentage from success to failure
Output
{ print $0; printf "sent:%d received:%d loss:%d%%\n", sent, received, loss; }
Finally we just need to print the relevant info.
I don't want to remember all of this
Instead of typing that out every time, or hunting down this answer, you can save the script to a file (e.g. packet_loss.awk). Then all you need to type is:
$ ping google.com | awk -f packet_loss.awk

As always, there are many different ways to do this., but here's one option:
This expression will capture the percent digits from "X% packet loss"
ping -c 5 -q $host | grep -oP '\d+(?=% packet loss)'
You can then subtract the "loss" percentage from 100 to get the "success" percentage:
packet_loss=$(ping -c 5 -q $host | grep -oP '\d+(?=% packet loss)')
echo $[100 - $packet_loss]

Assuming your ping results look like:
PING host.example (192.168.0.10) 56(84) bytes of data.
--- host.example ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4000ms
rtt min/avg/max/mdev = 0.209/0.217/0.231/0.018 ms
Piping your ping -c 5 -q through:
grep -E -o '[0-9]+ received' | cut -f1 -d' '
Yields:
5
And then you can perform your arithmetic.

echo $((100-$(ping -c 5 -q www.google.hu | sed -rn "/packet loss/ s#.*([0-9]+)%.*#\1#p")))

Try a script:
/bin/bash
rec=ping -c $1 -q $2 | grep -c "$2" | sed -r 's_$_ / \$1_' | xargs expr
Save it, and run it with two command line args. The first is number of packets, the second is the host.

Does this work for you?
bc -l <<<100-$(ping -c 5 -q $host |
grep -o '[0-9]% packet loss' |
cut -f1 -d% )
It takes the percentage reported by ping and subtracts it from 100 to get the percentage of received packets.

Related

tshark and awk outputs in 32 line quantums

I'm running tshark to capture stuff, then piping the output into awk to process it a bit. When I run the tshark command without the awk, I see the output in the terminal as it happens. When I pipe it into awk (I'm using -l on tshark), actually here it is:
tshark -i wlan1 -l subtype probereq | awk -f ./blah.awk
I see the counter go up as packages come in, but nothing happens. When it reaches 32, suddenly the first 32 lines appear, then nothing happens again. Can we get rid of this strange behaviour?
Ok, so tshark sample output after a few sec:
Running as user "root" and group "root". This could be dangerous.
Capturing on 'wlan1'
1 0.000000000 0c:08:b4:07:1c:41 → ff:ff:ff:ff:ff:ff 802.11 288 Probe Request, SN=100, FN=0, Flags=........, SSID=gigacube-E7CA
2 9.709432337 0c:08:b4:07:1c:41 → ff:ff:ff:ff:ff:ff 802.11 288 Probe Request, SN=117, FN=0, Flags=........, SSID=gigacube-E7CA
3 9.969377335 0c:08:b4:07:1c:41 → ff:ff:ff:ff:ff:ff 802.11 288 Probe Request, SN=119, FN=0, Flags=........, SSID=gigacube-E7CA
awk file:
BEGIN {
OFS = ",";
}
{
print $3, substr($13, 6);
}
The output of the awk script on the output of the tshak command IF i first send the tshark output into a file:
0c:08:b4:07:1c:41,gigacube-E7CA
0c:08:b4:07:1c:41,gigacube-E7CA
0c:08:b4:07:1c:41,gigacube-E7CA
66:3c:76:d8:29:b2,Wildcard
66:3c:76:d8:29:b2,Wildcard
66:3c:76:d8:29:b2,Wildcard
66:3c:76:d8:29:b2,Wildcard
... as expected.
But tshark -i wlan1 -l -n subtype probereq | awk -f ./blah.awk only outputs the counter for like a minute - until it reaches about 32, then outputs about 30 lines together. I'd like 2 things:
no counter
continuous output, as it comes out of tshark

Split the content of a string using as delimiter the "="

How can I split the following output, in order to store the ttl value (64 and 128) into a loop variable?
64 bytes from client2 (192.168.42.5): icmp_seq=1 ttl=64 time=0.324 ms
64 bytes from server (192.168.42.6): icmp_seq=1 ttl=128 time=0.663 ms
Thanks in advance
Regards
The following pipeline is one way to get the specific items you want. The grep extracts only the ttl=something bit and the cut removes the ttl=:
grep -o 'ttl=[^ ]*' | cut -c5-
You can see this in the following transcript:
pax> printf 'AA ttl=64 BB\nCC ttl=128 DD\n'
AA ttl=64 BB
CC ttl=128 DD
pax> printf 'AA ttl=64 BB\nCC ttl=128 DD\n' | grep -o 'ttl=[^ ]*' | cut -c5-
64
128
Or from a real ping command:
:: ping -c 5 127.0.0.1 | grep -o 'ttl=[^ ]*' | cut -c5-
64
64
64
64
64
There are no doubt other pipelines that can do the same thing (perhaps even simpler) but that's the first one that popped into my head. The grep -o flag is quite handy for displaying only the matched text rather than the entire line.
For example, a more complete solution for handling that output may be as follows:
pax> (
...> echo '64 bytes from client2 (192.168.42.5): icmp_seq=1 ttl=64 time=0.324 ms'
...> echo '64 bytes from server (192.168.42.6): icmp_seq=1 ttl=128 time=0.663 ms'
...> ) | awk '
...> / bytes from / {
...> gsub(/ttl=/, "", $7)
...> gsub(/\(/, "", $5)
...> gsub(/\):/, "", $5)
...> print $4" "$5" "$7
...> }' | while read NAME IP TTL ; do
...> echo "Machine ${NAME} with IP ${IP} has TTL ${TTL}"
...> done
Machine client2 with IP 192.168.42.5 has TTL 64
Machine server with IP 192.168.42.6 has TTL 128
The awk first selects the correct records, then modifies the fields so that you don't get the extra stuff (like the ttl= or the parentheses around the IP address). It then prints out three of the fields and sends that through a while loop to process them as single units (one ping response line per unit).
The body of the loop simply outputs the details but you can adjust the behaviour to do something else if desired.
You can use bash regular expressions:
while IFS= read -r line; do
if [[ $line =~ "ttl="([[:digit:]]+) ]]; then
ttl=${BASH_REMATCH[1]}
echo "do something with ttl value $ttl"
fi
done <<END
64 bytes from client2 (192.168.42.5): icmp_seq=1 ttl=64 time=0.324 ms
64 bytes from server (192.168.42.6): icmp_seq=1 ttl=128 time=0.663 ms
END
outputs
do something with ttl value 64
do something with ttl value 128
The BASH_REMATCH array variable contains the text that matched capturing parentheses. Also the 0th index of that array contains the part of the string that matched the whole regex, for example "tty=64" for the first line.
Something like:
stuff | sed -E 's/.*[[:space:]]ttl=([^[:space:]]+).*/\1/'
The regular expression is:
.* match anything
[[:space:]] a single space
ttl= ttl=
([^[:space:]]+) captures a group of one or more non-space characters
.* the rest of the string
Then the end
\1 replaces the whole line with the captured value
Thank you very much for all your answers!!!
I think that is enough to have a fair idea to start.

Expect script output

I am trying to output the size of an ARP table from a FW using an Expect script so it can be graphed. After the below code the output displayed to screen is shown:
/usr/bin/expect -f -<< EOD
spawn ssh test#1.2.3.4
sleep 1
expect {
"*word:" {send "password\r"}
}
sleep 1
expect {
"*>" {send "show arp all | match \"total ARP entries in table\"\r"}
}
sleep 1
expect {
"*>" {send "exit\r"}
}
expect eof
EOD
spawn ssh test#1.2.3.4
FW-Active
Password:
Number of failed attempts since last successful login: 0
test#FW-Active(active)> show arp all | match "total ARP entries in table"
total ARP entries in table : 2861
What I am trying to do is be able to output only the numeric value indicated from the total ARP entries in table. I am assuming I need to some how do a "cut" or "awk" or something to extract only the numbers but I am not having any luck. Any help is greatly appreciated.
You store the output of that whole command in a variable, let's say a.
Something like this will probably work. Since you're using expect, you might want to figure out how to store that output as a variable that way you can manipulate it. I stored the output as $a in my example.
$ echo $a
total ARP entries in table : 2861
$ echo ${a% *}
total ARP entries in table :
$ echo ${a% *}-
total ARP entries in table : -
$ echo ${a##* }
2861
Logic explanation (Parameter/Variable Substituion in BASH):
1) To removing/stripping the left hand side part, use # for reaching the first matching character value (reading / parsing from left side), ## for reaching the last matching character/value. It works by giving *<value> within the { } braces.
2) To removing/stripping the right hand side part, use % for reaching the first matching character value (reading / parsing from right side), %% for reaching the last matching character/value. It works by giving <value>* within the { } braces.
Or if you don't want to store the output or anything, then simply do this:
show arp all | match "total ARP entries in table" | grep -o "[0-9][0-9]*"
Or (the following assumes that you don't change
show arp all | match "total ARP entries in table" | sed "s/ *//g"|cut -d':' -f2

Tshark custom grep

So my command is:
tshark -Y 'wlan.fc.type_subtype==0x04'
So my output is:
21401 205.735966 Apple_90:ea:8e -> Broadcast 802.11 155 Probe Request, SN=3667, FN=0, Flags=........C, SSID=Broadcast
How can I get Apple_90:ea:8e + SSID=Broadcast and whats the logic behind the grep? Is it possible with grep?
Considering that: Apple_90:ea:8e and Broadcast will always change!
$ var='21401 205.735966 Apple_90:ea:8e -> Broadcast 802.11 155 Probe Request, SN=3667, FN=0, Flags=........C, SSID=Broadcast'
$ grep -oP '\S+(?= ->)|SSID=\S+' <<< "$var"
Apple_90:ea:8e
SSID=Broadcast
The grep option -o says "only return what was matched, not the whole line" and -P is to use the Perl regex engine (because we use look-arounds). The regex is
\S+ # One or more non-spaces
(?= ->) # followed by " ->"
| # or...
SSID=\S+ # "SSID=" and one or more non-spaces

ping script and log output and cut with grep

I want to ping a bunch of locations but not at the same time, in order so they don't timeout.
The input is for example: ping google.com -n 10 | grep Minimum >> output.txt
This will make the output of: Minimum = 29ms, Maximum = 46ms, Average = 33ms
But there are extra spaces in front of it which I don't know how to cut off, and when it outputs to the txt file it doesn't go to a new line. What I am trying to do is make it so I can copy and paste the input and ping a bunch of places once the previous finishes and log it in a .txt file and number them so it would look like:
Server 1: Minimum = 29ms, Maximum = 46ms, Average = 33ms
Server 2: Minimum = 29ms, Maximum = 46ms, Average = 33ms
Server 3: Minimum = 29ms, Maximum = 46ms, Average = 33ms
Server 4: Minimum = 29ms, Maximum = 46ms, Average = 33ms
Well, first of all, ping on linux limits packet number to send with -c, not -n.
Secondly, output of ping is not Minimum = xx ms, Maximum = yy ms, Avrage = zz ms, but rtt min/avg/max/mdev = 5.953/5.970/5.987/0.017 ms
So basically if you do something in lines of:
for server in google.com yahoo.com
do
rtt=`ping $server -c 2 | grep rtt`
echo "$server: $rtt" >> output.txt
done
You should achieve what you want.
[edit]
If cygwin is your platform, the easiest way to strip the spaces would be either what people are suggesting, sed, or then just | awk '{print $1}', will trim your line as well.
I think you might be able to solve this using sed two times and a while loop at the end:
N=1; ping google.com -n 10 | grep Minimum | sed -r 's/(Average = [[:digit:]]+ms)/\1\n/g' | sed -r s'/[[:space:]]+(Minimum)/\1/g' | while read file; do echo Server "$N": "$file"; N=$((N+1)); done >> output.txt
The steps:
The first sed fixes the newline issue:
Match the final part of the string after which you want a new line, in this case Average = [[:digit:]]+ms and put it into a group using the parenthesis
Then replace it with the same group (\1) and insert a newline character (\n) after it
The second sed removes the whitespaces, by matching the word Minimum and all whitespaces in front of it after which it only returns the word Minimum
The final while statement loops over each line and adds Server "$N": in front of the ping results. The $N was initialized to 1 at the start, and is increased with 1 after each read line
You can use sed to remove first 4 spaces :
ping google.com -n 10 | grep Minimum | sed s/^\ \ \ \ //

Resources