Coding style guidelines: Shell script
Inquisitor is a fairly complex system written in multiple languages, so all developers MUST use the same set of coding style standards to avoid unnecessary arguments and fights.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Standards compliance
We aim to be portable among POSIX shells and thus we SHOULD use only features specified in POSIX shell standard. The common way to check for standards compliance is:
- Usage of
/bin/dash– Debian Almquist Shell - Usage of following lints to check for bashisms and other unportable
constructions:
- checkbashisms.pl – available in:
- checkbashsisms package in ALT Linux
- devscripts package in Debian/Ubuntu
- checkbashisms.pl – available in:
General formatting style
We prefer K&R-inspired general formatting style, with standard C-like exception for functions, as they can’t be nested in each other. This example gives an overview of general pattern:
some_function()
{
local i
local count=0
while [ $# -gt 0 ]; do
case "$1" in
opt1) OPT1=1 ;;
opt2) OPT2=1; count=$(($count + 1)) ;;
longopt)
do_something
and_something_more --with "$OPT2"
if [ "$OPT2" = 1 ]; then
do_something
and_something_more
else
OPT2=1
fi
;;
list)
for i in $LIST; do
echo $i
done
;;
esac
shift
done
}
Indentation
Indentation MUST be always 1 tab ("\t"). One can use whatever (s)he
likes to show tabs in his/her favorite text editor: 8 spaces per tab, 4
spaces, special symbols, proportional font, etc.
Indentation is a MUST for bodies of the following constructions on distinct lines:
- functions
ifwhilefor
Choices of case MUST NOT be indented, as shown in example above.
Line length
One SHOULD use lines of 72 characters long, but use common sense if in doubt. It’s common to use longer lines if it helps, not hurts readability.
Continuation of long lines MUST be indented with one extra level of indentation. starting with second line. One SHOULD use this technique, for example, to group all options of a long command line, one option per line of code:
./configure \
--with-libs=$LIBS \
--with-feature=$FEATURE \
--enable-debug \
--disable-optimizations
In case when it’s possible to omit \, one SHOULD omit it:
[ -n "$VAR1" ] ||
do_something --with=argument ||
last_possible_fallback
cat some-file |
grep something |
sed 's/word1/word2/' |
sort | uniq -c | sort -rn
Comments
All comments SHOULD be on the same level of indentation as the code
it refers to. All comments MUST start with # (i.e. hash symbol
and a space):
# Comment
One SHOULD NOT use inline comments. All comments that refer to a specific line should go above that line.
For comments that mark up logical sections of a program, one SHOULD use the following layout:
# ====================================================================== # Header text for the following section # ======================================================================
That is: line full of = up to maximum characters allowed, header text
describing following section, line full of =. Whole header SHOULD be
delimited with blank line before and after it.
Functions
Functions MUST be declared without optional function keyword:
monitoring_start()
{
...
}
Globally useful functions should be declared in
client/lib/main/functions or similar file. All these functions should
have a descriptive comment in ShellDoc
format.
Function names SHOULD be descriptive and SHOULD be generated as
class_method, if possible: monitoring_start, test_promise_time,
memory_amount, etc.
Functions SHOULD NOT be more than a screen (~24-30 lines) long and SHOULD NOT use more than 5-6 levels of indentation. If algorithm is very complex, try splitting it into more functions.
Functions SHOULD return:
- Boolean results – using exit status: 0 = true, 1 = false, other values
for different failure codes. Such check functions SHOULD be named with
prefix like
is_orhas_that clearly depicts that it’s a check.
- All other results (strings, numbers, etc) – using stdout. Such functions should be executed using command substitution syntax.
Variables
Local variables MUST be declared local in a function and
MUST be in lower_case. If a variable needs to be initialized,
initializion SHOULD be combined with declaration:
local count=0 local i
Global variables MUST be in UPPER_CASE. No extra declarations
required. One SHOULD use global variables, unless dealing with library
functions that SHOULD avoid messing up global variables and thus
SHOULD use local variables.
When accessing the variables, one SHOULD NOT use extra braces around
variable name (${VAR}) if it’s possible to do so: $VAR. One SHOULD
use double quotes " to access the variables that could contains spaces
(especially file/path names):
cp "$SRC" "$DEST"
Conditions
All test invocations MUST use [ expression ] syntax, not test
call:
[ "$answer" = 42 ] || echo fail
if [ "$answer" != 42 ]; then
echo fail
exit 1
fi
For “and” and “or” logic operators, one SHOULD use shell operators
(i.e. && and ||), and not test operations (-a and -o):
[ "$answer" = 42 ] && [ -z "$question" ]
Note that most of our scripts are executed with -e option (exit
immediately after command fail)
Arithmetic expansion
One MUST use $((...)) for arithmetic expansion, and one MUST
reference variables inside this block as $VAR:
x=$(($x + 2))
One MUST delimit all arithmetic operators with spaces before and after an operator.
One SHOULD be aware of limitation of shell arithmetic, in particular that it can operate only on integers and on some platforms these integers are only 32-bit signed int: so, for example, it’s a bad idea to use shell arithmetics to make calculations with values like number of bytes on a modern HDD, etc.
Command substitution
One SHOULD use `command` syntax for command substitution. Usage of
$(command) syntax is permitted for nested command substitutions, but
generally, nested command substitutions are discouraged: one SHOULD
use a function, variable or temporary file in this case.
Pipelining
One MUST use the following rules for pipelining commands and filenames:
|pipe character is delimited with spaces both before and after it:
grep something from-file | sed 's/1/2/' | sort -u | wc -l
<,>and2>redirection operators must be delimited with space before, but not after:
sed 's/1/2/' <input >output 2>errors
Complex substitutions
Complex string substitutions, such as:
${parameter%word}${parameter%%word}${parameter#word}${parameter##word}${#parameter}
SHOULD NOT be used – one SHOULD use calls to external tools, such as
sed, cut, wc or scripting languages.
Sourcing
Sourcing (inclusion of another script file to run in current shell
interpreter) MUST be done using . builtin, not by source.
Complex algorithms
Shell scripting is fairly limited, so it’s generally agreed upon that it’s best to use shell what it’s designed for: to call other programs and track the results. If solving a particular task with shell script seem to be too awkward, one SHOULD use other scripting languages.
Inquisitor-specific style notes
See additional style guidelines, specific for various kinds of Inquisitor scripts:
Links to other shell script coding style guidelines
- http://lug.fh-swf.de/vim/vim-bash/StyleGuideShell.en.pdf
- http://www.shelldorado.com/goodcoding/
- http://hub.opensolaris.org/bin/view/Community+Group+on/shellstyle


