3

I have a file that contains this:

[...]

location /static {
    ...
    multiple lines
    ...
}

[...]

location /static/ {
    ...
    multiple lines
    ...
}

[...]

And I want to get:

[...]

# location /static {
#     ...
#     multiple lines
#     ...
# }

[...]

# location /static/ {
#     ...
#     multiple lines
#     ...
# }

[...]

How can I manage to do that giving my file to an unix command?

  • Interesting question. +1'd ya. – James T Snell Feb 7 '14 at 17:23
  • I don't know unix syntax, but you could try matching the pattern ^([^\[]) and replacing with #\1. This will work if none of the lines between bracketed expressions have a [ at the start of the line. – Excellll Feb 7 '14 at 19:59
  • Can the multiple lines contain } before the closing }? – terdon Feb 8 '14 at 15:05
  • I would have say no but I love that your answer assumes so eventually. – Natim Feb 9 '14 at 7:13
2

This is not trivial. If you can assume that each {} block does not contain other, nested {} blocks it is easier and you can do something like this:

perl -pe 'if(/location\s*\/static/){$n=1}elsif(/}/){$n=0} s/^/#/ if $n==1;' file

This simply sets $n to 1 if the current line matches location /static and sets it back to 0 at the first } found after the location/static. Then, as long as $n==1, it addas a # to the beginning of the line. The -p flag causes perl to automatically loop through the input file and print each line.

Now, if you can have arbitrary depth nested blocks within the blocks you want to comment, things get more complicated. For example, if you have something like this:

location /static {
   if(foo){
      print "one";
   }
   elsif(bar){
      print "two";
   }
}

For cases like that, the simple solution above will fail and you will have to use one that keeps track of the number of open {. For example (this is actually a one-liner, you can copy/paste directly into your terminal, I just expanded it for clarity):

perl -pe 'if(/location\s*\/static/){$n=1;}
          elsif(/}/ && $open==0){$n=0} 
          if($n==1 && /{/){$open++} ## count open brackets
          elsif($n==1 && /}/){$open--} ## count closing brackets
          if($n==1 && $open>0){ s/^/#/}; ' file

Finally, if the solutions work as expected, you can add the -i flag do make the changes to the file itself:

perl -i -pe 'if(/location\s*\/static/){$n=1}elsif(/}/){$n=0} s/^/#/ if $n==1;' file
  • The first one forget to comment the last } line. – Natim Feb 9 '14 at 7:20
  • Something like that? perl -pe 'if(/\s*location\s*\/static/){$n=1}elsif(/\s*}/ && $n==1){$n=0} if($n!=2){s/^/# /} if($n==0){ $n=2}' file – Natim Feb 9 '14 at 7:29
  • And to uncomment: perl -i -pe 'if(/^# \s*location\s*\/static/){$n=1} elsif(/^# \s*}/ && $n==1){$n=0} if($n!=2){s/^# //} if($n==0){$n=2}' filename – Natim Feb 9 '14 at 9:02
0

Extracting (possibly nested) delimited blocks with regular expressions is not particularly fun or easy. There is an elegant solution, though, using a module that's been shipping with Perl for a long time now (since Perl was one of your tags), viz. Text::Balanced:

#!/usr/bin/env perl
use strict;
use warnings;
use Text::Balanced qw( extract_bracketed );

my $in = do { local $/ = undef; <> };
while( $in ) {
    my $out;
    if    ( $in =~ s/^(location\s+\S+\s+)// ) { ( $out = $1 . extract_bracketed($in) ) =~ s/^/# /mg }
    elsif ( $in =~ s/^(.*[\r\n]*)// )         { $out = $1 }
    print $out;
}

This script works by repeatedly consuming (extracting) and analyzing the leading part of the string, until nothing is left:

  • If the leading part contains the location keyword, followed by whitespace (\s+), and something that looks like it could be an identifier (currently identified very crudely by it being a sequence of non-whitespace characters, \S+), then extract_bracketed will extract the delimited block that follows (by default, it will extract a block delimited by any of the following pairs: [], {}, () or <>). extract_bracketed will correctly deal with nested, balanced delimiters inside the block to be extracted. The following substitution s/^/# /mg is responsible for commenting out the individual lines in the block, no matter how many lines it might contain. The block (together with the leading location keyword) is then printed out.

  • Otherwise, a line (up to and including the newline character) is extracted and printed unmodified.

Some other things to note:

  1. the text is read and stored in its entirety in a string ($in) by undefining the record separator $/
  2. $1 is the special variable that holds the contents of the regular expression delimited by parentheses; e.g., for (location\s+\S+\s+), $1 contains the text location /static (including the trailing space).

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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