2

I have the following conf file for nginx:

user       www www;  ## Default: nobody
...

events {
  worker_connections  4096;  ## Default: 1024
}

http {
  include    conf/mime.types;
  ...

  server { # php/fastcgi
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    root         html;

    location ~ \.php$ {
      fastcgi_pass   127.0.0.1:1025;
    }
  }

  server { # simple reverse-proxy
    listen       80;
    server_name  domain2.com www.domain2.com;
    access_log   logs/domain2.access.log  main;

    # serve static files
    location ~ ^/(images|javascript|js|css|flash|media|static)/  {
      root    /var/www/virtual/big.server.com/htdocs;
      expires 30d;
    }

    # pass requests for dynamic content to rails/turbogears/zope, et al
    location / {
      proxy_pass      http://127.0.0.1:8080;
    }
  }
}

This is a foo example that I found on www.nginx.com.

The goal is to extract all the servers' blocks for various treatments. In this particular case, there are two servers' blocks that I am interested in.

Because I work on very limited environments, I can only use sed/grep/awk/unix systems commands. No python, no pearl, ...

The problem with that kind of configuration files is that it is maybe possible that the server block itself contains some sub-blocks (i.e server { ... directive { ... }}). Considering that, it is impossible to simply use grep -oP "server {.*?}".

Can grep do the job? I've tried many different regex but didn't find the good one. For now, I worked with the above file without backslash (i.e cat $FILE | tr -d "\n"). I've tried things like grep -oP "server\s{1,}{.*?({.*?}){0,}}" but it does not fit my needs...

The reason I want to use grep is that I think that awk is not very readable afterward, and to maintain the code should be easier with grep - but I could make an exception if it is way easier with awk!

Thanks :)

EDIT:

Output should looks like:

"server { # php/fastcgi
  listen       80;
  server_name  domain1.com www.domain1.com;
  access_log   logs/domain1.access.log  main;
  root         html;

  location ~ \.php$ {
    fastcgi_pass   127.0.0.1:1025;
  }
}",
"server { # simple reverse-proxy
  listen       80;
  server_name  domain2.com www.domain2.com;
  access_log   logs/domain2.access.log  main;

  # serve static files
  location ~ ^/(images|javascript|js|css|flash|media|static)/  {
    root    /var/www/virtual/big.server.com/htdocs;
    expires 30d;
  }

  # pass requests for dynamic content to rails/turbogears/zope, et al
  location / {
    proxy_pass      http://127.0.0.1:8080;
  }
}"

Something that I can treat using commands like echo $OUTPUT | tr -d ... | grep -v .... If possible, I want to have each server block extracted on a single line to loop on the result!

2

If I understand you correctly, you are looking for something like:

$ awk '/server *{/{c=1; print;next} c&&/{/{c++} c&&/}/{c--} c' file
  server { # php/fastcgi
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    root         html;

    location ~ \.php$ {
      fastcgi_pass   127.0.0.1:1025;
    }
  }

  server { # simple reverse-proxy
    listen       80;
    server_name  domain2.com www.domain2.com;
    access_log   logs/domain2.access.log  main;

    # serve static files
    location ~ ^/(images|javascript|js|css|flash|media|static)/  {
      root    /var/www/virtual/big.server.com/htdocs;
      expires 30d;
    }

    # pass requests for dynamic content to rails/turbogears/zope, et al
    location / {
      proxy_pass      http://127.0.0.1:8080;
    }
  }

Explanation

  • /server *{/{c=1; print; next;} : if this line matches server, 0 or more spaces and then a {, set the variable c to 1. So, c will be 1 each time we find a new server{ block. Then print the line and skip to the next one.
  • c&&/{/{c++} : if the c is defined and not 0, and this line matches a {, increment c by 1 (c++). This could have been written as if(c && /{/){ c=c+1}. So, the value of c will increase for each nested { block found.

  • c&&/}/{c--} : if c is defined and not 0, and this line matches a }, decrement c by 1. This could have been written as if(c && /}/){c=c-1}. This ensures that the value of c goes down as each nested {} block is closed.

  • c : this is an awk trick. The default action when something evaluates to true in awk is to print the current line. Here, it will print if c is defined and not 0 (true). Because of the commands above which increment and decrement c depending on whether we're in a server block, this will result in printing only the lines you're after.

  • It is not working for me; I've tested with both original file and with a file where I did a copy-n-paste of the content given in this post! – Colin Leverger Jul 1 '16 at 14:55
  • @ColinLeverger ah, I think the \s is a GNUism. Try the updated answer. – terdon Jul 1 '16 at 15:02
  • Just be careful of commented out lines that may cause incorrect behaviour... – Stephen Harris Jul 1 '16 at 15:17
  • 1
    @ColinLeverger no, grep is not the right tool here. It's designed for printing lines matching specific patterns. Matching multiline patterns is possible with GNU grep using the -Z flag to slurp the entire file, but here you also need to keep track of nested brackets so no, it won't work. – terdon Jul 4 '16 at 8:35
  • 1
    @ColinLeverger see updated answer. To get the location ~ \.php$ { } block in your example, you could just change the /server *{/ to /location.*php\$/. – terdon Jul 12 '16 at 8:52

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.