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.

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

Or with GNU grep:

grep -zPo '(?s)<Directory(?:.(?!</Directory))*?\+ExecCGI.*?</Directory>'
  • Excellent - just what I was looking for - brevity! – AnthonyK May 22 '14 at 22:07
  • It would be better if you explain the grep or perl command little bit. – Avinash Raj May 23 '14 at 1:52
  • I use the perl variant of this answer so I'll comment only on that: – AnthonyK Sep 10 '15 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 '15 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}. – Stéphane Chazelas Sep 10 '15 at 7:35
1

You could probably also use awk

awk 'BEGIN{RS="</Directory>\n"; ORS=RS} /\+ExecCGI/ {print}' file
  • 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). – Stéphane Chazelas May 23 '14 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.
  • I like this solution for its elegance - not to mention that I also learnt some perl tidbits along the way. Thanks. – AnthonyK May 22 '14 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>
  • 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 '14 at 13:26

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.