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
Indent 2 spaces. No tabs.
Use blank lines between blocks to improve readability. Indentation is two spaces. Whatever you do, don't use tabs. For existing files, stay faithful to the existing indentation.
Line Length and Long Strings
Maximum line length is 80 characters.
If you have to write strings that are longer than 80 characters, this should be done with a here document or an embedded newline if possible. Literal strings that have to be longer than 80 chars and can't sensibly be split are ok, but it's strongly preferred to find a way to make it shorter.
Pipelines
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 2 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 &&
.
Loops
Put ; do
and ; then
on the same line as the
while
, for
or if
.
Loops in shell are a bit different, but we follow the same principles
as with braces when declaring functions. That is: ; then
and ; do
should be on the same line as the if/for/while.
else
should be on its own line and closing statements
should be on their own line vertically aligned with the opening
statement.
Example:
# If inside a function, consider declaring the loop variable as
# a local to avoid it leaking into the global environment:
# local dir
for dir in "${dirs_to_cleanup[@]}"; do
if [[ -d "${dir}/${ORACLE_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
rm "${dir}/${ORACLE_SID}/"*
if (( $? != 0 )); then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if (( $? != 0 )); then
error_message
fi
fi
done
Case statement
- 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.
The matching expressions are indented one level from the case
and esac
.
Multiline actions are indented another level. In general, there is no need to
quote match expressions. Pattern expressions should not be preceded by an open
parenthesis. Avoid the ;&
and ;;&
notations.
Simple commands may be put on the same line as the pattern and
;;
as long as the expression remains readable. This is
often appropriate for single-letter option processing. When the
actions don't fit on a single line, put the pattern on a line on its
own, then the actions, then ;;
also on a line of its own.
When on the same line as the actions, use a space after the close
parenthesis of the pattern and another before the ;;
.
Variable expansion
In order of precedence: Stay consistent with what you find; quote your
variables; prefer "${var}"
over "$var"
.
These are strongly recommended guidelines but not mandatory regulation. Nonetheless, the fact that it's a recommendation and not mandatory doesn't mean it should be taken lightly or downplayed.
They are listed in order of precedence.
-
Stay consistent with what you find for existing code.
-
Quote variables, see Quoting section below.
-
Don't brace-delimit single character shell specials / positional parameters, unless strictly necessary or avoiding deep confusion.
Prefer brace-delimiting all other variables.
# Section of *recommended* cases.
# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ …"
# Braces necessary:
echo "many parameters: ${10}"
# Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"
# Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read -r f; do
echo "file=${f}"
done < <(find /tmp)
NOTE: Using braces in ${var}
is not a form of quoting. "Double quotes" must
be used as well.
Quoting
- Always quote strings containing variables, command substitutions, spaces or shell meta characters, unless careful unquoted expansion is required or it's a shell-internal integer (see next point).
- Use arrays for safe quoting of lists of elements, especially command-line flags. See Arrays below.
- Optionally quote shell-internal, readonly special variables that are defined
to be integers:
$?
,$#
,$$
,$!
(man bash). Prefer quoting of "named" internal integer variables, e.g. PPID etc for consistency. - 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
[[ … ]]
. See the Test,[ … ]
, and[[ … ]]
section below. - Use
"$@"
unless you have a specific reason to use$*
, such as simply appending the arguments to a string in a message or log.
# 'Single' quotes indicate that no substitution is desired.
# "Double" quotes indicate that substitution is required/tolerated.
# Simple examples
# "quote command substitutions"
# Note that quotes nested inside "$()" don't need escaping.
flag="$(some_command and its args "$@" 'quoted separately')"
# "quote variables"
echo "${flag}"
# Use arrays with quoted expansion for lists.
declare -a FLAGS
FLAGS=( --foo --bar='baz' )
readonly FLAGS
mybinary "${FLAGS[@]}"
# It's ok to not quote internal integer variables.
if (( $# > 3 )); then
echo "ppid=${PPID}"
fi
# "never quote literal integers"
value=32
# "quote command substitutions", even when you expect integers
number="$(generate_number)"
# "prefer quoting words", not compulsory
readonly USE_INTEGER='true'
# "quote shell meta characters"
echo 'Hello stranger, and well met. Earn lots of $$$'
echo "Process $$: Done making \$\$\$."
# "command options or path names"
# ($1 is assumed to contain a value here)
grep -li Hugo /dev/null "$1"
# Less simple examples
# "quote variables, unless proven false": ccs might be empty
git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}
# Positional parameter precautions: $1 might be unset
# Single quotes leave regex as-is.
grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"}
# For passing on arguments,
# "$@" is right almost every time, and
# $* is wrong almost every time:
#
# * $* and $@ will split on spaces, clobbering up arguments
# that contain spaces and dropping empty strings;
# * "$@" will retain arguments as-is, so no args
# provided will result in no args being passed on;
# This is in most cases what you want to use for passing
# on arguments.
# * "$*" expands to one argument, with all args joined
# by (usually) spaces,
# so no args provided will result in one empty string
# being passed on.
# (Consult `man bash` for the nit-grits ;-)
(set -- 1 "2 two" "3 three tres"; echo $#; set -- "$*"; echo "$#, $@")
(set -- 1 "2 two" "3 three tres"; echo $#; set -- "$@"; echo "$#, $@")