Skip to content

tatanus/bash_style_guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 

Repository files navigation

Bash Style Guide

This document defines the Bash coding style used for scripts in this project.


1. General Principles

  • Scripts must run on Bash 4.0+ and be portable across Linux (Ubuntu) and modern macOS with updated Bash.
  • ⚠️ Incompatibility: macOS ships with Bash 3.2 by default. Features like associative arrays will not work there. Users must install a newer Bash.
  • Prefer POSIX-compliant constructs, but Bash-specific features (arrays, [[ ]], ${var:-default}) are allowed where beneficial.
  • Security is the top priority:
    • Quote all variable expansions: "${var}".
    • Avoid eval (only allowed with explicit justification).
    • Use mktemp for temporary files/directories.
    • Sanitize and validate all user input, paths, and environment variables.

2. File Structure

Shebang

#!/usr/bin/env bash

Header Block

Each script must include a complete header:

###############################################################################
# NAME         : script_name.sh
# DESCRIPTION  : <brief description>
# AUTHOR       : Your Name
# DATE CREATED : YYYY-MM-DD
###############################################################################
# EDIT HISTORY:
# DATE       | EDITED BY    | DESCRIPTION
# -----------|--------------|-----------------------------------------------
# YYYY-MM-DD | Your Name    | Initial creation
###############################################################################

Strict Mode

set -uo pipefail
IFS=$'\n\t'
  • Never use set -e — it causes hidden, hard-to-debug failures when commands fail inside subshells, conditionals, or pipelines.
  • ✅ Instead, always use explicit error checks (if, ||, $?). This makes the script’s behavior predictable and debuggable.

3. Naming Conventions

  • Functionssnake_case_name
  • Variablessnake_case
  • ConstantsUPPERCASE (no leading underscores)
  • File-scope constantsreadonly

Examples:

function validate_paths_writable() {
    # Implementation
    return 0
}

log_path="/tmp/logs"
readonly log_path

VERSION="1.0.0"
PASS=0
FAIL=1
readonly VERSION PASS FAIL

4. Functions

Every function must include proc-doc comments:

###############################################################################
# function_name
#------------------------------------------------------------------------------
# Purpose  : What the function does
# Usage    : function_name [args]
# Arguments:
#   $1 : Description
#   $2 : Description
# Returns  : exit code meaning
# Globals  : vars/functions required
###############################################################################
function function_name() {
    return 0
}

Rules:

  • Functions must return status codes only (0 = success, non-zero = failure).
  • Functions must not return data via return. Use stdout or arrays for data.

5. Logging

Standalone Scripts

  • Structured logging with levels: INFO, WARN, ERROR, FAIL, PASS, DEBUG, VDEBUG.
  • Colors if ENABLE_COLOR=1.
  • File logging if ENABLE_FILE_LOGGING=1.

Libraries

  • Provide fallback loggers (info, warn, etc.) if not defined.

6. Error Handling

  • Manual error checks only (no set -e).
  • Use helpers: validate_commands, validate_env_vars, validate_paths_*.
  • Exit codes must be explicit.

Example:

if ! command -v foo > /dev/null 2>&1; then
    error "Missing required command: foo"
    exit 1
fi

7. Pipes & Readability

  • Use “display pipes”** for multi-line pipelines.
    Rationale: placing | at the start of the line makes diffs cleaner, aligns operators visually, and avoids trailing whitespace issues.
grep "pattern" file.txt \
    | sort \
    | uniq \
    | awk '{ print $1 }'
  • Inline short pipes (cmd1 | cmd2) are fine for simple cases.

  • Avoid Useless Use of Cat (UUOC) — piping a file into a command like cat file | while ... creates an unnecessary process and may introduce subshell issues. Instead, redirect input directly:

# BAD (UUOC)
cat file | while read -r line; do
    printf '%s
' "${line}"
done

# GOOD
while read -r line; do
    printf '%s
' "${line}"
done < file
  • Be aware of subshell scope loss:
    Variables modified inside a pipeline will not persist outside.
# BAD: result is empty because the while loop runs in a subshell
echo "hello" | while read -r word; do
    result="${word}"
