[colug-432] First script - would anyone care to review?

Rick Hornsby richardjhornsby at gmail.com
Thu Jun 4 23:09:35 EDT 2015



Stephen Potter wrote:
> Zach-
> Overall, this looks like fairly clean code.
Most of my comments are around improvements, rather than any fatal 
issues.  I explain _why_ I point out certain improvements, and I want to 
be clear - those reasons should not be taken as a reflection on or a 
personal criticism of you.

For a first script, I'm impressed for the same reason - you've obviously 
taken care and pain to write readable code and provide plenty of 
comments.  Keep doing that.  I don't see anything that's too insane or 
crazy or that I'd give you a bad grade over.  It is nicely formatted, 
most of your indenting is where it should be and not all crazy jacked up.

Most of your certutil and pk12util commands are called out with a short 
description.  Keep doing this.  You will be glad you did, and so will 
those who read your code long after you abandon it.

+1 for avoiding the useless use of cat.
> I do, however, see a lot of
> code duplication that could lead to typos or forgetting to update a
> particular section during script maintenance.  For example, not that
> this currently affects the script, look at the difference between lines
> 25, 39, and 46.  You have used double quotes in one case and single
> quotes in the others.
Be careful with your quoting, especially in shell scripts.  double quote 
and single quote can have very different, and mutually exclusive, 
consequences.

There is an alternative syntax to the backticks that I encourage my 
engineers to use because I find it is much easier to read.  These two 
are functionally equivalent:

$ipaddr=`ip addr`

$ipaddr=$(ip addr)

The backticks can be hard to tell apart from the single quote 
character.  In the end, this is a personal choice.  I see both in your 
script.  Try not to mix and match the styles.


nit: $ipadrss -> prefer the more common usage $ipaddr


>    From an efficiency standpoint, I also see several places where you run
> the same command multiple times to get different information each time,
> where you might be able to tighten it up and only do it once.  However,
> in this day and age with the speed of systems, this kind of efficiency
> may no longer be something to be concerned about.
If it isn't necessary to ask the system to do the same thing multiple 
times, it's just generally better not to.  Duplicating a command does go 
to maintainability and readability.  Use variables instead of repeating 
the same string/path/command over and over.  This is more true for 
custom paths than standard or reasonably well-known ones, but could be 
applied to both.

"Use variables" applies not just to the result of commands, but to 
things that you're using multiple times as well, like the path

     /root/ldapca

I see this path repeated many times.  Set it once somewhere near the top 
and use the variable for the remainder.  You will be much happier when 
you need to change that path or reference it again.  By using the 
variable, you help ensure that you don't accidentally forget an 's' in 
cacpassword in one place, or change the path half-way through and not 
realize it.  For example:

     ldapca_path=/root/ldapca
     capassword_file=$ldapca_path/capassword

(I use _path and _file here because I'm trying to reduce ambiguity of 
the variable name.  Is 'capassword' a string representing a password?  
No, in this case it represents a file so I call it out in the variable 
name.)

Using the variable also helps make clear to your reader what you intend, 
and makes it easier to spot something you didn't intend or mean to do.

     cd /root/ldapca

This might just be my personal preference, but if I were providing a 
critique to one of my students or engineers, I would say don't do cd in 
your scripts unless it is absolutely and completely necessary, which it 
almost never is.  You're globally altering the context of your script's 
execution and then depending on a relative path to be correct.  An 
altered context like this is very, very hard to keep track of and makes 
it much harder to read into what the full path is later in a script, 
separated from this cd command.


     [ $? -eq 0 ] && echo "localhost ok" || { echo "localhost not ok"; 
exit 1; } ;;

Be very sparing about using complex expressions like this.  Within 
reason, if there is a more readable way to write something, choose 
readable over terse.  That's a general critique both for your future 
self and future maintainers.  Try not to bury critical code branch 
changes (ie terminal fault handling) inside a single line like that.  It 
makes it easy to miss the 'exit 1' when reading and troubleshooting --- 
and makes it much harder to modify the behavior later if needed.

I know it looks cooler and more geek-cred to write complex single line 
chains, but it is much less readable and far less maintainable over time.

