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