11
grep -A 2 -B 3 

prints 2 lines after the grep string and prints 3 lines before.

grep -C 3 

prints 3 Lines before and 3 lines after

Unfortunately, the grep I'm using does not support these options. Are there any alternative commands or script available to simulate this? Using sed/awk/perl/shell scripts?

1
  • 2
    Install GNU's grep. More generally, when a new Sun machine arrived here, the very first step in the setup was what somebody called GNU > /usr/local. The GNU programs have lots of very useful extensions, and are designed to avoid arbitrary restrictions (but you do pay dearly in size and sometimes performance). Many propietary systems have "unofficial" package repositories with GNU and other tools. The "partner" won't tell you about them, even when they are managed by the vendor... Commented Jan 23, 2013 at 14:36

6 Answers 6

6

One moderately ugly way to do it is

grep -v pattern file >file.tmp; diff -c file.tmp file 

or replace -c with -C NUM for NUM lines of context. It'll produce extra output, though. (If your diff supports -u/-U NUM, it'll be cleaner.)

If your diff doesn't have -c/-C/-u, there are still ways to do it, but they're pretty ugly. On the other hand, a system whose diff doesn't even support -c probably doesn't have Perl either.

2
  • This is cool, Works like charm, though I had to use -bitw option with this to make it work for windows generated files. Commented Apr 12, 2011 at 20:40
  • You can send stdin to diff, and skip the temporary: grep -v pattern file | diff -c - file Commented Apr 13, 2011 at 4:18
5

ack requires only Perl, and includes -A, -B, and -C options that work like grep's. It uses Perl's regex syntax instead of grep's, and the way it selects files to search is quite different. You might want to try the -f option when using it (which prints out the files it will search without actually searching anything).

It can be installed as a single script that requires no non-core modules. Just drop it into your ~/bin directory (or anywhere else on your PATH that you have write access to) and make sure it's chmod'd executable.

5
  • 1
    Its production box and Unfortunately I dont have enough privilege to install anything, and I cant risk it, though, thanks for this tip I will install it and try on my home laptop Commented Apr 12, 2011 at 20:49
  • @Prashant, you don't need root to install ack for your own use. Commented Apr 12, 2011 at 21:10
  • Yes but still I cant use it there, though its sure that this script will stay forever in my ~/bin :) Commented Apr 12, 2011 at 22:11
  • @Prashant: Why can't you use it? It's just a perl script. Commented Apr 13, 2011 at 9:21
  • 2
    Its PRODUCTION box, need to take special permissions approvals bla bla bla... todo any thing on it. and anything goes wrong there on comes on my head ;) and Its not worth it :) Commented Apr 13, 2011 at 10:24
5

This simple perl script emulates grep -A to some extent

#!/usr/bin/perl $pattern=shift; #patthern to search $lines=shift; # number of lines to print $n = 0; while (<>) { $n = $lines if /$pattern/; # reset counting if ($n) { print; $n-- } # print if within $n = 0 if eof; # don't leak across file boundaries } 

Note that you may add a usage statement, to make script readable and usable ;)

USAGE: $./grep-A.pl <pattern> <numLines> <filename> 
4
  • Nice, which version of perl do I need to run this ? Commented Apr 12, 2011 at 20:08
  • I use v5.10.1, I guess perl 5 is fairly common these days. Commented Apr 12, 2011 at 20:19
  • ya its 5.8.8 and it works, great, but I need a script that does what -B do Commented Apr 12, 2011 at 20:24
  • Good. I would switch the order of arguments, though; grep-A 3 foo looks much more natural than grep-A foo 3. :-) Commented Feb 8, 2013 at 21:28
3

You can just install GNU grep or Ack (written in Perl, understands many of GNU grep's options and more).

If you prefer to stick to standard tools plus a bit of scripting, here's an awk script that emulates the behavior of GNU grep's -A and -B options. Minimally tested.

#!/bin/sh # grep-ac: a grep-like awk script # Arguments: pattern = awk regexp to search for # before = number of lines to print before a match # after = number of lines to print after a match { "exec" "awk" "-f" "$0" "$@"; } # The array h contains the history of lines that haven't been printed # but are eligible for being "before" lines. # The variable until contains the number of the last "after" line to print. start <= until { start = until + 1; } match($0, pattern) { # the current line matches for (i = start; i < NR; i++) { print h[i]; delete h[i]; } until = NR + after; start = until; } { if (NR <= until) print $0; else h[NR] = $0; start = NR - before + 1; delete h[start-1]; } END {exit !until} 

Run it as grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTER where PATTERN is the pattern to search for (an extended regular expression with a few awk additions), and NBEFORE and NAFTER are the numbers of lines to print before and after a match respectively (defaulting to 0). Example:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar' 
4
  • Any solution that stores data in array is out of question ... as I mentioned earlier , filesize is quite huge and it might over flow. Also awk on this system does not allow file size more than 3000 bytes. Commented Apr 12, 2011 at 22:19
  • 2
    @Prashant: I don't understand your objections. This script deletes lines once they're ineligible to be before-lines. It doesn't use more memory than is inherently necessary given the requirements, except that awk may have a higher overhead than a special-purpose program (but less than Perl, which you're also considering). The total size of the file is completely irrelevant. Commented Apr 12, 2011 at 22:28
  • 2
    { "exec" "awk" "-f" "$0" "$@"; }: very nifty way to get around the limitations in shebang-line parsing. Commented Oct 18, 2012 at 21:50
  • for (i in h) will loop over the array elements in an undefined order. You'd need GNU awk's PROCINFO["sorted_in"] = "@ind_num_asc" or use a for (i = first; i<= last; i++) loop instead. Commented Sep 12 at 11:39
2

It turns out that it's quite tricky to emulate -B, because of the issues that crop up when you have matching lines following each other directly. This pretty much disallows using any sort of single-pass-through file scanning.

I realized this while playing around with the following approximation:

perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file 

This will work roughly correctly as grep -A7 -B3 would, with the caveat described in the first paragraph.

An alternative (also single-file) solution to this issue is to use perl to feed sed a command string:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file 
2
  • pretty lenghty oneliner, but, this file is very huge, so pushing lines into array in this case is a bad Idea, isnt it ? Commented Apr 12, 2011 at 20:51
  • The shift @A if push(@A,$_)>7; bit only keeps an array of maximum size 7 around. (that's your -A parameter). The second option keeps an incredibly small file around (just run the perl without the sed outer layer to see what is generated there), but it does read the file twice. Commented Apr 12, 2011 at 20:54
0

Using sed you can first get the line numbers of matching lines, decrement & increment a given line number in a while loop and then use sed -n "n1,n2p" to print lines of leading (n1) and trailing (n2) context (similar to the sed alternative suggested by user455). Many read processes may lead to a performance hit though.

ed can directly reference the previous and following lines of a matched line, but fails if the specified line range does not exist; for example, matching line is line number 2, but 5 pre-match lines should be printed. Using ed it is therefore necessary to add an appropriate number of (empty) lines at the beginning and the end. (For huge files ed may not be the right tool though, see: bfs - big file scanner).

# sample code to match lines with number 5 plus previous & following line # (using Bash) printf '%s\n' {1..20} > num.txt # sed sed -n '/5/=' num.txt | while read num; do n1=$((num - 1)) n2=$((num + 1)) [[ $n1 -lt 1 ]] && n1=1 sed -n "${n1},${n2}p" num.txt echo -- done | sed -e '${/^--$/d;}' # ed cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}' H 0i beginning: added line one . $a end: added line one . ,g/5/km\ 'm-1,'m+1p q EOF 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.