1

I've got a task to extract data from several Apache servers. The task is to print out:

<Directory ...>
  ...
</Directory>

where +ExecCGI is located within. Let me give an example to illustrate. Assume that the Apache configuration file has numerous Directory sections as indicated below:

<Directory /var/www/site1/htdocs>
  Options +ExecCGI
  ...
  ...
</Directory>
...
...
...
<Directory /var/www/site1/Promo>
  Options -ExecCGI
  ...
  ...
</Directory>

From above, I would only like to get the following output:

<Directory /var/www/site1/htdocs>
  Options +ExecCGI
  ...
  ...
</Directory>

I've searched the forums and have found posts where people have asked questions on how to print out a whole section between tags (I know how to do that), or to change certain text when found (again, I know how to do that).

I will be changing the +ExecCGI to -ExecCGI, but the changes need to go through a review process and hence this question so that I can pull this data out.

4 Answers 4

3
perl -l -0777 -ne 'for (m{<Directory.*?</Directory>}gs) {print if /\+ExecCGI/}'

Or with GNU grep:

grep -zPo '(?s)<Directory(?:.(?!</Directory))*?\+ExecCGI.*?</Directory>'
5
  • Excellent - just what I was looking for - brevity!
    – AnthonyK
    May 22, 2014 at 22:07
  • It would be better if you explain the grep or perl command little bit. May 23, 2014 at 1:52
  • I use the perl variant of this answer so I'll comment only on that:
    – AnthonyK
    Sep 10, 2015 at 7:25
  • The -l means an octal value will follow the following -0 option. In perlrun, it states that, and I quote, "Any value 0400 or above will cause Perl to slurp files whole, but by convention the value 0777 is the one normally used for this purpose." - taken from 'perldoc perlrun'
    – AnthonyK
    Sep 10, 2015 at 7:33
  • @AnthonyK, here there's no octal value after -l (otherwise that'd be -l0777). The only purpose is to set the output record separator to newline ($\ = "\n"). The next -0777 sets the input record separator ($/ = undef as 0777 is an invalid byte. That's know as the slurp mode). Basically -l -0777 is equivalent to a BEGIN{$\=$/="\n";$/=undef}. Sep 10, 2015 at 7:35
1

You could probably also use awk

awk 'BEGIN{RS="</Directory>\n"; ORS=RS} /\+ExecCGI/ {print}' file
1
  • That doesn't print the part starting at <Directory (you could use index and substr to extract the part that start at <Directory). Note that multi-character/regex RS are not portable (mawk and gawk only). May 23, 2014 at 6:47
0

If you can use perl, here is a solution:

$ perl -nle '
    if (/<Directory/) {
        $flag = 1;
    }
    push @a, $_ if $flag;
    if (/<\/Directory/) {
        $flag = 0;
        if (grep {$_ =~ /\+ExecCGI/} @a) {
            push @f, @a;
        }
        @a = ();
     }
END {
    print join "\n", @f;
}' file
<Directory /var/www/site1/htdocs>
  Options +ExecCGI
  ...
  ...
</Directory>

Explanation

  • Each time we see <Directory, we set $flag = 1.
  • If $flag is true (1 means true in boolean context), we push current entry to array @a.
  • If we see </Directory, meaning we complete the block, we check if block contains +ExecCGI string grep {$_ =~ /\+ExecCGI/, then we push @a to @f.
  • Set @a to empty array to process another block.
1
  • I like this solution for its elegance - not to mention that I also learnt some perl tidbits along the way. Thanks.
    – AnthonyK
    May 22, 2014 at 13:22
-1

Here's an example in Bash (You should be able to do something similar in pretty much any language):

$ cat test.sh
#!/bin/bash

DIR=0
BLOCK=''
while read line
do
    if [ $DIR -eq 0 ] ; then
        if [[ $(echo $line | grep -i '<Directory') ]] ; then
            DIR=1
            BLOCK="$line"
        fi
    else
        BLOCK="$BLOCK\n$line"
        if [[ $(echo $line | grep -i '</Directory') ]] ; then
            if [[ $(echo $BLOCK | grep -i 'Options.*+ExecCGI') ]] ; then
                echo -e $BLOCK
            fi
            DIR=0
            BLOCK=""
        fi
    fi
done

Basically, we're just saving the block as we go along and then grepping it to see if it's got our pattern in it.

It's very simple, and is likely to have problems with some edge cases (if your config file has \ in it it might confuse echo -e for example), but you could expand the basic idea to deal with them.

Example in use:

$ cat test.conf
<Directory /var/www/site1/htdocs>
  Options +ExecCGI
  1
  2
</Directory>
3
4
5
<Directory /var/www/site1/Promo>
  Options -ExecCGI
  6
  7
</Directory>
<Directory /var/www/site1/htdocs>
  Options -Whatever +ExecCGI
  8
  9
</Directory>

$ cat test.conf | bash test.sh
<Directory /var/www/site1/htdocs>
Options +ExecCGI
1
2
</Directory>
<Directory /var/www/site1/htdocs>
Options -Whatever +ExecCGI
8
9
</Directory>
1
  • Bash is my daily scripting language and I would have come up with something similar. I was hoping to get something along a 1-liner but obviously I might be asking for too much.
    – AnthonyK
    May 22, 2014 at 13:26

You must log in to answer this question.

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