Fundamentals 25 min read

Why Consistent Shell Coding Standards Matter: A Practical Guide

This guide explains the importance of shell coding standards, outlines core principles such as correctness, readability and maintainability, defines mandatory and optional rules for file naming, indentation, comments, variable handling, control structures, error handling, and provides concrete examples and best‑practice recommendations for Bash scripts.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Why Consistent Shell Coding Standards Matter: A Practical Guide

Preface

Like other coding standards, this document discusses not only aesthetic formatting but also conventions and rules that should be followed. It focuses on the rules we commonly enforce and avoids giving opinions on non‑mandatory items.

Why have coding standards

80% of a software's lifecycle cost is spent on maintenance.

Almost no software is maintained by its original developers for its entire life.

Standards improve readability and help programmers understand new code quickly.

If source is shipped as a product, it must be well‑packaged and clear.

Coding‑standard principles

Correctness

Readability

Maintainability

Debuggability

Consistency

Beauty

Code‑standard level definitions

Optional: users may reference and decide whether to adopt.

Preferable: users should adopt unless special circumstances prevent it.

Mandatory: users must adopt unless a very special case applies (default).

Source files

Basics

Usage scenario

Shell scripts are recommended for simple utilities or wrapper scripts; keep individual scripts simple.

If mainly invoking other tools with little data, shell is a choice.

If performance is critical, use another language.

If handling complex data structures, use another language.

If the script grows, rewrite in another language early.

File name

Executable files should have no extension; library files must use .sh as an extension and be non‑executable.

my-useful-bin</code><code>my_useful_libraries.sh</code><code>myusefullibraries.sh

Bad example:

My_Useful_Bin</code><code>myUsefulLibraries.sh

File encoding

Source files must be UTF‑8 with LF line endings.

Line length

Limit lines to 120 characters; exceptions are import statements and URLs in comments.

Garbage cleanup (recommended)

Remove unused or commented‑out code to avoid clutter.

Structure

Use Bash

Only Bash is allowed for executable scripts. Files must start with #!/bin/bash and use set to configure options.

#!/bin/bash
set -e

License or copyright (recommended)

#
# 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 a new block; never use tabs. Close blocks return to the previous level.

main() {
    # 4‑space indent
    say="hello"
    flag=0
    if [[ $flag = 0 ]]; then
        echo "$say"
    fi
}

Pipelines

If a pipeline fits on one line, keep spaces around |. 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 stands alone. End statements are on their own line.

for dir in ${dirs_to_cleanup}; do
    if [[ -d "${dir}/${BACKUP_SID}" ]]; then
        # ...
    else
        mkdir -p "${dir}/${BACKUP_SID}"
    fi
done

case statement

Indent options by 4 spaces; pattern, commands and ;; each on separate lines.

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; avoid executable code between functions.

Main function

For scripts with multiple functions, define a main function and call it at the end.

main "$@"

Comments

Make code clearer.

Avoid excessive decoration.

Keep them simple and clear.

Write comments before coding.

Explain design intent, not just code behavior.

Align comment indentation with surrounding code; a single space after #.

File header

Each file should start with a top‑level comment describing its purpose, in addition to any copyright.

#!/bin/bash
#
# Perform hot backups of databases.

Function comments

Every function must include description, global variables used, parameters, and return value.

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

# TODO(mrmonkey): Handle unlikely edge cases (bug ####)

Naming

Function names

Use lowercase with underscores; double colon :: separates package name. No space before parentheses.

# Single function
my_func() {
    ...
}

# Part of a package
mypackage::my_func() {
    ...
}

Variable names

Same rules as function names.

Constants and environment variables

All uppercase with underscores, declared at the top.

# Constant
readonly PATH_TO_FILES='/some/path'

# Constant and environment
declare -xr BACKUP_SID='PROD'

Read‑only variables

Use readonly or declare -r to enforce immutability.

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
    error_message
else
    readonly zip_version
fi

Local variables

Declare one per line with local; separate declaration and assignment when using command substitution.

my_func2() {
    local name="$1"
    local my_var
    my_var="$(my_func)" || return
    ...
}

Exceptions and logging

Exceptions

Use shell exit codes to signal errors and differentiate cases.

if ! do_something; then
    err "Unable to do_something"
    exit "${E_DID_NOTHING}"
fi

Logging

Send error messages to STDERR.

err() {
    echo "[$(date +'%FT%T%z')]: $@" >&2
}

Variable expansion (recommended)

Prefer ${var} over $var to avoid ambiguity.

echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"

Variable quoting (recommended)

Quote strings that contain spaces, command substitutions, or special characters.

flag="$(some_command $@)"
echo "${flag}"
value=32
number="$(generate_number)"

Command substitution

Use $(command) instead of backticks.

var="$(command $(command1))"

Conditional testing

Prefer [[ ... ]] over [ ... ] or test for better safety and regex support.

if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
    echo "Match"
fi

String testing

Use -z and -n for empty/non‑empty checks.

if [[ -z "${my_var}" ]]; then
    do_something
fi

Filename expansion

When globbing, always prefix with ./ to avoid accidental option parsing.

# Safe removal
rm -v ./*

Avoid eval

Do not use eval for variable assignment.

Avoid pipe‑to‑while

Use process substitution or a for loop instead of piping into while to prevent subshell variable loss.

while read count filename; do
    total+="${count}"
    last_file="${filename}"
done < <(your_command | uniq -c)

Check return values

Always verify command success and provide useful exit codes.

if ! mv "${file_list}" "${dest_dir}/"; then
    echo "Unable to move ${file_list} to ${dest_dir}" >&2
    exit "${E_BAD_MOVE}"
fi

Prefer built‑ins over external commands

Use Bash arithmetic expansion and parameter substitution instead of expr or sed.

addition=$((X + Y))
substitution="${string/#foo/bar}"

Source external libraries

Use source instead of . for readability.

source my_libs.sh

Avoid unnecessary pipelines

Combine functionality into a single command when possible.

grep net.ipv4 /etc/sysctl.conf
grep -c net.ipv4 /etc/sysctl.conf
wc -l /etc/sysctl.conf

Use return, not exit, in functions

Functions should return status codes; the script decides whether to exit.

my_func() {
    [[ -e /dummy ]] || return 1
}
cleanup() { ... }
my_func
cleanup

Appendix: Common tools

Recommended tool: ShellCheck.

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

Article reposted by DevOps技术栈 (copyright belongs to the original author).

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.

best practicescoding standardsBashscript
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

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.