1

I'm working on a shell script (using Bash) for Linux systems.

As part of this script I'm using awk to get the percentage of packets dropped from a ping request. This works fine when executed from the terminal shown below:

test=`ping gnu.org -c10 | tail -2`; echo $test | awk '{printf $6 "\b"}' 

This gets the percentage of packets dropped and echos it using the backspace escape character to show just "0" rather tan "0%" - this is as the value is used later on as an integer value.

Inside the script, the code looks like this:

#Pings the gateway ten times and stores the result local pingResults=`ping -c10 $gateway | tail -2` #Uses awk to get the percentage of packets lost using \b to remove the % local packetsLost=`echo $pingResults | awk '{printf $6 "\b"}'` 

I return the value from packetsLost and echo it and it gives "0%", totally ignoring the \b

Could somebody please explain why this is happening inside the shell script and how I can remedy this?

I understand there are alternatives such as using cut but I'd like to keep the line as simple and efficient as possible.

6
  • Is the terminal's shell the same as the script's? Commented Aug 1, 2014 at 22:27
  • Yes, bash is used for both Commented Aug 1, 2014 at 22:28
  • 1
    Why not just gsub the % character off in awk instead of playing printing escape tricks? Commented Aug 1, 2014 at 22:32
  • 1
    Try printf("%s", substr($6, 1, length($6)-1)). Commented Aug 1, 2014 at 22:33
  • I felt like Awk was the cleanest and simplest way to achieve what I needed. Commented Aug 1, 2014 at 22:36

3 Answers 3

1

OMG! Parsing human-readable string basing on word counting is completely unmaintainable (‘write-only’) and unpredictable. Try to avoid it. It’s quite simple actually:

$ ping -q gnu.org -c10 |\ gawk '{ if (match ($0, /([[:digit:]]+(\.[[:digit:]]+)?)% packet loss/, a)) print a[1]; }' 0 

a[1] here corresponds to the first () group in the regexp.

P. S. When you parsing human-readable output it is necessary to ensure that locale (i. e. language) is the same that you expected while scripting, so in actual code ping call should look like:

local pingResults=$(LANG=C ping -q -c10 "$gateway") 

(Even if it’s seems that iputils-ping in particular does not have any translations.)

Sign up to request clarification or add additional context in comments.

9 Comments

Thank you! This is an excellent answer. I haven't worked very much with using ping programatically so I overlooked the quiet option.
This only matches numbers after the decimal. e.g. 99.9% packet loss would match 9.
If you're concerned about locale you should use [[:digit:]] instead of [0-9]. Also, instead of putting the condition inside the action part, just put it in the condition part: gawk 'match ($0, /([0-9]+)% packet loss/, a) {print a[1]}'.
@JohnB Where did you read that ping rounds percentages up to tenths and not to integers. I’ve done some test and got this: 145 packets transmitted, 127 received, 12% packet loss, time 144676ms while (145 − 127) / 145 ≈ 12.4 %.
@DimitryAlexandrov Try this on OS X and you'll see.
|
0

Check the next:

echo 1x 2x 3x 4x 5x 6x 7x | awk '{printf $6 "\b"}' | od -cb 

you will get the next output:

0000000 6 x \b 066 170 010 

So, when you run the

echo 1x 2x 3x 4x 5x 6x 7x | awk '{printf $6 "\b"}' 

you got printed:

  • character 6
  • character x
  • character backspace (what causes the character remove in the TERMINAL)

Now run:

aaa=$(echo 1x 2x 3x 4x 5x 6x 7x | awk '{printf $6 "\b"}') 

store the result to aaa, and show its content:

echo $aaa | od -bc 0000000 066 170 010 012 6 x \b \n 

When want identical result, use:

echo -n $aaa 

so in summary, use:

local packetsLost=`echo $pingResults | awk '{printf $6 "\b"}'` echo -n $packetLost 

2 Comments

As I'm using functions, echo works as return. The idea is to gather various data and at the end it will be placed into a large formatted string for output. I have attempted what you suggest but it still gives 0%, 100%, etc.
@Kervate in this case, you need remove the % from the output. (the \b doesnt remove it - as you can see it in the dump.)
0

awk '{sub(/%/, "", $6); printf $6} or awk '{print substr($6, 1, length($6) - 1)}'

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.