No, grep can't do that. However, you could easily write your own custom search tool with awk or perl. e.g.
find ./ -name '*.md' -exec awk -v search="yada-yada" '
/^# / { title=$0; sub(/^# /,"",title) };
/^## / { subtitle=$0; sub(/^## /,"",subtitle) };
$0 ~ search { printf "%s:%i:%s:%s:%s\n", FILENAME,FNR,title,subtitle,$0 }' {} +
This is a very primitive example, and could be customised to suit your exact needs, and otherwise greatly improved. It should probably be wrapped in a shell script so you can have something like -v search="$1"
rather than hard-coding it to "yada-yada"
.
Here's a slightly better version, in perl. This one doesn't require find
(it uses perl's own File::Find module instead) and is possibly easier to extend with better option handling (e.g. you could support searching multiple directories similar to find
, or add -i
or -v
options for case-insensitivity or to invert the match, same as grep and some other programs):
#!/usr/bin/perl
use strict;
use File::Find;
# Very primitive argument handling, should use Getopt::Long or
# one of the other Getopt::* modules
my $path = shift; # first arg is dir to search, e.g. './'
my $search = shift; # second arg is regex to search for
find({ wanted => \&wanted, no_chdir => 1}, $path);
sub wanted {
# This uses \z in the regex rather than just $ because
# filenames can contain newlines.
next unless (-f $File::Find::name && /\.md\z/s);
# open the file and "grep" it.
open(my $fh, "<", $File::Find::name) || warn "couldn't open $File::Find::name: $!\n";
my $title = '';
my $subtitle = '';
while(<$fh>) {
chomp;
if (/^# /) {
($title = $_) =~ s/^# //;
} elsif (/^## /) {
($subtitle = $_) =~ s/^## //;
} elsif (/$search/) {
printf "%s:%i:%s:%s:%s\n", $File::Find::name, $., $title, $subtitle, $_;
# uncomment the next line if you want only the first match in
# any given file (i.e. same as '-m 1' with grep):
# close $fh;
}
};
close($fh);
}
Sample run:
$ ./grep-md.pl ./ yada-yada
./file.md:5:Title:Subtitle:yada-yada
./file2.md:5:Another Title:And Another Subtitle:yada-yada
./sub/dir/several/levels/deep/file3.md:5:Third Title:File Three Subtitle:yada-yada
BTW, this could also be written to use find ... -exec
to find the files rather than doing it with File::Find, and would probably be better if it did....I mostly wrote it this way to show multiple different ways of achieving the same end goal.