Operations 26 min read

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.

Efficient Ops
Efficient Ops
Efficient Ops
Why Consistent Shell Script Standards Matter: A Practical Guide

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.sh

Bad examples:

My_Useful_Bin
myUsefulLibraries.sh

File 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 -e

License 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 \
    | command4

Loops

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
done

Case 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}'"
        ;;
esac

Function 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" feature

Naming

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}"
fi

Command 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
cleanup

Tools

ShellCheck

Source: http://itxx00.github.io/blog/2020/01/03/shell-standards/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Operationscoding standardsBash
Efficient Ops
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.