-
Notifications
You must be signed in to change notification settings - Fork 14
Shell Code Style and Considerations
Bash is the only shell scripting language permitted.
Indent 4 spaces. No tabs.
Use blank lines between blocks to improve readability. Indentation is four spaces. Whatever you do, don't use tabs. For existing files, stay faithful to the existing indentation.
The preferable line length is 80 characters, but lines up to 120 characters is acceptable.
Lowercase, with underscores to separate words and terminated in '.sh' (optional for executable scripts). A executable must start with #!/usr/bin/env bash
followed by Intel's copyright notice, all other must start with Intel's copyright notice.
Example:
#!/usr/bin/env bash
# Copyright (C) 2019 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
Comment tricky, non-obvious, interesting or important parts of your code.
The open parenthesis is always on the same line as the function name. The closing parenthesis is always on a separate line.
For example:
my_func() { % OK
...
}
my_func2()
{ % NOT OK
...
}
Put ; do
and ; then
on the same line as the while
, for
or if
.
Examples:
if something ; then
do_stuff
fi
if something ; then
do_stuff
elif something_else ; then
do_other_stuff
elif full_moon ; then
howl
else
turn_into_a_newt
fi
for myvar in "the first" "the second" "and the third" ; do
einfo "This is ${myvar}"
done
for (( i = 1 ; i <= 10 ; i++ )) ; do
einfo "i is ${i}"
done
while hungry ; do
eat_cookies
done
Indent alternatives by 4 spaces and ;;
on a separate line.
Example:
case "$var" in
up | down)
;;
left | right)
do_something
;;
debug)
debug_something
;;
*)
exit 1
;;
esac
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.
For example:
# Single function
my_func() {
...
}
# Part of a package
mypackage::my_func() {
...
}
All caps, separated with underscores, declared at the top of the file. Constants and anything exported to the environment should be capitalized.
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr ORACLE_SID='PROD'
Some things become constant at their first setting (for example, via getopts
). Thus, it's OK to set a constant in getopts
or based on a condition, but it should be made readonly
immediately afterwards. Note that declare
doesn't operate on global variables within functions, so readonly
or export
is recommended instead.
VERBOSE='false'
while getopts 'v' flag; do
case "${flag}" in
v)
VERBOSE='true'
;;
esac
done
readonly VERBOSE
Use readonly
or declare -r
to ensure they're read only.
As globals are widely used in shell, it's important to catch errors when working with them. When you declare a variable that is meant to be read-only, make this explicit.
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
error_message
else
readonly zip_version
fi
Declare function-specific variables with local
. This avoids polluting the global name space and inadvertently setting variables that may have significance outside the function.
Declaration and assignment must be separate statements when the assignment value is provided by a command substitution, as the local
builtin does not propagate the exit code from the command substitution.
my_func2() {
local name="$1"
# Separate lines for declaration and assignment:
local my_var
my_var="$(my_func)" || return
# DO NOT do this: $? contains the exit code of 'local', not my_func
local my_var="$(my_func)"
[[ $? -eq 0 ]] || return
...
}
Use $(command)
instead of backticks.
Nested backticks require escaping the inner ones with \
. The $(command)
format doesn't change when nested and is easier to read.
Example:
# This is preferred:
var="$(command "$(command1)")"
# This is not:
var="`command \`command1\``"
Important: The [[ ]]
form is generally safer than [ ] and should be used in all new code.
This is because [[ ]]
is a bash syntax construct, whereas [ ] is a program which happens to be implemented as an internal -- as such, cleaner syntax is possible with the former. For a simple illustration, consider:
bash$ [ -n $foo ] && [ -z $foo ] && echo "huh?"
huh?
bash$ [[ -n $foo ]] && [[ -z $foo ]] && echo "huh?"
bash$
However, if you need either path name expansion or word splitting, then [
is required, but be aware of what you are doing to avoid classical mistakes.
Example:
# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
echo "Match"
fi
# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
echo "Match"
fi
Use quotes rather than filler characters where possible.
Bash is smart enough to deal with an empty string in a test. So, given that the code is much easier to read, use tests for empty/non-empty strings or empty strings rather than filler characters.
# Do this:
if [[ "${my_var}" == "some_string" ]]; then
do_something
fi
# -z (string length is zero) and -n (string length is not zero) are
# preferred over testing for an empty string
if [[ -z "${my_var}" ]]; then
do_something
fi
# This is OK (ensure quotes on the empty side), but not preferred:
if [[ "${my_var}" == "" ]]; then
do_something
fi
# Not this:
if [[ "${my_var}X" == "some_stringX" ]]; then
do_something
fi
To avoid confusion about what you're testing for, explicitly use -z
or -n
.
# Use this
if [[ -n "${my_var}" ]]; then
do_something
fi
# Instead of this as errors can occur if ${my_var} expands to a test
# flag
if [[ "${my_var}" ]]; then
do_something
fi
Pipelines should be split one per line if they don't all fit on one line.
If a pipeline all fits on one line, it should be on one line.
If not, it should be split at one pipe segment per line with the pipe on the newline and a 4 space indent for the next section of the pipe. This applies to a chain of commands combined using |
as well as to logical compounds using ||
and &&
.
Example:
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
SUID and SGID are forbidden on shell scripts.
There are too many security issues with shell that make it nearly impossible to secure sufficiently to allow SUID/SGID. While bash does make it difficult to run SUID, it's still possible on some platforms which is why we're being explicit about banning it.
Use sudo to provide elevated access if you need it.
Use common sense and BE CONSISTENT.
*Other names and brands may be claimed as the property of others.