26

I run diff with the -p option so the output will include the name of the function where each change occurred. Is there an analogous option for grep? If not, what other command could I use instead?

Instead of -B to show a fixed number of context lines that immediately precede a match, I'd like for the match to be preceded by just one line with the most recent function signature, however many lines back it was in the file. If the option I'm looking for were -p, output might look like this, for example:

$ cat foo.c
int func1(int x, int y)
{
  return x + y;
}
int func2(int x, int y, int z)
{
  int tmp = x + y;
  tmp *= z;
  return tmp;
}

$ grep -p -n -e 'return' foo.c
1-int func1(int x, int y)
3:  return x + y;
--
5-int func2(int x, int y, int z)
9:  return tmp;

9 Answers 9

24

There is no such function in GNU grep, although it has been discussed in the past.

However if your code is under git's control, git grep has an option -p that will do that.

Sign up to request clarification or add additional context in comments.

1 Comment

I think I've read that discussion before, long ago. It's probably what I was thinking of when I tried grep -p today. I'm using Mercurial now, but I might try Git for my next project. Thanks.
14

Here you go:

git grep --no-index -n -p 'return'

You just need git. The files being searched do not need to be part of a git repo. But if they are, then omit --no-index and get an instant speed boost!

1 Comment

This is a great answer. Given that many of us are going to have git on our machines, this solution will work for most.
6

Assuming you are searching for foobar:

grep -e "^\w.*[(]" -e foobar *.h *.cpp | grep -B 1 foobar

greps for all functions and all foobar, then greps for just foobar and the preceding lines - which will be only foobars and the containing functions.

tested on cygwin on windows version

3 Comments

This is really handy.
Slightly easy to understand: grep -e ^\\w.*\( -e 'return' foo.c | grep -B 1 'return'
Looking back at this 8 years later, I have no idea why I didn't use two -e's instead of 'or' |. Thanks for the improved expression :)
2

Unfortunately, no. This feature does not exist in grep nor does it exist in ack (which is ab improved grep replacement).

I really do wish this existed, though. It would come in handy. Someone did take a shot at implementing it a while back, but it doesn't look like their patch ever got accepted (or was ever even posted online, strangely). You can try emailing him and see if he still has the code and still wants to get an option to show C functions into grep.

You could write a regular expression to match a C function, but I bet that'd be one monster of a regexp.

3 Comments

