Foreword
Which shell to use
Bash
only shell script language that is allowed to use for executable files.
Scripts should start with #!/bin/bash
with a minimum set of flags. Use set
to set shell options so that calling your script as bash <script_name>
does not violate its functionality.
Limiting all shell scripts to bash gives us a consistent shell language that is installed on all of our machines.
The only exception is if you are limited by the terms of what you are programming. One example would be the Solaris SVR4 packages, which require the use of the usual Bourne shell for any scripts.
When to use shell
Shell should be used only for small utilities or simple wrapper skrptov.
Although shell scripting is not a development language, it is used to write various utilities throughout Google. This style guide is more of a recognition of its use, rather than an offer to use it in a wide application.
Some recommendations:
- If you most often call other utilities and do relatively little data manipulation, the shell is an acceptable choice for the task.
- If performance matters, use something else, but not a shell.
- If you find that you need to use arrays more than to assign
${PIPESTATUS}
, you must use Python. - If you are writing a script longer than 100 lines, you probably should write it in Python. Keep in mind that scripts are growing. Rewrite your script in another language earlier to avoid time consuming rewriting later.
Shell files and interpreter call
File extensions
Executable files should not have an extension (strongly preferred) or the .sh
extension. Libraries must have the .sh
extension and must not be executable.
There is no need to know in which language the program is written when it is executed, and the shell does not require an extension, so we prefer not to use it for executable files.
However, it is important for libraries to know in which language it is written, and sometimes it is necessary to have similar libraries in different languages. This allows you to have identically named library files with identical targets, but the names of different languages should be identical, except for the language-specific suffix.
SUID / SGID
SUID and SGID are not allowed on shell scripts.
There are too many security issues here, which make it almost impossible to provide sufficient protection for SUID / SGID. Although bash makes it difficult to launch a SUID, it is still possible on some platforms, so we explicitly prohibit its use.
Use sudo
to provide enhanced access if you need it.
Environment
STDOUT vs STDERR
All error messages should be sent to STDERR
.
It helps to separate the normal state from the actual problems.
The feature for displaying error messages is recommended to be used with other state information.
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
Comments
File header
Start each file with a description of its contents.
Each file should have a comment title that includes a brief description of its contents. Copyright notice and author information are optional.
Example:
Feature Comments
Any function that is not obvious or short should be commented. Any function in the library should be commented regardless of its length or complexity.
You need to make someone else understand how to use your program or how to use the function in your library, simply by reading the comments (and the need for self-improvement) without reading the code.
All comments on features should include:
- Function description
- Used and modified global variables
- Arguments received
- Return values other than the standard exit codes in the last command.
Example:
Implementation Comments
Comment on complex, non-obvious, interesting or important parts of your code.
This is assumed to be the usual practice of commenting code in Google. Do not comment on everything. If there is a complicated algorithm or you are doing something unusual, add a short comment.
TODO Comments
Use TODO comments for code that is a temporary, short-term solution or a fairly good, but not perfect.
This is in agreement with the C ++ manual .
TODO comments should include the word TODO in capital letters and then your name in parentheses. The colon is optional. It is also preferable to indicate the bug / ticket number next to the TODO element.
Example:
Although you must follow a style that is already used in the files you are modifying, for any new code, the following is required.
Indentation
Indent 2 space. No tabs.
Use blank lines between blocks to improve readability. Indenting is two spaces. No matter what you do, do not use tabs. For existing files, stay true to the current indents.
String length and length of values
The maximum string length is 80 characters.
If you need to write lines longer than 80 characters, this should be done with the help of here document
or, if possible, the built-in newline
. Literal values that can be longer than 80 characters and cannot be divided reasonably allowed, but it is strongly recommended to find a way to make them shorter.
Pipelines
Pipelines should be divided into one line each, if they do not fit on one line.
If plyplayn fit on one line, it should be on one line.
If not, it should be divided so that each section is on a new line and indented by 2 spaces for the next section. This refers to a chain of commands combined using '|' and also to logical connections using '||' and '&&'.
Cycles
Put ; do
; do
and ; then
; then
on the same line as while
, for
or if
.
The cycles in the shell are slightly different, but we follow the same principles as with curly brackets when declaring functions. That is ; then
; then
and ; do
; do
must be in the same line as if
/ for
/ while
. else
must be on a separate line, and the closing statements must be on their own line, vertically aligned with the opening instruction.
Example:
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
- Separate options in 2 spaces.
- For single-line variations, a space is required after the closing parenthesis of the pattern and before
;;
. - Long or multi-command options should be divided into several lines with a template, actions and
;;
on separate lines.
The corresponding expressions esac
back one level from case
and esac
. Multiline actions also have indentation on a separate level. No need to put expressions in quotes. Expression templates should not precede open parentheses. Avoid using &;
and ;;&
notation.
case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac
Simple commands can be put on one line with the pattern and ;;
while the expression remains readable. This is often suitable for handling single-letter options. When actions do not fit on one line, leave the template in its line, the next action, then ;;
also in own line. When it is the same line as with the action, use the space after the closing bracket of the template and the other before ;;
.
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
In order of priority: observe what is already used; enclose variables in quotes; prefer "${var}"
more than "$var"
, but with an eye to the context of use.
These are rather recommendations as the topic is controversial enough for mandatory regulation. They are listed in order of priority.
- Use the same style that you find in the existing code.
- Put variables in quotes, see the Quotes section below.
Do not put single characters for quotes and curly brackets for shell / positional parameters unless it is strictly necessary and to avoid confusion.
Prefer braces for all other variables.
Quotes
- Always use quotes for values that contain variables, command substitutions, spaces, or shell metacharacters, until safe values are not shown in quotes.
- Prefer quotes for values that are “words” (as opposed to command parameters or path names)
- Never quote whole numbers in quotes.
- Know how quotes work for match patterns in
[[
. - Use
"$@"
if you have no special reason to use $*
.
Features and errors
Command Substitution
Use $(command)
instead of backquotes.
Nested back quotes require shielding of internal quotes using \
. The $ (command)
format does not change depending on nesting and is easier to read.
Example:
Checks, [
and [[
[[ ... ]]
is preferable to [
, test
or /usr/bin/[
.
[[ ... ]]
reduces the possibility of error, since the resolution of the path or the division of words between [[
and ]]
and [[ ... ]]
does not allow the use of a regular expression, where [ ... ]
not.
Value check
Use quotation marks, not additional characters, where possible.
Bash is smart enough to work with an empty string in the test. Therefore, the resulting code is much easier to read, use checks for empty / non-empty values or empty values, without using additional characters.
To avoid confusion in what you are testing, use -z
or -n
.
Wildcard Expressions for File Names
Use an explicit path when creating substitution expressions for file names.
Since file names can begin with the -
character, it is much safer to ./*
lookup expression as ./*
instead of *
.
Eval
eval
should be avoided.
Eval allows you to expand the variables passed in the input, but it can also set other variables without being able to check them.
While in
Use command substitution or for
loop, preferably in while
. Variables changed in a while
do not apply to the parent, because the loop commands are executed in a sabershell.
Implicit sables in pipe while
can make it difficult to track errors.
last_line='NULL' your_command | while read line; do last_line="${line}" done
Use a for loop if you are sure that the input will not contain spaces or special characters (this usually does not imply user input).
total=0
Using command substitution allows you to redirect the output, but executes commands in an explicit sub-variable, in contrast to the implicit sub-variable, which creates a bash for the while
.
total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c)
Use while
loops where there is no need to pass complex results to the parent shell — this is typical when more complex parsing is required. Remember that simple examples are sometimes much easier to solve using a tool like awk. This can also be useful when you do not specifically want to change the variables of the parent environment.
Name Agreement
Function names
In lower case, with underscores for word separation. Separate libraries with ::
. After the function name, parentheses are required. The function keyword is optional, but if used, it is consistent throughout the project.
If you are writing individual functions, use lowercase and single words with underscores. If you are writing a package, separate the package names with ::
. The brackets must be on the same line as the function name (as in other languages in Google), and not have a space between the function name and the bracket.
When "()" comes after the function name, then the function keyword looks redundant, but it improves the quick identification of functions.
Variable name
As for function names.
Variable names for cycles must be named the same for any variable that you iterate over.
for zone in ${zones}; do something_with "${zone}" done
Names of environment variable constants
All capital letters, separated by underscores, are declared at the top of the file.
Constants and everything that is exported to the environment must be in upper case.
Some things remain constant when they are first installed (for example, via getopts
). Thus, it is quite normal to set a constant via getopts
or based on a condition, but it must be made readonly
immediately after that. Note that declare
does not work with global variables inside functions, so readonly
or export
recommended instead.
VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE
Source file naming
Lower case, with an underscore for word separation, if necessary.
This applies to matching other styles of code in Google: maketemplate
or make_template
, but not make-template
.
Read Only Variables
Use readonly
or declare -r
to ensure that they are read-only.
Since global is widely used in the shell, it is important to catch errors when working with them. When you declare a read-only variable, make it explicit.
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi
, , local
. .
, , local
. , .
, ; local
exit code .
my_func2() { local name="$1"
. .
, . , set , .
. .
main
, main
, , .
, main
. , ( , ). main:
main "$@"
, , , main
— , .
.
$?
if
, .
Example:
if ! mv "${file_list}" "${dest_dir}/" ; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi
, PIPESTATUS
, - , , PIPESTATUS
( , [
PIPESTATUS
).
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
shell , .
, bash, ( , sed
).
Example:
Conclusion
.
, Parting Words C++ .