-
Code should be easy to read and understand.
-
Keep the code as simple as possible. Avoid unnecessary complexity.
-
Use meaningful names for variables, functions, etc. Names should reveal intent.
-
Functions should be small and do one thing well. They should not exceed a few lines.
-
Function names should describe the action being performed.
-
Prefer fewer arguments in functions. Ideally, aim for no more than two or three.
-
Only use comments when necessary, as they can become outdated.
Instead, strive to make the code self-explanatory. -
When comments are used, they should add useful information that is not
readily apparent from the code itself. -
Properly handle errors and exceptions to ensure the software's robustness.
-
Consider security implications of the code.
Implement security best practices to protect against vulnerabilities and attacks. -
Executables must start with
#!/usr/bin/env bashand minimal flags. -
Use set to set shell options so that calling your script as
bash script_name
does not break its functionality. -
Shell should only be used for small utilities or simple wrapper scripts.
-
If you are writing a script that is more than 100 lines long, or that uses
non-straightforward control flow logic, suggest a rewrite in a more
structured language now. -
Executables should have a .sh extension or no extension.
-
Libraries must have a .sh extension and should not be executable.
-
SUID and SGID are forbidden on shell scripts.
-
All error messages should go to STDERR.
-
Start each file with a description of its contents.
-
Every file must have a top-level comment including a brief overview of its contents.
-
A copyright notice and author information are optional.
-
Any function that is not both obvious and short must have a function header comment.
Any function in a library must have a function header comment regardless of
length or complexity. -
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. -
All function header comments should describe the intended API behaviour.
-
Description of a function includes
Globals: List of global variables used and modified
Arguments: Arguments taken,Outputs: Output to STDOUT or STDERR
Returns: Returned values other than the default exit status of last command. -
Comment tricky, non-obvious, interesting or important parts of your code.
-
Indent 2 spaces. No tabs.
-
Maximum line length is 80 characters.
-
If you have to write literal strings that are longer than 80 characters,
this should be done with a here document or an embedded newline if possible. -
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. -
Put
; thenand; doon the same line as theif,for, orwhile. -
elseshould be on its own line and closing statements (fianddone) should be
on their own line vertically aligned with the opening statement. -
Although it is possible to omit
in "$@"in for loops we recommend consistently
including it for clarity. -
In case statements, indent alternatives by 2 spaces.
One-line alternatives need 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. -
In order of precedence: Stay consistent with what you find;
quote your variables; prefer"${var}"over"$var". -
Use
"$@"unless you have a specific reason to use"$*", such as simply
appending the arguments to a string in a message or log. -
Use
$(command)instead of backticks. -
[[…]]is preferred over[ … ], test and/usr/bin/[. -
Use quotes rather than filler characters where possible.
-
Use an explicit path when doing wildcard expansion of filenames.
-
evalshould be avoided. -
Bash arrays should be used to store lists of elements, to avoid quoting
complications. This particularly applies to argument lists. -
Given the choice between invoking a shell builtin and invoking a separate process,
choose the builtin. -
Always check return values and give informative return values.
-
A function called
mainis required for scripts long enough to contain
at least one other function. -
Put all functions together in the file just below constants.
Don’t hide executable code between functions.
Doing so makes the code difficult to follow and results in nasty surprises
when debugging. -
If you’ve got functions, put them all together near the top of the file.
Only includes, set statements and setting constants may be done
before declaring functions. -
Declare function-specific variables with local. Remember that bash uses dynamic scoping i.e., local variables can be
accessed by called functions with the function's body. -
File names should be lowercase, with underscores to separate words if desired.
-
Constants and anything exported to the environment should be capitalized,
separated with underscores, and declared at the top of the file. -
For the sake of clarity readonly or export is recommended
vs. the equivalent declare commands. -
Aliases should be avoided in scripts. Use functions instead.
-
Always use
(( … ))or$(( … ))rather thanletor$[ … ]orexpr. -
Use process substitution or the readarray builtin (bash4+)
in preference to piping to while.
Pipes create a subshell, so any variables modified within a pipeline
do not propagate to the parent shell. -
Function names should be 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. -
Avoid modifying the values of the following bash variables in a script:
BASH_VERSION,BASH_VERSINFO,EUID,UID,PPID,PID,PGID,SID,
SHLVL,RANDOM,LINENO,FUNCNAME,BASH_SOURCE,BASH_LINENO,OSTYPE,
MACHTYPE,HOSTTYPE,HOSTNAME,LINES,COLUMNS,GROUPS,HOME,OPTARG
,OPTIND,REPLY,SECONDS,TERM,DEBUG. -
It's worth noting that Bash variables starting with an underscore
(e.g.,_,__, etc.) are generally reserved for internal use and should not
be modified. -
When you have a function
mainin the script, wrap the call tomainas below:if [[ "${BASH_SOURCE[0]}" == "${0}" ]];then main "$@" fi
-
Every script must have a debug mode that can be enabled from the command line. It can be as simple as
set -x. -
For a script that uses complex logic, it is a good practice to provide a dry-run
mode with the option enabled from the command line. -
Complex scripts must provide a verbose mode enabled using -v and --verbose from the command line. When verbose mode
is enabled, the script must provide detailed information about its execution, including any errors or warnings. -
When sourcing a file (say
include.sh) for a script that may be executed from anywhere, use the following template:SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/include.sh"
-
A script's usage must also include examples.