Shell Style Guide

Revision 1.26

Paul Armstrong
Too many more to mention

Each style point has a summary for which additional information is available by toggling the accompanying arrow button that looks this way: . You may toggle all summaries with the big arrow button:

Toggle all summaries
Table of Contents

Background

Which Shell to Use

link
Bash is the only shell scripting language permitted for executables.

When to use Shell

link
Shell should only be used for small utilities or simple wrapper scripts.

Shell Files and Interpreter Invocation

File Extensions

Executables should have no extension (strongly preferred) or a .sh extension. Libraries must have a .sh extension and should not be executable.

SUID/SGID

link
SUID and SGID are forbidden on shell scripts.

Environment

STDOUT vs STDERR

link
All error messages should go to STDERR.

Comments

File Header

link
Start each file with a description of its contents.

Function Comments

link
Any function that is not both obvious and short must be commented. Any function in a library must be commented regardless of length or complexity.

Implementation Comments

link
Comment tricky, non-obvious, interesting or important parts of your code.

TODO Comments

link
Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.

Formatting

While you should follow the style that's already there for files that you're modifying, the following are required for any new code.

Indentation

link
Indent 2 spaces. No tabs.

Line Length and Long Strings

Maximum line length is 80 characters.

Pipelines

Pipelines should be split one per line if they don't all fit on one line.

Loops

link
Put ; do and ; then on the same line as the while, for or if.

Case statement

link
  • Indent alternatives by 2 spaces.
  • A one-line alternative needs a space after the close parenthesis of the pattern and before the ;;.
  • Long or multi-command alternatives should be split over multiple lines with the pattern, actions, and ;; on separate lines.

Variable expansion

link
In order of precedence: Stay consistent with what you find; quote your variables; prefer "${var}" over "$var", but see details.

Quoting

link
  • Always quote strings containing variables, command substitutions, spaces or shell meta characters, unless careful unquoted expansion is required.
  • Prefer quoting strings that are "words" (as opposed to command options or path names).
  • Never quote literal integers.
  • Be aware of the quoting rules for pattern matches in [[.
  • Use "$@" unless you have a specific reason to use $*.

Features and Bugs

Command Substitution

link
Use $(command) instead of backticks.

Test, [ and [[

link
[[ ... ]] is preferred over [, test and /usr/bin/[.

Testing Strings

link
Use quotes rather than filler characters where possible.

Wildcard Expansion of Filenames

link
Use an explicit path when doing wildcard expansion of filenames.

Eval

link
eval should be avoided.

Pipes to While

link
Use process substitution or for loops in preference to piping to while. Variables modified in a while loop do not propagate to the parent because the loop's commands run in a subshell.

The implicit subshell in a pipe to while can make it difficult to track down bugs.

last_line='NULL'
your_command | while read line; do
  last_line="${line}"
done

# This will output 'NULL'
echo "${last_line}"

Use a for loop if you are confident that the input will not contain spaces or special characters (usually, this means not user input).

total=0
# Only do this if there are no spaces in return values.
for value in $(command); do
  total+="${value}"
done

Using process substitution allows redirecting output but puts the commands in an explicit subshell rather than the implicit subshell that bash creates for the while loop.

total=0
last_file=
while read count filename; do
  total+="${count}"
  last_file="${filename}"
done < <(your_command | uniq -c)

# This will output the second field of the last line of output from
# the command.
echo "Total = ${total}"
echo "Last one = ${last_file}"

Use while loops where it is not necessary to pass complex results to the parent shell - this is typically where some more complex "parsing" is required. Beware that simple examples are probably more easily done with a tool such as awk. This may also be useful where you specifically don't want to change the parent scope variables.

# Trivial implementation of awk expression:
#   awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts
cat /proc/mounts | while read src dest type opts rest; do
  if [[ ${type} == "nfs" ]]; then
    echo "NFS ${dest} maps to ${src}"
  fi
done

Naming Conventions

Function Names

link
Lower-case, with underscores to separate words. Separate libraries with ::. Parentheses are required after the function name. The keyword function is optional, but must be used consistently throughout a project.

Variable Names

link
As for function names.

Constants and Environment Variable Names

link
All caps, separated with underscores, declared at the top of the file.

Source Filenames

link
Lowercase, with underscores to separate words if desired.

Read-only Variables

link
Use readonly or declare -r to ensure they're read only.

Use Local Variables

link
Declare function-specific variables with local. Declaration and assignment should be on different lines.

Function Location

link
Put all functions together in the file just below constants. Don't hide executable code between functions.

main

link
A function called main is required for scripts long enough to contain at least one other function.

Calling Commands

Checking Return Values

link
Always check return values and give informative return values.

Builtin Commands vs. External Commands

link
Given the choice between invoking a shell builtin and invoking a separate process, choose the builtin.

Conclusion

Use common sense and BE CONSISTENT.

Please take a few minutes to read the Parting Words section at the bottom of the C++ Guide.

Revision 1.26