You could grep for your pattern | function signature. There will be some extra noise but you'll get what you want.
For what I'm looking for, the regexp to match a function signature wouldn't have to be any better than the one diff uses — even ^\w.*\( seems to do a pretty good job. But applying it without limiting it to the context of the "real" match yields a lot of extra noise — I have over 1300 matches in just one subdirectory of my current project.
It's a feature that I'm thinking about for ack 2.x.
2

I wrote a script to grep C files and show the C function names and signature along with the results. Based on ctags.

#!/bin/bash

#
# grep_c_code
#
# Grep C files and print the results along with the function name and signature.
# Requires: ctags, gawk, sed, bash, and you probably want grep too.
#
# Written by David Stav, December 19 2012.
#
# Released to the public domain.
#

if [ $# -lt 2 ]; then
    echo "Usage: $0 <grep_cmd> <files/dirs...>" >&2
    echo "" >&2
    echo "Example:" >&2
    echo "  $0 'grep --color=always -n -e \"PATTERN\"' file1 file2 dir1 dir2 | less -R" >&2
    exit 1
fi

GREP_CMD="$1"
shift

GAWK_SCRIPT="`
sed -n -e '/^##### START of gawk script #####$/,/^##### END of gawk script #####$/p' \"$0\" | \
sed -n -e '2,$ { $ D; p}'
`"

ctags -f - -R --sort=no -n --fields=+afikKmsSzt --extra=+fq "$@" | \
gawk "$GAWK_SCRIPT" "$GREP_CMD" | \
bash

exit 0

##### START of gawk script #####
function parse_line(a)
{
    a["tagname"] = $1;
    a["filename"] = $2;
    a["line_number"] = gensub(/^([0-9]+).*$/, "\\1", 1, $3);
    if (a["line_number"] == $3)
    {
        a["line_number"] = "0";
    }
    a["kind"] = gensub(/^.*\tkind:([^\t]+).*$/, "\\1", 1, $0);
    if (a["kind"] == $0)
    {
        a["kind"] = "unknown kind";
    }
    a["signature"] = gensub(/^.*\tsignature:(.*)$/, "\\1", 1, $0);
    if (a["signature"] == $0)
    {
        a["signature"] = "";
    }
}

function grep_section(a, next_line_number)
{
    printf("\n");
    printf("\n");
    printf("\n");
    printf("cat '%s' | \\\n", a["filename"]);
    printf("sed -n -e '%s,%sp' | \\\n", a["line_number"], next_line_number);
    printf("%s | \\\n", grep_cmd);
    printf("sed -e '1 i \\\n");
    printf("\\n\\n\\n--\\\n");
    printf("[%s:%s]\\\n", a["filename"], a["line_number"]);
    printf("<%s> %s%s\\\n", a["kind"], a["tagname"], a["signature"]);
    printf("'\n");
}

BEGIN \
{
    FS = "\t";
    grep_cmd = ARGV[1];
    ARGV[1] = ""
}

!/^!/ \
{
    parse_line(next_line);
    if (a["line_number"])
    {
        next_line_number = next_line["line_number"] - 1;
        grep_section(a, next_line_number);
        delete a;
    }
    for (key in next_line)
    {
        a[key] = next_line[key];
    }
}

END \
{
    if (a["line_number"])
    {
        next_line_number = "$";
        grep_section(a, next_line_number);
    }
}
##### END of gawk script #####

Enjoy. :)

Comments

2

As with most text processing operations, it's trivial with awk:

$ awk -v re='return' '/^[[:alpha:]]/{f=FNR"-"$0} $0~re{printf "%s\n%d:%s\n--\n",f,FNR,$0; f="" }' file
1-int func1(int x, int y)
3:  return x + y;
--
5-int func2(int x, int y, int z)
9:  return tmp;
--

The above assumes a function signature is any line that starts with a letter (/^[[:alpha:]]/). If that's not the way your code is written, just tweak to suit.

3 Comments

Really trivial indeed! BTW how do I exclude re inside //?
I meant to say ignore the instance of the search pattern which is within commented lines. e.g //return bla bla bla
Nevermind. I have figured that out (not worth a new question)
1

Here is an imperfect solution. It has the following flaws:

  1. It requires a tool called ctags
  2. Consequently, it works for C files, or any languages that ctags supports, but not beyond that
  3. It displays all C function headers, no matter what. This is the biggest problem with my script, you might be able to find a way to overcome it.

I called my script `cgrep.sh', which has the following syntax:

cgrep.sh search-term files...

Cgrep.sh works by relying on ctags to produce a list of search patterns for function headers. We can then search for both the function headers and the search term. Without further ado, here is cgrep.sh:

#!/bin/sh

# Grep, which includes C function headers
# cgrep term files*

TERM=$1                             # Save the search term
shift

ctags "$@"                          # produces the tags file
sed -i.bak 's:^.*/^:^:;s:/$::' tags # Prepare the tags file for grep
                                    # Original contents is backed up to tags.bak
grep -f tags -e $TERM "$@"          # Grep both headers and search term
rm tags tags.bak                    # Clean up

Comments

1

Actually "grep -p" has been a fixture in AIX for the last two decades from what I can recall. It is out there, and it's just a matter of porting the behaviour over in fresh code.

It's crude, though, and may need help to know that blank lines within a function don't count.

Comments

0

You could write a script that grep -vs into a temporary file and then diff -ps that with the original. That way diff would find the lines that grep removed (i.e. the lines that you want), and you would get the exact same function matching.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.