Is there any alternative to grep’s -A -B -C switches (to print few lines before and after )?

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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

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.

Method 3

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>

Method 4

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" "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d5f195">[email protected]</a>"; }
# 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.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

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'

Method 5

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

Method 6

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 '%sn' {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


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x