diff options
Diffstat (limited to 'doc/bash-style.cli')
-rw-r--r-- | doc/bash-style.cli | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/doc/bash-style.cli b/doc/bash-style.cli new file mode 100644 index 0000000..67f9da2 --- /dev/null +++ b/doc/bash-style.cli @@ -0,0 +1,314 @@ +// file : doc/bash-style.cli +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +"\title=Bash Style Guide" + +// NOTES +// +// - Maximum <pre> line is 70 characters. +// + +"\h1|Contents|" +"\$TOC$" + +" +\h1#intro|Introduction| + +Bash works best for simple tasks. Needing arrays, arithmetic, and so on, is +usually a good indication that the task at hand is too complex for Bash. + +Most of the below rules can be broken if there is a good reason for it. +Besides making things consistent, rules free you from having to stop and think +every time you encounter a particular situation. But if it feels that the +prescribed way is clearly wrong, then it probably makes sense to break it. +You just need to be clear on why you are doing it. + +See also \l{https://google.github.io/styleguide/shell.xml Google's Bash Style +Guide}; we agree with quite a few (but not all) items in there. In particular, +it provides a lot more rationale compared to this guide. + +\h1#style|Style| + +Don't use any extensions for your scripts. That is, call it just \c{foo} +rather than \c{foo.sh} or \c{foo.bash}. Use lower-case letters and dash +to separate words, for example \c{foo-bar}. + +Indentation is two spaces (not tabs). Maximum line length is 79 characters +(excluding newline). Use blank lines between logical blocks to improve +readability. + +Variable and function names should use lower-case letters with underscores +separating words. + +For \c{if}/\c{while} and \c{for}/\c{do} the corresponding \c{then} or \c{do} +is written on the same line after a semicolon, for example: + +\ +if [ ... ]; then +fi + +for x in ...; do +done +\ + +For \c{if} use \c{[ ]} for basic tests and \c{[[ ]]} only if the previous form +is not sufficient. Never use \c{test}. Do use \c{elif}. + +\h1#struct|Structure| + +The overall structure of the script should be as follows: + +\ +#! /usr/bin/env bash + +# <SUMMARY> +# +# [<FUNCTIONALITY-DESCRIPTION>] +# +# [<OPTIONS-DESCRIPTION>] +# +usage=\"usage: $0 <OPTIONS>\" + +owd=\"$(pwd)\" +trap \"{ cd '$owd'; exit 1; }\" ERR +set -o errtrace # Trap in functions. + +function info () { echo \"$*\" 1>&2; } +function error () { info \"$*\"; exit 1; } + +[<OPTIONS-ARGUMENTS-DEFAULTS>] + +[<OPTIONS-ARGUMENTS-PARSING>] + +[<OPTIONS-ARGUMENTS-VALIDATION>] + +<FUNCTIONALITY> +\ + +\h#struct-summary|SUMMARY| + +One-two sentences describing what the script does. + +\h#struct-func-desc|FUNCTIONALITY-DESCRIPTION| + +More detailed functionality description for more complex scripts. + +\h#struct-opt-desc|OPTIONS-DESCRIPTION| + +Description of command line options. For example: + +\ +# -q +# Run quiet. +# +# -t <dir> +# Specify the alternative toolchain installation directory. +\ + +\h#struct-opt|OPTIONS| + +Command line options summary. For example: + +\ +usage=\"usage: $0 [-q] [-t <dir>] <file>\" +\ + +\h#struct-opt-arg-default|OPTIONS-ARGUMENTS-DEFAULTS| + +Set defaults to variables that will contain option/argument values. For +example: + +\ +quiet=\"n\" +tools=\"/usr/local\" +file= +\ + +\h#struct-opt-arg-parse|OPTIONS-ARGUMENTS-PARSING| + +Parse the command line options/arguments. For example: + +\ +while [ \"$#\" -gt 0 ]; do + case \"$1\" in + -q) + quiet=\"y\" + shift + ;; + -t) + shift + tools=\"${1%/}\" + shift + ;; + *) + if [ -n \"$1\" ]; then + error \"$usage\" + fi + + file=\"$1\" + shift + ;; + esac +done +\ + +\h#struct-opt-arg-valid|OPTIONS-ARGUMENTS-VALIDATION| + +Validate option/argument values. For example: + +\ +if [ -z \"$file\" ]; then + error \"$usage\" +fi + +if [ ! -d \"$file\" ]; then + fail \"'$file' does not exist or is not a directory\" +fi +\ + +\h#struct-func|FUNCTIONALITY| + +Implement script logic. For diagnostics use the \c{info()} and \c{error()} +functions defined above (so that it goes to stderr, not stdout). If using +functions, then define them just before use. + +\h1#quote|Quoting| + +We quote every variable expansion, no exceptions. For example: + +\ +if [ -n \"$foo\" ]; then + ... +fi +\ + +We also quote every variable assignment: + +\ +quiet=\"y\" +\ + +This also applies to command substitution (which we always write as +\c{$(foo arg)} rather than \c{`foo arg`}), for example: + +\ +list=\"$(cat foo)\" +\ + +Note that a command substitution creates a new quoting context, for example: + +\ +list=\"$(basename \"$1\")\" +\ + +Note that quoting will inhibit globbing so you may end up with expansions +along these lines: + +\ +rm -f \"$dir/$name\".* +\ + +If you have multiple values (e.g., program arguments) that may contain spaces, +don't try to handle them with quoting and use arrays instead. Here is a +typical example of a space-aware argument handling: + +\ +files=() + +while [ \"$#\" -gt 0 ]; do + case \"$1\" in + + ... + + *) + shift + files=(\"${files[@]}\" \"$1\") + shift + ;; + esac +done + +rm -f \"${files[@]}\" +\ + +In the same vein, never write: + +\ +cmd $* +\ + +Instead always write: + +\ +cmd \"$@\" +\ + +\h1#trap|Trap| + +Our scripts use the error trap to automatically terminate the script in case +any command fails. If you need to check the exit status of a command, use +\c{if}, for example: + +\ +if grep \"foo\" \"bar\"; then + info \"found\" +fi + +if ! grep \"foo\" \"bar\"; then + info \"not found\" +fi +\ + +If you need to ignore the exit status, you can use \c{|| true}, for example: + +\ +foo || true +\ + +\h1#function|Functions| + +If a function takes arguments, provide a brief usage after the function +header, for example: + +\ +function dist() # <pkg> <dir> +{ + ... +} +\ + +For non-trivial/obvious functions also provide a short description of its +functionality/purpose, for example: + +\ +# Prepare a distribution of the specified packages and place it into the +# specified directory. +# +function dist() # <pkg> <dir> +{ + ... +} +\ + +Inside functions use local variables, for example: + +\ +function dist() +{ + local x=\"foo\" +} +\ + +If the evaluation of the value may fail (e.g., it contains a program +substitution), then place the assignment on a separate line since \c{local} +will ignore the error. For example + +\ +function dist() +{ + local b + b=\"$(basename \"$2\")\" +} +\ +" |