1153 lines
35 KiB
XML
1153 lines
35 KiB
XML
<?xml version="1.0"?>
|
|
<?xml-stylesheet type="text/xsl" href="styleguide.xsl"?>
|
|
<GUIDE title="Shell Style Guide">
|
|
|
|
<p align="right">
|
|
|
|
Revision 1.26
|
|
</p>
|
|
|
|
|
|
<address>
|
|
Paul Armstrong<br/>
|
|
Too many more to mention<br/>
|
|
</address>
|
|
|
|
<OVERVIEW>
|
|
|
|
<CATEGORY title="Background">
|
|
|
|
|
|
|
|
<STYLEPOINT title="Which Shell to Use">
|
|
<SUMMARY>
|
|
<code>Bash</code> is the only shell scripting language permitted for
|
|
executables.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Executables must start with <code>#!/bin/bash</code> and a minimum
|
|
number of flags. Use <code>set</code> to set shell options so that
|
|
calling your script as <code>bash <i><script_name></i></code>
|
|
does not break its functionality.
|
|
</p>
|
|
<p>
|
|
Restricting all executable shell scripts to <b>bash</b>
|
|
gives us a consistent shell language that's installed on all our
|
|
machines.
|
|
</p>
|
|
<p>
|
|
The only exception to this is where you're forced to by whatever
|
|
you're coding for. One example of this is Solaris SVR4 packages which
|
|
require plain Bourne shell for any scripts.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="When to use Shell">
|
|
<SUMMARY>
|
|
Shell should only be used for small utilities or simple wrapper
|
|
scripts.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
While shell scripting isn't a development language, it is used for
|
|
writing various utility scripts throughout Google. This
|
|
style guide is more a recognition of its use rather than
|
|
a suggestion that it be used for widespread deployment.
|
|
</p>
|
|
<p>
|
|
Some guidelines:
|
|
<ul>
|
|
<li>
|
|
If you're mostly calling other utilities and are doing relatively
|
|
little data manipulation, shell is an acceptable choice for the
|
|
task.
|
|
</li>
|
|
<li>
|
|
If performance matters, use something other than shell.
|
|
</li>
|
|
<li>
|
|
If you find you need to use arrays for anything more than
|
|
assignment of <code>${PIPESTATUS}</code>, you should use Python.
|
|
</li>
|
|
<li>
|
|
If you are writing a script that is more than 100 lines long, you
|
|
should probably be writing it in Python instead. Bear in mind
|
|
that scripts grow. Rewrite your script in another language
|
|
early to avoid a time-consuming rewrite at a later date.
|
|
</li>
|
|
</ul>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
</CATEGORY>
|
|
|
|
</OVERVIEW>
|
|
|
|
<CATEGORY title="Shell Files and Interpreter Invocation">
|
|
|
|
<STYLEPOINT title="File Extensions">
|
|
<SUMMARY>
|
|
Executables should have no extension (strongly preferred) or a
|
|
<code>.sh</code> extension.
|
|
Libraries must have a <code>.sh</code> extension and should not be
|
|
executable.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
It is not necessary to know what language a program is written in when
|
|
executing it and shell doesn't require an extension so we prefer not to
|
|
use one for executables.
|
|
</p>
|
|
<p>
|
|
However, for libraries it's important to know what language it is and
|
|
sometimes there's a need to have similar libraries in different
|
|
languages. This allows library files with identical purposes but
|
|
different languages to be identically named except for the
|
|
language-specific suffix.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="SUID/SGID">
|
|
<SUMMARY>
|
|
SUID and SGID are <em>forbidden</em> on shell scripts.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
Use <code>sudo</code> to provide elevated access if you need it.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Environment">
|
|
|
|
<STYLEPOINT title="STDOUT vs STDERR">
|
|
<SUMMARY>
|
|
All error messages should go to <code>STDERR</code>.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
This makes it easier
|
|
to separate normal status from actual issues.
|
|
</p>
|
|
<p>
|
|
A function to print out error messages along with other status
|
|
information is recommended.
|
|
<CODE_SNIPPET>
|
|
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
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Comments">
|
|
|
|
<STYLEPOINT title="File Header">
|
|
<SUMMARY>
|
|
Start each file with a description of its contents.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Every file must have a top-level comment including a brief overview of
|
|
its contents.
|
|
A
|
|
copyright notice
|
|
and author information are optional.
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
#!/bin/bash
|
|
#
|
|
# Perform hot backups of Oracle databases.
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
|
|
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Function Comments">
|
|
<SUMMARY>
|
|
Any function that is not both obvious and short must be commented. Any
|
|
function in a library must be commented regardless of length or
|
|
complexity.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
It should be possible for someone else to learn how to use your
|
|
program or to use a function in your library by reading the comments
|
|
(and self-help, if provided) without reading the code.
|
|
</p>
|
|
<p>
|
|
All function comments should contain:
|
|
<ul>
|
|
<li>
|
|
Description of the function
|
|
</li>
|
|
<li>
|
|
Global variables used and modified
|
|
</li>
|
|
<li>
|
|
Arguments taken
|
|
</li>
|
|
<li>
|
|
Returned values other than the default exit status of the last
|
|
command run
|
|
</li>
|
|
</ul>
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
#!/bin/bash
|
|
#
|
|
# Perform hot backups of Oracle databases.
|
|
|
|
export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'
|
|
|
|
#######################################
|
|
# Cleanup files from the backup dir
|
|
# Globals:
|
|
# BACKUP_DIR
|
|
# ORACLE_SID
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# None
|
|
#######################################
|
|
cleanup() {
|
|
...
|
|
}
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Implementation Comments">
|
|
<SUMMARY>
|
|
Comment tricky, non-obvious, interesting or important parts of your code.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
This follows general Google coding comment practice. Don't comment
|
|
everything. If there's a complex algorithm or you're doing something
|
|
out of the ordinary, put a short comment in.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="TODO Comments">
|
|
<SUMMARY>
|
|
Use TODO comments for code that is temporary, a short-term solution, or
|
|
good-enough but not perfect.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
This matches the convention in the <a href="cppguide.html?showone=TODO_Comments#TODO_Comments">C++
|
|
Guide</a>.
|
|
</p>
|
|
<p>
|
|
TODOs should include the string TODO in all caps, followed by your
|
|
username in parentheses. A colon is optional. It's preferable to put a
|
|
bug/ticket number next to the TODO item as well.
|
|
</p>
|
|
<p>
|
|
Examples:
|
|
|
|
<CODE_SNIPPET>
|
|
# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Formatting">
|
|
<p>
|
|
While you should follow the style that's already there for files that
|
|
you're modifying, the following are required for any new code.
|
|
</p>
|
|
|
|
<STYLEPOINT title="Indentation">
|
|
<SUMMARY>
|
|
Indent 2 spaces. No tabs.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Line Length and Long Strings">
|
|
<SUMMARY>
|
|
Maximum line length is 80 characters.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<p>
|
|
<CODE_SNIPPET>
|
|
# DO use 'here document's
|
|
cat <<END;
|
|
I am an exceptionally long
|
|
string.
|
|
END
|
|
|
|
# Embedded newlines are ok too
|
|
long_string="I am an exceptionally
|
|
long string."
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Pipelines">
|
|
<SUMMARY>
|
|
Pipelines should be split one per line if they don't all fit on one line.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
If a pipeline all fits on one line, it should be on one line.
|
|
</p>
|
|
<p>
|
|
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 '&&'.
|
|
<CODE_SNIPPET>
|
|
# All fits on one line
|
|
command1 | command2
|
|
|
|
# Long commands
|
|
command1 \
|
|
| command2 \
|
|
| command3 \
|
|
| command4
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Loops">
|
|
<SUMMARY>
|
|
Put <code>; do</code> and <code>; then</code> on the same line as the
|
|
<code>while</code>, <code>for</code> or <code>if</code>.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Loops in shell are a bit different, but we follow the same principles
|
|
as with braces when declaring functions. That is: <code>; then</code>
|
|
and <code>; do</code> should be on the same line as the if/for/while.
|
|
<code>else</code> should be on its own line and closing statements
|
|
should be on their own line vertically aligned with the opening
|
|
statement.
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
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
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Case statement">
|
|
<SUMMARY>
|
|
<ul>
|
|
<li>
|
|
Indent alternatives by 2 spaces.
|
|
</li>
|
|
<li>
|
|
A one-line alternative needs a space after the close parenthesis of
|
|
the pattern and before the <code>;;</code>.
|
|
</li>
|
|
<li>
|
|
Long or multi-command alternatives should be split over multiple
|
|
lines with the pattern, actions, and <code>;;</code> on separate
|
|
lines.
|
|
</li>
|
|
</ul>
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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
|
|
<code>;&</code> and <code>;;&</code> notations.
|
|
</p>
|
|
<CODE_SNIPPET>
|
|
case "${expression}" in
|
|
a)
|
|
variable="..."
|
|
some_command "${variable}" "${other_expr}" ...
|
|
;;
|
|
absolute)
|
|
actions="relative"
|
|
another_command "${actions}" "${other_expr}" ...
|
|
;;
|
|
*)
|
|
error "Unexpected expression '${expression}'"
|
|
;;
|
|
esac
|
|
</CODE_SNIPPET>
|
|
<p>
|
|
Simple commands may be put on the same line as the pattern <i>and</i>
|
|
<code>;;</code> 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 <code>;;</code> 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 <code>;;</code>.
|
|
</p>
|
|
<CODE_SNIPPET>
|
|
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
|
|
</CODE_SNIPPET>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Variable expansion">
|
|
<SUMMARY>
|
|
In order of precedence: Stay consistent with what you find;
|
|
quote your variables;
|
|
prefer "${var}" over "$var", but see details.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
These are meant to be guidelines, as the topic seems too controversial for
|
|
a mandatory regulation.
|
|
<br/>
|
|
They are listed in order of precedence.
|
|
</p>
|
|
<ol>
|
|
<li>
|
|
Stay consistent with what you find for existing code.
|
|
</li>
|
|
<li>
|
|
Quote variables, see <a href="#Quoting">Quoting section below</a>.
|
|
</li>
|
|
<li>
|
|
<p>
|
|
Don't brace-quote single character shell
|
|
specials / positional parameters, unless strictly necessary
|
|
or avoiding deep confusion.
|
|
<br/>
|
|
Prefer brace-quoting all other variables.
|
|
<CODE_SNIPPET>
|
|
# Section of <em>recommended</em> 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 f; do
|
|
echo "file=${f}"
|
|
done < <(ls -l /tmp)
|
|
|
|
# Section of <em>discouraged</em> cases
|
|
|
|
# Unquoted vars, unbraced vars, brace-quoted single letter
|
|
# shell specials.
|
|
echo a=$avar "b=$bvar" "PID=${$}" "${1}"
|
|
|
|
# Confusing use: this is expanded as "${1}0${2}0${3}0",
|
|
# not "${10}${20}${30}
|
|
set -- a b c
|
|
echo "$10$20$30"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</li>
|
|
</ol>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Quoting">
|
|
<SUMMARY>
|
|
<ul>
|
|
<li>
|
|
Always quote strings containing variables, command substitutions,
|
|
spaces or shell meta characters, unless careful unquoted expansion
|
|
is required.
|
|
</li>
|
|
<li>
|
|
Prefer quoting strings that are "words"
|
|
(as opposed to command options or path names).
|
|
</li>
|
|
<li>
|
|
Never quote <em>literal</em> integers.
|
|
</li>
|
|
<li>
|
|
Be aware of the quoting rules for
|
|
<a href="#Test,_%5B_and_%5B%5B">pattern matches in [[</a>.
|
|
</li>
|
|
<li>
|
|
Use "$@" unless you have a specific reason to use $*.
|
|
</li>
|
|
</ul>
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
<CODE_SNIPPET>
|
|
# 'Single' quotes indicate that no substitution is desired.
|
|
# "Double" quotes indicate that substitution is required/tolerated.
|
|
|
|
# Simple examples
|
|
# "quote command substitutions"
|
|
flag="$(some_command and its args "$@" 'quoted separately')"
|
|
|
|
# "quote variables"
|
|
echo "${flag}"
|
|
|
|
# "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 everytime, and
|
|
# $* is wrong almost everytime:
|
|
#
|
|
# * $* 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 "$#, $@")
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Features and Bugs">
|
|
|
|
<STYLEPOINT title="Command Substitution">
|
|
<SUMMARY>
|
|
Use <code>$(command)</code> instead of backticks.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Nested backticks require escaping the inner ones with <code>\</code>.
|
|
The <code>$(command)</code> format doesn't change when nested and is
|
|
easier to read.
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
# This is preferred:
|
|
var="$(command "$(command1)")"
|
|
|
|
# This is not:
|
|
var="`command \`command1\``"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Test, [ and [[">
|
|
<SUMMARY>
|
|
<code>[[ ... ]]</code> is preferred over <code>[</code>,
|
|
<code>test</code> and <code>/usr/bin/[</code>.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
<code>[[ ... ]]</code> reduces errors as no pathname expansion or word
|
|
splitting takes place between <code>[[</code> and <code>]]</code> and
|
|
<code>[[ ... ]]</code> allows for regular expression matching where
|
|
<code>[ ... ]</code> does not.
|
|
<CODE_SNIPPET>
|
|
# This ensures the string on the left is made up of characters in the
|
|
# alnum character class followed by the string name.
|
|
# Note that the RHS should not be quoted here.
|
|
# For the gory details, see
|
|
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
|
|
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
|
|
echo "Match"
|
|
fi
|
|
|
|
# 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
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Testing Strings">
|
|
<SUMMARY>
|
|
Use quotes rather than filler characters where possible.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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.
|
|
<CODE_SNIPPET>
|
|
# 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
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
To avoid confusion about what you're testing for, explicitly use
|
|
<code>-z</code> or <code>-n</code>.
|
|
<CODE_SNIPPET>
|
|
# 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
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Wildcard Expansion of Filenames">
|
|
<SUMMARY>
|
|
Use an explicit path when doing wildcard expansion of filenames.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
As filenames can begin with a <code>-</code>, it's a lot safer to
|
|
expand wildcards with <code>./*</code> instead of <code>*</code>.
|
|
<CODE_SNIPPET>
|
|
# Here's the contents of the directory:
|
|
# -f -r somedir somefile
|
|
|
|
# This deletes almost everything in the directory by force
|
|
psa@bilby$ rm -v *
|
|
removed directory: `somedir'
|
|
removed `somefile'
|
|
|
|
# As opposed to:
|
|
psa@bilby$ rm -v ./*
|
|
removed `./-f'
|
|
removed `./-r'
|
|
rm: cannot remove `./somedir': Is a directory
|
|
removed `./somefile'
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Eval">
|
|
<SUMMARY>
|
|
<code>eval</code> should be avoided.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Eval munges the input when used for assignment to variables and can set
|
|
variables without making it possible to check what those variables
|
|
were.
|
|
<CODE_SNIPPET>
|
|
# What does this set?
|
|
# Did it succeed? In part or whole?
|
|
eval $(set_my_variables)
|
|
|
|
# What happens if one of the returned values has a space in it?
|
|
variable="$(eval some_function)"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Pipes to While">
|
|
<SUMMARY>
|
|
Use process substitution or for loops in preference to piping to while.
|
|
Variables modified in a while loop do not propagate to the parent
|
|
because the loop's commands run in a subshell.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
The implicit subshell in a pipe to while can make it difficult to track
|
|
down bugs.
|
|
<BAD_CODE_SNIPPET>
|
|
last_line='NULL'
|
|
your_command | while read line; do
|
|
last_line="${line}"
|
|
done
|
|
|
|
# This will output 'NULL'
|
|
echo "${last_line}"
|
|
</BAD_CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
Use a for loop if you are confident that the input will not contain
|
|
spaces or special characters (usually, this means not user input).
|
|
<CODE_SNIPPET>
|
|
total=0
|
|
# Only do this if there are no spaces in return values.
|
|
for value in $(command); do
|
|
total+="${value}"
|
|
done
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
Using process substitution allows redirecting output but puts the
|
|
commands in an explicit subshell rather than the implicit subshell that
|
|
bash creates for the while loop.
|
|
<CODE_SNIPPET>
|
|
total=0
|
|
last_file=
|
|
while read count filename; do
|
|
total+="${count}"
|
|
last_file="${filename}"
|
|
done < <(your_command | uniq -c)
|
|
|
|
# This will output the second field of the last line of output from
|
|
# the command.
|
|
echo "Total = ${total}"
|
|
echo "Last one = ${last_file}"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
Use while loops where it is not necessary to pass complex results
|
|
to the parent shell - this is typically where some more complex
|
|
"parsing" is required. Beware that simple examples are probably
|
|
more easily done with a tool such as awk. This may also be useful
|
|
where you specifically don't want to change the parent scope variables.
|
|
<CODE_SNIPPET>
|
|
# Trivial implementation of awk expression:
|
|
# awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts
|
|
cat /proc/mounts | while read src dest type opts rest; do
|
|
if [[ ${type} == "nfs" ]]; then
|
|
echo "NFS ${dest} maps to ${src}"
|
|
fi
|
|
done
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Naming Conventions">
|
|
|
|
<STYLEPOINT title="Function Names">
|
|
<SUMMARY>
|
|
Lower-case, with underscores to separate words. Separate libraries
|
|
with <code>::</code>. Parentheses are required after the function name.
|
|
The keyword <code>function</code> is optional, but must be used
|
|
consistently throughout a project.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
If you're writing single functions, use lowercase and separate words
|
|
with underscore. If you're writing a package, separate package names
|
|
with <code>::</code>. Braces must be on the same line as the function
|
|
name (as with other languages at Google) and no space between the
|
|
function name and the parenthesis.
|
|
<CODE_SNIPPET>
|
|
# Single function
|
|
my_func() {
|
|
...
|
|
}
|
|
|
|
# Part of a package
|
|
mypackage::my_func() {
|
|
...
|
|
}
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
The <code>function</code> keyword is extraneous when "()" is present
|
|
after the function name, but enhances quick identification of
|
|
functions.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Variable Names">
|
|
<SUMMARY>
|
|
As for function names.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Variables names for loops should be similarly named for any variable
|
|
you're looping through.
|
|
<CODE_SNIPPET>
|
|
for zone in ${zones}; do
|
|
something_with "${zone}"
|
|
done
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Constants and Environment Variable Names">
|
|
<SUMMARY>
|
|
All caps, separated with underscores, declared at the top of the file.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Constants and anything exported to the environment should be
|
|
capitalized.
|
|
<CODE_SNIPPET>
|
|
# Constant
|
|
readonly PATH_TO_FILES='/some/path'
|
|
|
|
# Both constant and environment
|
|
declare -xr ORACLE_SID='PROD'
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
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 <code>declare</code> doesn't operate on global variables
|
|
within functions, so <code>readonly</code> or <code>export</code> is
|
|
recommended instead.
|
|
</p>
|
|
<CODE_SNIPPET>
|
|
VERBOSE='false'
|
|
while getopts 'v' flag; do
|
|
case "${flag}" in
|
|
v) VERBOSE='true' ;;
|
|
esac
|
|
done
|
|
readonly VERBOSE
|
|
</CODE_SNIPPET>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Source Filenames">
|
|
<SUMMARY>
|
|
Lowercase, with underscores to separate words if desired.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
This is for consistency with other code styles in Google:
|
|
<code>maketemplate</code> or <code>make_template</code> but not
|
|
<code>make-template</code>.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Read-only Variables">
|
|
<SUMMARY>
|
|
Use <code>readonly</code> or <code>declare -r</code> to ensure they're
|
|
read only.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
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.
|
|
<CODE_SNIPPET>
|
|
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
|
|
if [[ -z "${zip_version}" ]]; then
|
|
error_message
|
|
else
|
|
readonly zip_version
|
|
fi
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Use Local Variables">
|
|
<SUMMARY>
|
|
Declare function-specific variables with <code>local</code>. Declaration
|
|
and assignment should be on different lines.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
Ensure that local variables are only seen inside a function and its
|
|
children by using <code>local</code> when declaring them. This avoids
|
|
polluting the global name space and inadvertently setting variables
|
|
that may have significance outside the function.
|
|
</p>
|
|
<p>
|
|
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.
|
|
<CODE_SNIPPET>
|
|
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
|
|
|
|
...
|
|
}
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Function Location">
|
|
<SUMMARY>
|
|
Put all functions together in the file just below constants. Don't hide
|
|
executable code between functions.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
If you've got functions, put them all together near the top of the
|
|
file. Only includes, <code>set</code> statements and setting constants
|
|
may be done before declaring functions.
|
|
</p>
|
|
<p>
|
|
Don't hide executable code between functions. Doing so makes the code
|
|
difficult to follow and results in nasty surprises when debugging.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="main">
|
|
<SUMMARY>
|
|
A function called <code>main</code> is required for scripts long enough
|
|
to contain at least one other function.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
In order to easily find the start of the program, put the main
|
|
program in a function called <code>main</code> as the bottom most
|
|
function. This provides consistency with the rest of the code base as
|
|
well as allowing you to define more variables as <code>local</code>
|
|
(which can't be done if the main code is not a function). The last
|
|
non-comment line in the file should be a call to <code>main</code>:
|
|
<CODE_SNIPPET>
|
|
main "$@"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
Obviously, for short scripts where it's just a linear flow,
|
|
<code>main</code> is overkill and so is not required.
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Calling Commands">
|
|
|
|
<STYLEPOINT title="Checking Return Values">
|
|
<SUMMARY>
|
|
Always check return values and give informative return values.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
For unpiped commands, use <code>$?</code> or check directly via an
|
|
<code>if</code> statement to keep it simple.
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
if ! mv "${file_list}" "${dest_dir}/" ; then
|
|
echo "Unable to move ${file_list} to ${dest_dir}" >&2
|
|
exit "${E_BAD_MOVE}"
|
|
fi
|
|
|
|
# Or
|
|
mv "${file_list}" "${dest_dir}/"
|
|
if [[ "$?" -ne 0 ]]; then
|
|
echo "Unable to move ${file_list} to ${dest_dir}" >&2
|
|
exit "${E_BAD_MOVE}"
|
|
fi
|
|
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
Bash also has the <code>PIPESTATUS</code> variable that allows checking
|
|
of the return code from all parts of a pipe. If it's only necessary to
|
|
check success or failure of the whole pipe, then the following is
|
|
acceptable:
|
|
<CODE_SNIPPET>
|
|
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
|
|
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
|
|
echo "Unable to tar files to ${dir}" >&2
|
|
fi
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
<p>
|
|
However, as <code>PIPESTATUS</code> will be overwritten as soon as you
|
|
do any other command, if you need to act differently on errors based on
|
|
where it happened in the pipe, you'll need to assign
|
|
<code>PIPESTATUS</code> to another variable immediately after running
|
|
the command (don't forget that <code>[</code> is a command and will
|
|
wipe out <code>PIPESTATUS</code>).
|
|
<CODE_SNIPPET>
|
|
tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
|
|
return_codes=(${PIPESTATUS[*]})
|
|
if [[ "${return_codes[0]}" -ne 0 ]]; then
|
|
do_something
|
|
fi
|
|
if [[ "${return_codes[1]}" -ne 0 ]]; then
|
|
do_something_else
|
|
fi
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
<STYLEPOINT title="Builtin Commands vs. External Commands">
|
|
<SUMMARY>
|
|
Given the choice between invoking a shell builtin and invoking a separate
|
|
process, choose the builtin.
|
|
</SUMMARY>
|
|
<BODY>
|
|
<p>
|
|
We prefer the use of builtins such as the <em>Parameter Expansion</em>
|
|
functions in <code>bash(1)</code> as it's more robust and portable
|
|
(especially when compared to things like sed).
|
|
</p>
|
|
<p>
|
|
Example:
|
|
<CODE_SNIPPET>
|
|
# Prefer this:
|
|
addition=$((${X} + ${Y}))
|
|
substitution="${string/#foo/bar}"
|
|
|
|
# Instead of this:
|
|
addition="$(expr ${X} + ${Y})"
|
|
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"
|
|
</CODE_SNIPPET>
|
|
</p>
|
|
</BODY>
|
|
</STYLEPOINT>
|
|
|
|
</CATEGORY>
|
|
|
|
<CATEGORY title="Conclusion">
|
|
<p>
|
|
Use common sense and <em>BE CONSISTENT</em>.
|
|
</p>
|
|
<p>
|
|
Please take a few minutes to read the Parting Words section at the bottom
|
|
of the <a href="cppguide.html">C++ Guide</a>.
|
|
</p>
|
|
</CATEGORY>
|
|
|
|
<p align="right">
|
|
Revision 1.26
|
|
</p>
|
|
|
|
</GUIDE>
|