done
printf 'Result: %s
' "${result}"   # prints empty

# GOOD: process substitution avoids subshell
while read -r word; do
    result="${word}"
done < <(echo "hello")
printf 'Result: %s
' "${result}"   # prints "hello"

8. Arrays vs Strings

Why: Space-delimited strings are unsafe — filenames or arguments containing spaces will break parsing. Arrays handle arbitrary input safely and preserve element boundaries.

  • ✅ Use arrays for lists: arr=("a" "b")
  • ❌ Never use space-delimited strings for lists: list="a b c"

Iteration:

arr=("alpha" "beta" "gamma")
for item in "${arr[@]}"; do
    printf '%s\n' "${item}"
done

9. Conditionals & Tests

Why: [[ ... ]] is safer and more predictable than [ ... ].

  • [[ ]] does not perform word splitting or pathname expansion.

  • [ ] can break if variables contain -, ], or glob characters.

  • (( )) is preferred for arithmetic because it's cleaner and avoids string parsing.

  • Always use [[ ... ]] (not [ ... ]).

  • Arithmetic: use (( ... )).

  • Avoid [ with < or > (interpreted as redirection).

Example:

if [[ "${x}" -gt 3 ]]; then
    printf 'x > 3\n'
fi

10. Command Substitution

  • ✅ Use $(...) (nestable, readable).
  • ❌ Ban legacy backticks — they are harder to read, cannot be nested cleanly, and behave inconsistently across shells.

Example:

date_str="$(date +%Y-%m-%d)"
printf 'Today is %s
' "${date_str}"

11. Input Handling

  • Always use read -r (prevents \ escaping).
  • Quote variables when using read.
  • Avoid for f in $(ls) — it breaks on spaces, globbing, and unusual filenames, and spawns an unnecessary ls process. ✅ Use for f in * instead, which is safer and more portable.

Example:

while read -r line; do
    printf '%s\n' "${line}"
done < input.txt

for f in *; do
    printf 'File: %s\n' "${f}"
done

12. Printing

  • ✅ Always use printf (portable, predictable).
  • ❌ Ban echo -e — behavior varies across shells (not portable) and can cause unexpected escapes.

Example:

printf 'Hello %s' "${name}"

13. External Commands

Why: Forking external commands (grep, awk, cut) is slower and less portable when the same result can be achieved with Bash built-ins.
Prefer Bash parameter expansion or built-in string manipulation whenever possible. External tools should only be used when Bash cannot handle the task.

  • Avoid grep | awk | cut unnecessarily — use Bash parameter expansion when possible.
  • Use external commands only when Bash features cannot handle the task.

Example:

# BAD
echo "${file}" | cut -d. -f1

# GOOD
base="${file%%.*}"
printf 'Base filename: %s\n' "${base}"

14. Signals

  • Scripts must trap SIGINT and SIGTERM gracefully.

  • Exit codes should follow the 128 + signal number convention:

    • SIGINT (2) → 128 + 2 = 130
    • SIGTERM (15) → 128 + 15 = 143
      This makes it clear to parent processes or monitoring systems that the script terminated due to a signal.

Example:

function exit_on_signal_sigint() {
    warn "Program interrupted (SIGINT)."
    exit 130
}

function exit_on_signal_sigterm() {
    warn "Program terminated (SIGTERM)."
    exit 143
}

trap exit_on_signal_sigint SIGINT
trap exit_on_signal_sigterm SIGTERM

15. Validation Helpers

Scripts should validate environment and dependencies early:

  • validate_commands
  • validate_env_vars
  • validate_paths_readable
  • validate_paths_writable

16. Formatting & Linting

  • All code must pass:
    • ShellCheck (with project .shellcheckrc)
    • shfmt with options:
      shfmt -i 4 -ci -bn -kp -sr -ln bash
      
  • Indentation: 4 spaces (no tabs).

✅ Summary

This style guide enforces:

  • Safety-first scripting (no set -e, validated inputs, traps).
  • Consistency (4-space indent, snake_case, UPPERCASE constants).
  • Readability (structured logging, proc-docs, display pipes).
  • Portability (Bash 4+, avoid non-portable constructs).

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors