Shell Script Standards
These guidelines should be followed when writing shell scripts.
Musts
Executability
- Include a
#!
line at the top of the file to indicate the shell under which the script should be run. In particular, if the script uses special features of a specific shell (e.g.,bash
), the#!
line must indicate that specific shell (e.g.,#!/bin/bash
). - Make the file executable.
Error Handling
- Execution of a script must stop upon (fatal) errors. This can be done, for example, by appending
|| exit 1
to lines that must succeed for the script to continue, or by usingset -e
(perhaps beware ofset -e
?). - If a script completes successfully, it must end with exit status 0.
- If a script fails to complete, it must end with a nonzero exit status.
Syntax
- Always use curly braces around variables, like
${varName}
. (This makes it crystal clear where a variable name starts and ends.) - Be defensive about spaces and special characters in parameters and variables—specifically, path variables should always be quoted.
- Use
$( … )
for subshell execution (don't use backticks` … `
). - Use line-continuation (backslash-escaping of the newline) to break commands up into logical parts that aren't ridiculously long.
Formatting
- Indentation by 4 spaces.
- For line-continuation, indent subsequent lines one level.
if
statements:- Put
then
on the same line asif
/elif
(or on the same line as the last part of the condition if the condition is split across multiple lines, using a;
between the condition andthen
. - Put
elif
,else
, andfi
on their own lines. - Indent the bodies of the
then
andelse
clauses.
- Put
case
statements:case
-in
on its own line- Indent each case (the pattern to match) one level.
- Indent the body a second level.
- Put the
;;
between cases on its own line, at the same indentation level as the body.
for
-do
-done
,while
-do
-done
,until
-do
-done
, andselect
-do
-done
:- Format similarly to
if
-then
-fi
statements.
- Format similarly to
Shoulds
- Shell script file names should have the extension
.sh
.
Portability
Whenever it's reasonable to do so (doesn't add significant burden for little or no gain), it is preferable to:
- use
sh
techniques overbash
-specific techniques - use POSIX or BSD commands and parameters over GNU/Linux-specific (or other system-specific) commands and parameters (unless the context of the script dictates that it will only ever be used on a more specific system than generic POSIX or BSD)
Remember that for the most part, OS X is BSD and not GNU/Linux. On OS X, sed
, awk
, and some other commands do not support all of the features and parameters of their GNU counterparts.
Error Handling
-
A useful construct for exiting on errors, but with additional error handling is:
# After the command that might error, instead of || exit 1... if [ "$?" -ne 0 ]; then # Additional error handling (messaging, etc.) here. exit 1 fi
-
Using distinct values for each nonzero exit status makes it easier for the caller of the script to determine what happened based on the exit status. Starting at 1 and incrementing for each distinct
exit
is one way to do this.
Syntax
- Prefer long/verbose parameter and flag names when available, to aid in readability.
- With complex commands, prefer one parameter/flag per line, using line-continuation.
- Prefer
case
over chainedif
-else
-if
.
Formatting
- Use single blank lines to separate logically-separate blocks of commands.
- Lines should not exceed 120 characters.
- Xcode doesn't know how to indent shell scripts—don't trust its auto-indentation on shell scripts.
Documentation
- Use comments to explain what things do.
- Produce output to the console or log to show progress and make it easier to diagnose failure conditions.
Tips & Tricks
- explainshell.com
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
will (for the most part) get the directory of the currently-executing script underbash
.- Generally, executing a shell script (e.g.,
./myscript.sh
) runs its commands in a separate shell process; using the.
operator (orsource
underbash
) on a shell script (e.g.,source ./myscript.sh
or. ./myscript.sh
) runs its commands within the current shell. The latter can be useful for loading configuration-type variable definitions from an external file.