I worked with a coder some years back who wrote terse, complex messes 
because he thought it made him look smarter and gave him the false sense 
that he alone owned his code. No one else could understand it enough to 
monkey with it.  Needless to say, he was lousy at his job because in the 
real world people have to collaborate.  I wasn't out to change his code 
- but I was trying to use it.  Without much documentation or commenting, 
I was forced to try to wade through the swamp of frustration - wasting 
tons of time.  When something he wrote wasn't working properly, it would 
take much longer to get it fixed.

     [ -f /etc/openldap/changes.ldif ] && { ldapmodify -Y EXTERNAL -H 
ldapi:/// -f /etc/openldap/changes.ldif; } || { echo "failed to modify 
changes.ldif"; exit 1; }

That line is too long and as mentioned above, too complex for a single 
line.  It is too complex because you're squashing tests and 
object/state/system modifying actions and a terminating fault command 
into a single line - basically one long statement.  Break it down - 
while it takes more lines, it is much more readable and easier to maintain.

     [ -f /usr/share/migrationtools/migrate_common.ph ] && { sed -i 
's/"padl.com"/"example.com"/' 
/usr/share/migrationtools/migrate_common.ph; } && { sed -i 
's/"dc=padl,dc=com"/"dc=example,dc=com"/' 
/usr/share/migrationtools/migrate_common.ph; }

At the risk of sounding like I'm harping on the same theme, please don't 
do this.

Here's a rule of thumb - if a line seems like a run-on sentence, it 
probably is.

Sometimes long lines (some of your certutil commands) are reasonably 
unavoidable.
>  From an output perspective, if you have more than one of a particular
> network interface (eth0, eth1, for example), your output doesn't readily
> identify which one works or doesn't work, which makes troubleshooting a
> bit more difficult.  Again, with the types of systems you are probably
> running this on, it probably doesn't significantly affect you, but it is
> something to think about as you develop more complex scripts with
> additional inputs and outputs. You might want to include $netw in your
> output to identify the interfaces.
Agreed.  Try also in general to do a little bit more error checking, 
with reasonable explanations and if possible a resolution or two for a 
failure.

You do some sanity checking, but you also run very long series of 
commands without making sure any of them worked before moving on.  As 
Stephen points out, it may not matter too much here but it is something 
to think about.

Along these same lines, be careful with the append redirect.  It takes 
more work, but it is useful to check before using an append to make a 
configuration change.  Say the script got half-way through and broke.  
Fix, run it again - the appends that happened before the script broke 
are going to happen again - duplicating configuration inside your config 
files which is messy and may have unintended consequences.

Using >> when you're writing to a log file - who cares.  Modifying 
pre-existing configuration files is a little different case.
>
> I do see one particular error in the host networking case script. Line
> 26 is attempting to cut information from a string, but the cut command
> takes a file as input.  To get this to work (you don't actually use it,
> because you have the lines that use it commented out), you would need to
> do an "echo $netw | cut".

At the end of each case statement, put the closing ;; on its own line 
for readability.

>
> An interesting thing you might want to look into to bring your scripting
> to the next level.  You have a couple of places where you do several
> echo statements that append to a file (lines 124-131 and 148-150, for
> example).  You could make these a little easier to read (and save
> yourself some keystrokes, which are always prone to typos) if you use
> "cat" and a "heredoc", like:
>
> cat<<EndofLdapConf>>  /etc/openldap/ldap.conf
> URI     ldap://instructor.example.com
> BASE     dc=example,dc=true
> TLS_REQUIRE allow
> EndofLdapConf
+1 to the HEREDOC suggestion.  The way you did it is fine - there's 
nothing wrong with how it is.  Stephen is correct though, for a long 
block like this it might be a little better as a HEREDOC statement.


     #enable logging

     echo "local4.* /var/log/ldap.log" >> /etc/rsyslog.conf
     systemctl restart rsyslog

Move this to be way earlier in your script.  Nothing about what you're 
doing depends on it being at the end, but it could help a great deal in 
troubleshooting to have the logs available before starting slapd, ssd, etc.


One last thing - the last thing, actually - try to explicitly exit with 
0.  It is not required, it will happen automagically etc etc - having it 
as the last thing you do will make your script a bit more readable and 
assure your reader that a) you intend to end things at this point and b) 
they can reasonably expect the return value / exit code to be 0 unless 
something has gone wrong.

Overall, this looks pretty good.  Keep at it.

exit 0



More information about the colug-432 mailing list