Why Consistent Shell Script Standards Matter: A Practical Guide
This guide explains the importance of shell script coding standards, outlines core principles such as correctness, readability, maintainability, and consistency, and provides detailed recommendations on file naming, encoding, line length, indentation, comments, testing, and safe use of commands to improve script quality and reduce maintenance costs.
Preface
Like other coding conventions, this document addresses not only aesthetic formatting but also conventions and standards. It focuses on rules we commonly follow; for non‑mandatory items we avoid giving opinions.
Why Have a Coding Standard?
Coding standards are crucial for developers for several reasons:
80% of a software's lifecycle cost is spent on maintenance.
Almost no software is maintained throughout its life by its original developers.
Standards improve readability, allowing programmers to understand new code quickly and thoroughly.
If source is released as a product, it must be well‑packaged and clear, just like any other product.
Principles of coding standards.
The guidelines in this document aim to maximize the following principles:
Correctness
Readability
Maintainability
Debuggability
Consistency
Beauty
Although many fundamentals are covered, no single standard can answer every question; developers must still apply judgment after writing code.
Code Standard Levels
Optional: users may refer to it and decide whether to adopt.
Preferable: users should adopt unless special circumstances prevent it.
Mandatory: users must adopt except in very rare special cases.
Note: Unspecified items default to Mandatory.
Source Files
Basics
Use Cases
Shell scripts are recommended only for relatively simple utilities or wrapper scripts; a single script should not become overly complex.
When deciding to use a shell script, follow these principles:
If the main purpose is to invoke other tools and data volume is small, shell is a good choice.
If performance is critical, prefer another language.
If handling complex data structures, prefer another language.
If the script grows and may continue to grow, rewrite it in another language early.
File Names
Executable files should not have an extension; library files must use .sh as an extension and be non‑executable.
File names must be all lowercase and may contain underscores _ or hyphens -. Use hyphens for executables and underscores for libraries.
Examples:
my-useful-bin
my_useful_libraries.sh
myusefullibraries.shBad examples:
My_Useful_Bin
myUsefulLibraries.shFile Encoding
Source files must be UTF‑8 and use LF line endings.
Line Length
Limit lines to 120 characters; longer lines hinder readability and indentation. Exceptions are import statements and URLs in comments. For strings longer than 120 characters, use here‑documents or embedded newlines.
Example:
# 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."Whitespace
Only spaces are allowed as whitespace in source files; tabs may be used only if set to 4 spaces. No trailing meaningless whitespace.
Garbage Cleanup (Recommended)
Remove unused or commented‑out code, variables, and methods to avoid clutter.
Structure
Using Bash
Bash is the only allowed executable script shell. Executable files must start with #!/bin/bash and use set to configure options so that bash <em>script_name</em> runs correctly.
#!/bin/bash
set -eLicense or Copyright (Recommended)
Place license and copyright information at the top of the source file, e.g.:
#
# Licensed under the BSD 3‑Clause License (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
# https://opensource.org/licenses/BSD-3-Clause
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#Indentation
Increase indentation by 4 spaces for each new block (no tab characters). Decrease when the block ends. Indentation applies to both code and comments.
main() {
# indent 4 spaces
say="hello"
flag=0
if [[ $flag = 0 ]]; then
# indent 4 spaces
echo "$say"
fi
}Pipelines
If a pipeline fits on one line, keep it on one line with spaces around the pipe. For long pipelines, break after the pipe and indent 4 spaces.
# single‑line pipeline
command1 | command2
# multi‑line pipeline
command1 \
| command2 \
| command3 \
| command4Loops
Place ; do, ; then on the same line as while, for, or if. else should be on its own line. End statements on their own line aligned with the start.
for dir in ${dirs_to_cleanup}; do
if [[ -d "${dir}/${BACKUP_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${BACKUP_SID}"
rm "${dir}/${BACKUP_SID}/"*
if [[ "$?" -ne 0 ]]; then
error_message
fi
else
mkdir -p "${dir}/${BACKUP_SID}"
if [[ "$?" -ne 0 ]]; then
error_message
fi
fi
doneCase Statements
Indent options by 4 spaces. Place pattern, commands, and the terminating ;; on separate lines. Avoid leading parentheses and ;& / ;& symbols.
case "${expression}" in
a)
variable="..."
some_command "${variable}" "${other_expr}" ...
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" ...
;;
*)
error "Unexpected expression '${expression}'"
;;
esacFunction Placement
Place all functions after constants and before executable code. Do not hide executable statements between functions.
Main Function
For scripts longer than a few lines and containing other functions, define a main function and call it at the end:
main "$@"Comments
Comments should clarify code, be simple and precise, start before coding, explain design intent rather than describe behavior, and be aligned with the surrounding code. Use a single space after #.
File Header
Each file must start with a top‑level comment describing its purpose, in addition to any license.
#!/bin/bash
# Perform hot backups of databases.Function Documentation
Every function must include comments describing its purpose, global variables used, parameters, and return values.
# ---------------------------------------
# Cleanup files from the backup dir
# Globals: BACKUP_DIR BACKUP_SID
# Arguments: None
# Returns: None
# ---------------------------------------
cleanup() {
...
}TODO Comments
Use uppercase TODO with optional identifier in parentheses to mark temporary or imperfect code.
# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
# TODO(--bug=123456): remove the "Last visitors" featureNaming
Function Names
Use lowercase letters with underscores; use double colons :: to separate package names. No space before parentheses.
# Single function
my_func() {
...
}
# Part of a package
mypackage::my_func() {
...
}Variable Names
Same rules as function names. Loop variables should resemble the iterated collection.
Constants and Environment Variables
All uppercase with underscores, declared at the top. Use readonly or declare -xr for read‑only variables.
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr BACKUP_SID='PROD'Read‑Only Variables
Use readonly or declare -r to enforce immutability.
Local Variables
Declare one variable per line; use local inside functions and separate declaration from assignment.
my_func2() {
local name="$1"
local my_var
my_var="$(my_func)" || return
...
}Exception and Logging
Use shell return values for exceptions and direct error messages to STDERR.
err() {
echo "[$(date +'%FT%T%z')]: $@" >&2
}
if ! do_something; then
err "Unable to do_something"
exit "${E_DID_NOTHING}"
fiCommand Substitution
Prefer $(command) over backticks for readability and nesting.
var="$(command "$(command1)")"Conditional Tests
Use [[ ... ]] instead of [ ... ] or test for better safety and regex support.
String Tests
Prefer variable references and -z / -n tests over string concatenation.
Filename Expansion
Specify explicit paths when using globbing to avoid accidental option parsing.
Avoid eval
Do not use eval unless absolutely necessary.
Avoid Piping into while Loops
Use process substitution or a for loop instead, because a pipe creates a subshell that isolates variable changes.
Check Return Values
Always verify command exit codes, using if or $?, and provide meaningful return values.
Prefer Built‑ins Over External Commands
Built‑ins have lower overhead and fewer dependencies.
File Loading
Prefer source over . for readability.
Content Filtering and Statistics
Use a single command with appropriate options instead of chaining multiple commands with pipes when possible.
Proper Use of Return vs. Exit
Functions should use return rather than exit so that cleanup code can still run.
# Good
my_func() {
[[ -e /dummy ]] || return 1
}
cleanup() { ... }
my_func
cleanup
# Bad
my_func() {
[[ -e /dummy ]] || exit 1
}
cleanup() { ... }
my_func
cleanupTools
ShellCheck
Source: http://itxx00.github.io/blog/2020/01/03/shell-standards/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
