|

[Bash Challenge 9] Can You Solve This Bash Script Puzzle?

Welcome to the Bash Challenge #9 by Yes I Know IT & It’s FOSS. In this weekly challenge, we will show you a terminal screen, and we will count on you to help us obtaining the result we wanted. There can be many solutions, and being creative is the most amusing part of the challenge.

If you haven’t done it already, do take a look at previous challenges:

You can also buy these challenges (with unpublished challenges) in book form and support us:

[irp posts=”17248″ name=”Bash It Out! Bash Script Puzzle Book by It’s FOSS is Available Now!”]

Ready to play? So here is this week’s challenge:

My command outputs are in the wrong order!

This week, I want a shell function to log the round trip time (rtt) to a server. Only if the ping command has succeeded, I want to record the date of the measure on the line below the rtt. Given those requirements, I end up with that solution:

probe() (
  ping -qnc2 www.google.com | \
    grep rtt & \
    date +"OK %D %T"
)
rm -f log
probe >> log
probe >> log
cat log

But, for some reason, the date and rtt lines are swapped in the log file?!? Why? Could you fix that? Is there a cleaner way to achieve my goal?

Answering those questions is your challenge.

Few details

To create this challenge, I used:

  • GNU Bash, version 4.4.5 (x86_64-pc-linux-gnu)
  • Debian 4.8.7-1 (amd64)
  • All commands are those shipped with a standard Debian distribution
  • No commands were aliased

The solution

How to reproduce

Here is the raw code we used to produce this challenge. If you run that in a terminal, you will be able to reproduce exactly the same result as displayed in the challenge illustration (assuming you are using the same software version as me):

rm -rf ItsFOSS
mkdir -p ItsFOSS
cd ItsFOSS
clear
probe() (
  ping -qnc2 www.google.com | \
    grep rtt & \
    date +"OK %D %T"
)
rm -f log
probe >> log
probe >> log
cat log

What was the problem?

I’ve simply made a typo: I mistaken & for && — maybe was I confused by the pipe (|) symbol above? Indeed, all the |, & and && operators can be used to join two shell commands. But they have completely different meanings:

cmd1 | cmd2
The pipe

Run both commands in parallel in a sub-shell, using the output of cmd1 as input to cmd2. The pipe is a very common way to combine several basic commands in order to accomplish complex tasks.

cmd1 & cmd2
The ampersand

Run cmd1 as a background process, and in parallel, to run cmd2 in the foreground. The two commands are not connected in any way using that operator.

cmd1 || cmd2
The short-circuit logical OR

Run cmd2 only if cmd1 has failed. As a consequence cmd1 must complete before cmd2 is eventually run. In other words, commands run sequentially.

cmd1 && cmd2
The short-circuit logical AND

Run cmd2 only if cmd1 was successful. As a consequence cmd1 must complete before cmd2 is eventually run. In other words, commands run sequentially.

Armed with that knowledge, let’s now review my original code:

probe() (
  ping -qnc2 www.google.com | \
    grep rtt & \
    date +"OK %D %T"
)

1. I want to run the ping command and send its output to the grep command. The pipe is the right operator.
2. But after that, I wanted to write the date only if the pipe was successful. Here, I needed the logical AND operator (&&). But instead of that, I used the & operator, that basically run ping | grep into the background — always. And date in the foreground — always.
There is a race condition as both processes are now running in parallel and compete to write on stdout. Unsurprisingly, in that particular example, the date command won every time over the ping command.

Therefore the correct syntax would have been:

probe() (
  ping -qnc2 www.google.com | \
    grep rtt && \
    date +"OK %D %T"
)

In my case, the issue was immediately visible because, obviously, the ping command takes more time to complete than the date command. But as this is often the case with race conditions, such mistakes could easily remain hidden for a very long time. For example, the following example is a lot less deterministic:

probe() (
   ping -qnc2 itsfoss.com | sed 1q & \
   ping -qnc2 kernel.org | sed 1q
)

From my location in France, on 2000 runs, the first ping lost only 3 times. That means the “bug” was visible only in 0.15% of the cases. Next time you report some occasional software crash — be kind with your favorite FOSS developers and remember that even caused by apparently minor errors, race conditions are hard to reproduce and even harder to trace!

But maybe you know some testing tools that could help pinpoint such typos in Bash scripts? If this is the case, don’t hesitate to share that with us using the comment section below!

And don’t forget to stay tuned for more fun.

Similar Posts

  • You wanted && instead of &

    [email protected]:~$ ping -qnc2 http://www.google.com | grep rtt & date +”OK %D %T”
    [1] 31929
    OK 02/05/17 14:35:28
    [email protected]:~$ rtt min/avg/max/mdev = 7.100/7.827/8.554/0.727 ms

    [1]+ Done ping -qnc2 http://www.google.com | grep rtt
    [email protected]:~$
    [email protected]:~$ ping -qnc2 http://www.google.com | grep rtt && date +”OK %D %T”
    rtt min/avg/max/mdev = 7.702/11.188/14.675/3.488 ms
    OK 02/05/17 14:35:44