A Unix programmer heads over to the local diner to get something to eat for lunch. He, or Bob as he prefers, knows better than to manually scan the entire menu with his eyeballs because it's inefficient. Bob knows that there's a better way to automate the process in searching for what he wants to eat.
Last time he was here he had a pretty good pasta-and-shrimp plate for under 10 bucks.
Bob wonder's if this same dish is still available. He pops open his laptop running Linux and scrapes the restaurant website menu table into a plain text file.
menu.txt
the menu
steak burrito $11.95
pasta and shrimp $9.75
caesar salad $6.50
Now that Bob has the menu, he does a grep
search for the pasta-and-shrimp plate:
$ cat menu.txt | grep shrimp
pasta and shrimp $9.75
So far so good. He filters for the price column by using awk
and the NF
variable (which is the number of columns fields available) in order to get the last column containing the prices:
$ cat menu.txt | grep shrimp | awk '{print $NF}'
$9.75
The starvation is kicking in and he's typing furiously by now. Bob proceeds with sed
to turn the dollar amount into an integer by replacing everything except for what's inbetween the dollar symbol and decimal period. He'll use a capturing group and replace the contents with the first capture group \1
:
$ cat menu.txt | grep shrimp | awk '{print $NF}' | sed -E 's/\$([0-9]+)\..*/\1/g'
9
Finally, using the handy test
command and the less-than flag -lt
, Bob can check whether the price is less than 10
with the help of xargs
to pipe the value as the first argument to test
:
$ cat menu.txt | grep shrimp | awk '{print $NF}' | sed -E 's/\$([0-9]+)\..*/\1/g' | xargs -I {} test {} -lt 10
But wait there's no output! Actually, test
returns an exit status code of 0
if the condition passes or 1
if no match.
Bob simply echo's Available if the previous command was successful by using &&
, otherwise he'll echo :( by using the double pipe ||
if it was not successful:
$ cat menu.txt | grep shrimp | awk '{print $NF}' | sed -E 's/\$([0-9]+)\..*/\1/g' | xargs -I {} test {} -lt 10 && echo 'Available!' || echo ':('
Available!
Voila! there it is, the desired pasta-and-shrimp plate is still the under ten bucks. Bob is happy and proceeds to order his favorite dish.
(hope you liked this intro post to unix pipes and bash commands!)
Top comments (33)
What sort of monster does
cat | grep
!?With real life menus with millions of lines, you usually start with
head | grep
, and then, when you one-liner of 15 lines is complete, you replace head with cat and cross your fingersreal life menus with millions each? ;)
That was my attempt to make a joke
You can almost accomplish this entire task using awk. For example:
awk -F '$' '/shrimp/ {printf "%s- %.2f\n", $1, $NF}' < menu.txt
awk is Turing Complete.
s/almost//
Some people, including me:
reddit.com/r/programming/comments/...
Grep won't be able to find anything because you are searching for ! Which is not in the menu
I've been reading the thread about this on /r/programming (how circular!) and they're mostly (deliberately?) missing the point and trying to optimise it rather than to see it as a demonstration of pipes.
cough
EDIT: changed
\d
shortcode for[0-9]
so it'll work with BSD and GNU grep.That's what this bit at the end is for:
That bit of the regex will only match on a line that contains a dollar sign (
$
), a single digit ([0-9]
), and a literal dot (\.
). So "$2.99" will match, but "$12.99" won't, because there are two digits between the dollar sign and the dot.Try it.
| rev | cut -d\ -f1 | rev
The equivalent is much more readable with PowerShell:
$shrimp = Get-Content menu.txt | Select-String 'shrimp'
$price = $shrimp | ForEach-Object { $cols = $_ -split ' '; $cols[-1] }
$under10 = [decimal]$price.Substring(1) -lt 10
if ( $under10 ) { 'Available!' } else { ':(' }
Of course, you can use aliases to make this a bit "shorter", but then we're back in the world of Unix, where precious bytes have to be saved to achieve acceptable performance on 300 baud Teletype terminals:
$p = gc menu.txt | sls 'shrimp' | % { $c = $_ -split ' '; $c[-1] }
if ([decimal]$p.Substring(1) -lt 10) {'Available!'} else {':('}
Given this menu:
My Nix programmer does this:
awk rules!
if you are looking for meals under 10 bucks, as shown in
($NF < 10)
, why isn't caesar salad($6.50) in the output ?"test returns an exit status code of 1 if the condition passes or 0 if no match." It is actually backwards: reddit.com/r/programming/comments/...
Why are you guys alwas using awk for field separation? There ist
cut -d" " -f3
that is nice as wellThis is why two unix programmers will never eat together, because they will start to discuss which is the best way to order the shrimps
Bob should've rounded up. He probably ran off when he remembered he didn't factor taxes in!
Oh & btw, he should've done a case insensitive grep
grep -i shrimp menu.txt
. Although if he's using awk anyway, then he might have included that there too and avoid all those unnecessary pipes!awk '$0~/shrimp/{print $NF}
In a lot of countries the tax is part of the price
You're blowing up on Reddit
Some comments may only be visible to logged-in visitors. Sign in to view all comments.