I'm looking at source files. For PureBasic actually, but whatever the language, there are common elements involved. In my case, the command would be cover extensions pb, pbi, pbf, and pbp:

find . -name "*.pb*" -exec grep -Hnr ... \;

The problem is, this can result in very long lines, and too contracted to read easily because they include full/path/filename.ext:ln#:contents of that line.

I want to do one of two things: Either cut off the path completely (last / before first :) and leave:

filename.ext:ln#:contents of that line

or split the line so that I have:

ln#:contents of that line

Either way makes the results more readable. You could even split it where you had:

contents of that line

for increased readability. I'm flexible. It can be any of these. It doesn't even have to be limited to a one-line command or these specific commands.

I've tried every technique I could find listed, involving find,grep and sed but I can't get the combo to do exactly what I want.


I assume, from your description, that you are doing this (I broke it into four lines to avoid horizontal scrolling)

find . -type f -name "*.pb*" \
     -exec grep -Hn pattern "{}" ";" >>tmp.txt;
cat tmp.txt | more

If you are using Gnu find, you can use -execdir instead of -exec; that will cause the grep command to be executed from within the directory of the target file, with the result that the filename will be relative to the directory it is in. (i.e. ./filename.ext). That's not quite what you're asking for, but it's pretty close, and there are other good reasons to use -execdir.

     -execdir grep -Hn pattern "{}" ";"      # See note 1

Another option is to use an intermediate shell to edit the filename label. This requires Gnu grep for the --label option:

     -exec bash -c 'grep -Hn --label="$(basename "$1")" pattern "$1"' \
           _ "{}" ";"

That's quite a bit longer and a lot more work for the OS, but it does eliminate the ./.

For the second option, you can pipe the entire output through sed, inserting a newline after the first (or second) : in each line.

find . -type f -name "*.pb*" \
     -exec grep -Hn pattern "{}" ";" |
sed 's/:/&\n/' >>tmp.txt.

or (second colon):

find . -type f -name "*.pb*" \
     -exec grep -Hn pattern "{}" ";" |
sed 's/:[^:]*:/&\n/' >>tmp.txt.


  1. There is no point telling grep to do a recursive search (-r) because you are never giving it a directory as an argument. find has already recursively found all the files. So I removed the option from all my examples.

  2. cat tmp.txt | more is Useless Use of Cat (UUOC). Just use more tmp.txt.

  3. With Gnu find replace ; with + for more efficiency (except in the example which uses bash -c).


Starting from the premise that you want to search for PATTERN in your files, here are solutions for each of your suggested layouts:

  1. Remove the leading pathname component

    find . -name '*.pb*' -type f -execdir grep -Hn 'PATTERN' {} \; | cut -c3- >tmp.txt
    more tmp.txt

    When using execdir the program it references is always called from the target directory, and the current file's pathname starts with ./. The cut -c3- simply removes the leading ./ from the pathname.

  2. Split the line with pathname, followed by line number and match

    find . -name '*.pb*' -type f -exec grep -Hn 'PATTERN' {} \; | sed 's/:/:\n/' >tmp.txt
  3. Split the line with pathname and line number, followed by match

    find . -name '*.pb*' -type f -exec grep -Hn 'PATTERN' {} \; | sed 's/^\([^:]*:[^:]*:\)/\1\n/' >tmp.txt

    Here, the sed pattern splits at the second colon (the RE reads as Start at the beginning of the line. Match zero of more non-colon characters followed by a colon. And again. Now replace that match with itself and then a newline).

None of these solutions will work well if your filenames contain colons or newlines.


You could try with awk instead of grep, e.g. print only basename:

find . -name '*.pb*' -exec awk 'function basename(file) {
sub(".*/", "", file)
return file
print basename(FILENAME)":"FNR":"$0
}' {} +

and to split path/line content:

find . -name '*.pb*' -exec awk '/pattern/{print FILENAME"\n"FNR":"$0}' {} +


find . -name '*.pb*' -exec awk '/pattern/{print FILENAME":"FNR":\n"$0}' {} +

If you want to print the filename only once, followed by all matching lines in that file, with two greps:

find . -name '*.pb*' -exec grep -l pattern {} \; -exec grep -n pattern {} \;

These should work OK with all kind of file names (even with those that contain colons)...


use grep -l

find . -type f  -exec grep -ilHn --color=always 'searchphrase' {} \;
  • this does not answer the question; it will print only the (full path) file name not the matching lines - please read the question before posting answers – don_crissti Feb 23 '17 at 18:07
  • right i agree i overlooked it. – Konstantin Gerasimov Feb 23 '17 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.