summaryrefslogtreecommitdiff
path: root/doc/bash-style.cli
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-10-17 17:19:59 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-10-17 17:19:59 +0200
commite44edfb832e2491cd17edaa3dc8dc7b9e9265356 (patch)
tree8a5b5e14cb0c3544dab5567ff62d84802c005c79 /doc/bash-style.cli
parenta1fdc495e0bf1e89c799460e64c8571b9710e5b3 (diff)
Add bash style guide
Diffstat (limited to 'doc/bash-style.cli')
-rw-r--r--doc/bash-style.cli314
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\")\"
+}
+\
+"