# sed to fix a messy nginx.conf

I have a few servers to fix that repeatedly get their nginx.conf file messed up due to a rogue deploy script.

These are the three sed commands I'm using to fix at present:

sed -i '' 's/^$$[[:blank:]]*$$#$$[[:blank:]]*$$server_names_hash_bucket_size/\1server_names_hash_bucket_size/g' nginx.conf


The first finds any kind of blank space at the start of a line followed by a pound symbol and the string server_names_hash_bucket_size. Then it replaces it with the original blank space (to honour any indentations) and the string without the pound.

sed -i '' '/^$$[[:blank:]]*$$server_names_hash_bucket_size/s/[0-9]//g' nginx.conf


The second gets rid of any numbers:

sed -i '' '/^$$[[:blank:]]*$$server_names_hash_bucket_size/s/;/64;/g' nginx.conf


The third finds the semi colon and replaces it with the necessary value 64;

I'm currently testing this in terminal OSX, but ultimately will be used on Linux machines.

Does anyone know how to tidy this up and can point out any potential Linux gotchas with sed?

In the general case, the answer to any question regarding sed is "use something else."

Not that sed is not useful! It definitely is! It's just that it's best for simple tasks that have compact, obvious solutions.

In particular, Perl is good at replacing sed scripts, thanks to its more expressive regular expressions and uncomplicated access to conventional programming constructs. It even comes with an automated tool to do the translation from sed to Perl, called s2p.

perl -pli.bak -e'
next unless s/^(\s*)(#\s*)?(?=server_names_hash_bucket_size)/$1/; tr/\d//d; s/;/64;/; ' nginx.conf  The newlines are optional and included for readability. Line by line: • -p means "print each line after running this code against it." This should be familiar since sed works exactly the same way. • -l means "remove newlines before running the code and reinstate them before printing." • -i.bak means "edit in-place and save the original as original.bak". • -e means the same thing as in sed. • We're only interested in lines with server_names_hash_bucket_size, so skip to the next line if the replace fails (the unmodified line still prints; we're skipping the rest of this code but not the printing). • We need to include lines that had no #, so make the # match optional with ? after (#\s*). • (?=...) is a lookahead; the text must exist but is not included in the left side of the substitution, so we don't need to include it on the right side. Only the (optional) leading whitespace ($1) needs to be reinstated on the RHS.
1. tr works like y in sed. This line is equivalent to s/[0-9]//g (as far as we're concerned. Technically, there are digits other than 0-9 but they aren't going to appear in your nginx.conf.)

2. Same as sed.

You can simplify even further by recognizing that the only significant parts of a server_names_hash_bucket_size line, after you throw away the number, are the leading whitespace and terminal comments.

Preserve the indent as before, throw away as many non-comment characters as possible ([^#]*) and rewrite the line in one replace operation:

perl -pli.bak -e' s/^(\s*)(?:#\s*)?(server_names_hash_bucket_size)[^#]*/$1$2 64;/ ' nginx.conf


(?: makes that group non-capturing, so the second capture (\$2) will be server_names_hash_bucket_size.

At this point, we're back in "simple enough for sed" territory:

s/^$$[[:blank:]]*$$$$#[[:blank:]]*$$*$$server_names_hash_bucket_size$$[^#]*/\1\3 64;/

• Fantastic write up @oh-my-goodness, I'm going to adopt some of Perls more intuitive syntax for complex adjustments. The final 'simple enough for sed' solution works perfectly too; commented matches of the string are ignored correctly, indentations are honoured, and the 64 value is successfully placed where needed. Thank you – mar627 Mar 2 at 3:04
• thanks! Note I made a small fix to the sed after your comment, to allow matching more than one space after #. – Oh My Goodness Mar 2 at 7:46