3
\$\begingroup\$
#!/bin/bash
#extractApacheResolutionName

GrepAndFilter="ServerName|ServerAlias"
RemoveBadMatch="s/^\s*($GrepAndFilter)\s+//gI"
RemoveTrailingWhitespaces="s/\s*$//"
RemoveComas="s/,//g"
ReplaceSpacesWithNewLines="s/ +/\s/g"

grep --line-buffered -Ei "$GrepAndFilter" /dev/stdin \
    | sed -E "$RemoveBadMatch; $RemoveTrailingWhitespaces; $RemoveComas; $ReplaceSpacesWithNewLines"

The script is to be used mainly to gather all the URLs of a remote Httpd Configuration, and output them one URL per line, such as

ssh hostname "cat /etc/httpd/conf.d/*.conf" | extractApacheResolutionName

Nb : the actual script contains a sort

... | rev | sort -u | rev

... but that's another question I asked on StackOverflow, because I would like to extract it to another piece of software.

What can I do to improve my script ?

  • from a readability point of view ?

  • from a performance point of view ?

\$\endgroup\$
  • \$\begingroup\$ Do you have sample input and expected output? I've prepared an alternative, but have nothing to test it with. \$\endgroup\$ – Toby Speight Oct 18 '18 at 14:51
1
\$\begingroup\$
  • This doesn't use anything in /bin/bash that's not available in /bin/sh, so prefer to use the simpler and more portable shell.
  • Not all sed implementations allow multiple semicolon-separate commands in one argument; if you want to be portable, specify each separately:

    sed -E -e "$RemoveBadMatch" -e "$RemoveTrailingWhitespaces" ...
    
  • No need to run a separate grep - we can use the pattern as an address for the commands in our sed script.

  • Final command pipeline can be exec'ed to save on creating new processes.

I'd just write this as a sed program; no need for a shell:


#!/bin/sed -Ef

# remove lines that don't start with ServerName or ServerAlias
/^\s*Server(Name|Alias)\s+/!d

# remove the prefix
s///

# remove commas and trailing whitespace
s/,//g
s/\s+$//

# split words into separate lines
s/ +/\n/g

Obviously, not invoking a shell will improve performance.

\$\endgroup\$
  • \$\begingroup\$ I never thought of using sed this way. You broadened my horizon. \$\endgroup\$ – Pierre-Antoine Guillaume Oct 18 '18 at 20:46
2
\$\begingroup\$

What is "bad match" in here?

RemoveBadMatch="s/^\s*($GrepAndFilter)\s+//gI"

My understanding is that this pattern is used to remove the $GrepAndFilter prefix from lines, with any surrounding whitespace. I don't see that prefix as "bad match" in any sense of the word. It seems RemoveFilterPrefix would be a better fit.


Here, instead of \s*$ as the pattern, I'd use \s+$, to emphasize that the content will be modified only when there are non-zero trailing whitespaces.

RemoveTrailingWhitespaces="s/\s*$//"

Here, the pattern doesn't match the name:

ReplaceSpacesWithNewLines="s/ +/\s/g"

I think you meant \n as the replacement, because \s doesn't have a special meaning in a replacement string, so as written, this command will replace spaces with the letter s.


grep assumes /dev/stdin as input by default, no need to specify it explicitly.


In many systems, the Apache configuration files often contain ServerName and ServerAlias on commented out lines. The posted script won't work with such lines, and I can easily imagine such lines in production systems too. I suggest to make the script more robust by excluding commented lines. This could be as simple as making the first filter more strict:

GrepAndFilter="^\s*(ServerName|ServerAlias)\s+"
\$\endgroup\$

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.