How would I search for a certain string that is preceded by another string, in most cases several lines up, and followed by another string, i.e. contained in a certain environment?

%several lines of text
%probably a few more lines of footnote content
%several lines of further text

Thus I am searching for footnotes (starting with \footnote{) contained in a quote environment (starting with \begin{quote}, ending with end{quote}, in most cases spanning over several lines). Actually, in reality I should search for another environment contained in footnotes which occur inside quote environments, thus adding a second layer of conditions, but probably once I understand the simplified example I can figure out my real world problem. The actual environment I search for occurs about 150 times in a directory containing about 20 .tex files, constituting the sources for a roughly 500 pages book supposed to go to print in a few days, and that particular combination produces an undesired effect, which many times, if not looking carefully, will go unnoticed.

As output of my search I would like to get the file name in which the match occurs (I would probably search through *tex, although, if that should be too complicated, I could also work with a bash for loop), and its line number.

One will also have to keep in mind that


would be something occurring many times, but it would be a false positive.

  • 2
    Add the expected output to the question. Does it also need to contain the two strings themselves? Nov 15, 2021 at 23:56
  • @NasirRiley I've added the expected output. No, the strings themselves do not need to be included in the output.
    – muk.li
    Nov 16, 2021 at 0:03

3 Answers 3


You can't do this with grep, you'll need something more capable like awk or perl. Here's a very simple example of doing this in perl:

$ cat find-fn-in-quote.pl 

while(<>) {
  # strip comments so that we ignore commented-out quotes & footnotes.

  # detect beginning and end of quotes
  if (m/\Qbegin{quote}\E/) { $qt = 1; $ql = $.};
  if (m/\Qend{quote}\E/)   { $qt = 0 };

  if (eof) {
    # reset line-counter ($.) after every input file

    # reset $qt to zero, in case of unbalanced begin/end{quote}
    $qt = 0;

  # skip to next input line if we're not inside a quote.
  next unless ($qt);

  if (m/\\footnote\{/) {
   print "$ARGV: found footnote beginning on line $. inside quote beginning on line $ql\n";
   # For terse output, comment out or delete the print statement above
   # and un-comment one of these:
   #printf "%s:fn=%i:q=%i\n", $ARGV, $., $ql;
   #printf "%s:%i:%i\n", $ARGV, $., $ql;


Note: This script is not capable of properly handling nested begin{quote}s. I doubt that this will be a problem, as it's unlikely to happen outside of contrived examples. It also assumes that a quote will exist entirely within one file - in fact, the script deliberately enforces this to ensure that a bug in one input file (i.e. a missing end{quote}) won't affect subsequent files.

I created 3 text files to test this with. input1.txt contains your first sample input. input2.txt contains your second sample input with the false-positives you don't want to match. input3.txt contains input2.txt followed by input1.txt (i.e. cat input2.txt input1.txt > input3.txt). Running the script on these input files gives the following output:

$ ./find-fn-in-quote.pl *.txt
input1.txt: found footnote beginning on line 3 inside quote beginning on line 1
input3.txt: found footnote beginning on line 14 inside quote beginning on line 12
  • Works! But made me aware of the fact that the problem not only occurs in quote environments, but also in verse, possibly in others. How do I add the word alternative to the regex?
    – muk.li
    Nov 16, 2021 at 7:55
  • 1
    I wouldn't use regex alternation for that because quotes and verses are independent of each other. instead, copy the tests for \begin{quote} and \end{quote}, modify them to test for verse instead, and use a different variable (e.g. $vs) instead of $qt. Then change the next unless ... line to next unless ($qt or $vs);. You'd also need to produce different output depending on whether you found a footnote inside a quote or inside a verse.
    – cas
    Nov 16, 2021 at 8:02
  • 1
    alternatively, write two versions of the script - one to find footnotes inside quotes, another to find footnotes inside verses.
    – cas
    Nov 16, 2021 at 8:07
  • Yes, two versions will be the easier solution. Anyway, I'm taking the opportunity to play a bit with perl, and try to make it work for both cases in one script. Should that become too complicated I can still work with two separate scripts.
    – muk.li
    Nov 16, 2021 at 8:23
  • have fun! perl's pretty much the ideal text processing language, it can do everything that sed, awk, grep, tr, cut, and many others can do....and that's even before you start looking at the CPAN module library. BTW, I often use little perl scripts to generate TeX output - e.g. to create enumerate or description lists or tables from a database (or a csv file. or whatever) or to convert plain, or structured or markdown text to consistently formatted tex.
    – cas
    Nov 16, 2021 at 8:49

An awk version of the

awk 'FNR==1{looking=0; print  FILENAME}
  looking&&$0~/footnote{/{print FNR, $0}' *.tex

Simply starts "looking" when a quote begins and stops when it ends.

Like @cas example this resets at each opening of a file.

If you are going to be looking inside the footnote for another environment then it gets trickier as you will need to start looking for for balanced } for the \footnote on different lines, which is a different animal.

  • Yeah, correctly handling nested curly-braces etc gets very complicated. I first thought of using perl's Regexp::Common::balanced module and then I realised it didn't need to be anywhere near that complicated - all it needs to do is detect whether a footnote is inside quote or not.
    – cas
    Nov 16, 2021 at 8:11
  • @cas surely it's OP's need to find an environment inside the footnote that drives...... the need to encapsulate the search for that environment within the footnote{..\n...whatever other environments..\n....} scope. Finding the footnote in the quote/verse is a walk in the park.
    – bu5hman
    Nov 16, 2021 at 8:20
  • huh? (extra text to satisfy min length of 15 characters)
    – cas
    Nov 16, 2021 at 8:45

GNU awk:

awk '
/^\\end{quote}$/ && fire        {report = report OFS str}
/^\\end{quote}$/                {block=cont=fire=0; str=""}
/^\\begin{quote}$/              {block=1}
block && /^\\footnote{%$/       {cont=1; str=FNR; next}
block && cont && /}/            {fire=1;cont=0}
ENDFILE                         {if(report)print FILENAME report; report = ""}
' *.tex

Output format:

filename line_number line_number ...

All line numbers with matches are output in one line for each file. File names without matches are not displayed.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .