Everyone knows how to write CMD scripts. And this is the reason why we have so many messy CMD scripts around. Unfortunately, I haven’t seen anywhere some guidelines on how to write clear, maintenable batch files. So here is my take:
1) Think about discovery and usability. Anyone should be able to discover what your script is supposed to do (or how to invoke it) without knowing anything a-priori about it. Be friendly with those people.
First of all, if you need parameters, then make sure that (a) you have proper validation at least for the presence of these parameters and (b) you have a proper Help/Usage screen, displayed in the case when the script is called with no parameters, or with the /? option. Please don’t display huge Usage screens. 40 lines for a minimal help should be enough. Eventually, if your options won’t fit in one screen, then create a separate TXT or HTML file that documents the script in detail. And, even better, add in your usage screen a URL to the public location of your documentation.
2) Make sure that your script works properly (or fails properly) in non-standard conditions. By that, I mean things like passing arguments containing spaces, quotes, double-quotes, grave accents, etc. You might get around this by enclosing variables with double-quotes, but what about the case when your script parameters contain double-quotes themselves?
3) Additionally, the working directory can also give you lots of surprises. It sounds obvious, but would your script work properly if executed from a directory other than the actual script location? Always use absolute paths on every referenced file – %~dp0 is your friend.
And, even more traps: does your script work if the working directory contains spaces? What about the case when the script is executed from a network share? Make sure that you are prepared.
4) As I just mentioned, be prepared to have bugs in your script. Would a bug somewhere in your code cause disastruous consequences? I hope not :-). Also, make sure that you can easily debug your script by adding an optional command-line parameter to turn on “ECHO ON” spew. (echo off should be by default). Also, if you turn on additional spew, make sure it’s not too much. Some commands are not useful there (like REMs) so you might want to prefix them with @ anyway.
5) It goes without saying, but don’t forget to add comments for every relevant command-line execution. It is very easy to end up with a very cryptic batch file. Also, be generous with blank lines, especially between sequences of cryptic commands.
6) Think seriously about security. Is your script intended to “list” directories created by unprivileged users, for example to look for suspicious files? Can your script be “tricked” into running malicious commands just by listing some files? Sometimes, things like this are pretty un-obvious.
7) Use procedural programming as much as possible – “CALL :LABEL” is your friend. Try to partition your script into separate, clearly distinguished units of execution. This is especially useful when dealing with FOR commands (which tend to be crammed with various other sequences of sub-commands). That said, be careful when passing parameters in these calls – there are all sorts of exception rules when dealing with spaces and (double) quotes in parameters. If you want to pass parameters to these “routines” it is much easier to define local variables containing the data.
Also, avoid creating a bunch of small scripts that are cross-calling each other. Instead, define a single, self-contained script that uses a bunch of internal CALL :LABEL statements instead – you get almost the same thing.
8) If you regularly write scripts, then you should know by heart the following keywords: FOR, CALL, SET, GOTO and SETLOCAL and all their command-line options. You should also be familiar with all the cryptic syntax of manipulating variables. Expressions like %T1:a=22% or %~dpi should not look strange to you.
9) Make sure that you control properly the lifetime of your environment variables. Nothing is more annoying than to debug random bugs due “leaking” env variables that affect subsequent runs of your script (or your routines). Use SETLOCAL extensively. First of all, make sure that you have a SETLOCAL at the beginning of your file. Second, make sure that all your relevant “routines” have the associated SETLOCALs (unless your intention is to explicitly leak variables, but that should be more the exception than the rule).
10) Don’t forget about proper maintenance. Make sure that you maintain a version string at the beginning of your script. Nothing fancy, something like this: “@REM v1.01 some text”. But don’t forget to update this version each time you make a change! Otherwise, it is hard to keep track of all these intermediate versions of your script that are floating around on the intranet. And, last, don’t forget to save a master copy of your script in a safe location which is easy to access and remember. A network share on a regularly backed-up server is probably the best place.
[update: minor fixes]