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 1to 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.
ifstatements:- Put
thenon 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, andfion their own lines. - Indent the bodies of the
thenandelseclauses.
- Put
casestatements:case-inon 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-fistatements.
- 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
shtechniques 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
exitis 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
caseover 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 (orsourceunderbash) on a shell script (e.g.,source ./myscript.shor. ./myscript.sh) runs its commands within the current shell. The latter can be useful for loading configuration-type variable definitions from an external file.