Function name
Lowercase letters, underscores:
# Single function my_func() { ... } # Part of a package mypackage::my_func() { ... }
variable name
Lowercase letters, underscores, loop variables:
for zone in ${zones}; do something_with "${zone}" done
read-only variable
Use readonly or declare -r statement:
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi
Constants and Environment Variables
uppercase letter:
# Constant readonly PATH_TO_FILES='/some/path' # Both constant and environment declare -xr ORACLE_SID='PROD' VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE
Use local variables
local declaration:
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 ... }
source file name
Lowercase letters, underscores:
make_template.sh
print error message
err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2 } if ! do_something; then err "Unable to do_something" exit "${E_DID_NOTHING}" fi
Basic Statement Format
Indent four spaces, or a tab, and a tab is set to four spaces.
Lines can be up to 80 characters long, use \ to wrap.
Pipes: If the entire pipeline operation can fit on one line, write the entire pipeline operation on the same line. Otherwise, the entire pipeline operation should be split into one segment per line, and the next part of the pipeline operation should place the pipe character on a new line and indent by 2 spaces. This applies to the combined command chain of the use pipe character '|' and the logical operation chain using '|' and '& &'
# All fits on one line command1 | command2 # Long commands command1 \ | command2 \ | command3 \ | command4
for loop
Please put ;do, ;then and while, for, if on the same line.
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 [[ "$?" -ne 0 ]]; then error_message fi else mkdir -p "${dir}/${ORACLE_SID}" if [[ "$?" -ne 0 ]]; then error_message fi fi done
case statement
case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac
verbose='false' aflag='' bflag='' files='' while getopts 'abf:v' flag; do case "${flag}" in a) aflag='true' ;; b) bflag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) error "Unexpected option ${flag}" ;; esac done
variable expansion
Use \({var} instead of \)var , as detailed below:
- Be consistent with what you find in existing code.
- Reference variables See the next section, References.
- Do not enclose single-character shell special or positional variables in curly braces unless absolutely necessary or to avoid deep confusion. It is recommended to enclose all other variables in curly brackets.
quote
set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@" set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@"
command substitution
use $(command) instead of backticks`command`.
test, [ and [[
[[ ... ]] is recommended instead of [ , test , and /usr/bin/[ .
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi if [[ "filename" == "f*" ]]; then echo "Match" fi
test string
if [[ "${my_var}" = "some_string" ]]; then do_something fi if [[ -n "${my_var}" ]]; then do_something fi # true if string is not empty if [[ -z "${my_var}" ]]; then do_something fi # true when string is empty if [[ "${my_var}" = "" ]]; then do_something fi # true when string is empty
Wildcard expansion of filenames
Since filenames may start with - , it is much safer to use the extended wildcard ./* than * .
rm -v ./* # it is good rm -v * # not good
pipe-directed while loop
Pipes lead to implicit sub shell s in while loops that make tracking down bug s difficult.
last_line='NULL' your_command | while read line; do last_line="${line}" done echo "${last_line}"
If you are sure that the input does not contain spaces or special symbols (usually meaning that it was not entered by the user), then you can use a for loop.
total=0 for value in $(command); do total+="${value}" done
Using procedure substitution allows redirecting output, but put the command into an explicit subshell instead of the implicit subshell that bash creates for while loops.
total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c) echo "Total = ${total}" echo "Last one = ${last_file}"
A while loop can be used when there is no need to pass complex results to the parent shell. This usually requires some more complex "parsing". Note that simple examples may be easier to accomplish using tools such as awk. This may also be useful when you specifically don't want to change the parent shell's scope variables.
cat /proc/mounts | while read src dest type opts rest; do if [[ ${type} == "nfs" ]]; then echo "NFS ${dest} maps to ${src}" fi done
Check the return value
mv "${file_list}" "${dest_dir}/" if [[ "$?" -ne 0 ]]; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi