The Many Paths to a Solution

For this smarter version of grep, I simply could tell the user what flags to use or set specific flags with GREP_OPTIONS, an environment variable, but let's build out wegrep, as discussed.

For usage, it's going to be as simple as possible: command, pattern, source file. Like this:


wegrep '^Alice' wonderland.txt

This would search the file wonderland.txt for the regex "Alice", rooted to the beginning of a line.

Easily done:


grep=/usr/bin/grep
if [ $# -ne 2 ] ; then
  echo "Usage: wegrep [pattern] filename" ; exit 1
fi
$grep -C2 -n -E "$1" "$2"

I even added some error checking to ensure that the user specified the right number of parameters, with a simple error message to hide some of the complexity of the real grep command.

For a test file, I'm going to use the first four paragraphs of Lewis Carrol's immortal Alice in Wonderland, as downloaded from Project Gutenberg.

Here's the result of my first invocation:


$ sh wegrep '^Alice' wonderland.txt
11-Down the Rabbit-Hole
12-
13:Alice was beginning to get very tired of sitting by her
14-sister on the bank, and of having nothing to do: once
15-or twice she had peeped into the book her sister was
--
--
26-
27-There was nothing so very remarkable in that; nor did
28:Alice think it so very much out of the way to hear the
29-Rabbit say to itself, 'Oh dear! Oh dear! I shall be
30-late!' (when she thought it over afterwards, it

You can see that grep does a good job with this task, showing me two lines of context above and below each match, and denoting which line contains the match itself by having the : separate the line number from the content.

But what if your version of grep doesn't have support for the -C flag? What if you actually need to identify which lines match the pattern, then roll your own context display?

Building Your Own Context

Since grep is still available, and all but the most ancient of grep implementations support the -E flag to allow the user to specify a regular expression, the task can be broken into two parts: identify which lines match, then figure out a way to list lines (n-2)..n..(n+2), as shown in the above output.

The first task can be done surprisingly easily because grep has a handy -n flag that appends line numbers. With that, getting a list of which lines match the specified pattern is straightforward.

But, let's see what's output first:


$ grep -n -E '^Alice' wonderland.txt
13:Alice was beginning to get very tired of sitting by her
28:Alice think it so very much out of the way to hear the

Now it's a job for Superman! I mean, um, cut:


grep -n -E "$pattern" "$file | \
  cut -d: -f1
13
28

Let's switch to the other task of showing a range of lines centered on the specified line. You could do this with a tortured pairing of head and tail, but sed is a much better tool for the job this time.

In fact, sed makes it easy. Want to grab lines 12, 13 and 14? This'll do the trick:


sed '12,14p' wonderland.txt

Well, not quite. The problem is that the default behavior of sed is to echo every line it sees in addition to whatever the user specifies, so you'll end up with every line from wonderland.txt and additionally have lines 12–14 appear a second time as the statement is matched and executed (the p suffix means "print").

______________________

Dave Taylor has been hacking shell scripts for over thirty years. Really. He's the author of the popular "Wicked Cool Shell Scripts" and can be found on Twitter as @DaveTaylor and more generally at www.DaveTaylorOnline.com.