Bash style guide

Style Guide

Based on Community Bash Style Guide

When to use bash

  • ✔️ It need to glue userland utilities together.
  • ❌ Do complex tasks (e.g. database queries).

Style conventions

  • Use the #!/usr/bin/env bash shebang wherever possible.

  • Memorize and utilize set -eu -o pipefail at the very beginning of your code.

    • Never write a script without set -e at the very very beginning.
      • This instructs bash to terminate in case a command or chain of command finishes with a non-zero exit status, avoiding unhandled error conditions.
      • Use constructs like if myprogramm --parameter ; then ... for calls that might fail and require specific error handling. Use a cleanup trap for everything else.
    • Use set -u in your scripts.
      • This will terminate your scripts in case an uninitialized variable is accessed.
      • Uninitialized variables will fail in case it’s used in another script which sets the -u flag, better for security.
    • Use set -o pipefail to get an exit status from a pipeline (last non-zero will be returned).
  • Never use TAB for indentation.

    • Consistently use two (2) or four (4) character indentation.
  • Always put parameters in double-quotes: util "--argument" "${variable}".

  • Avoid putting if .. then, while .. do, for .. do, case .. in on a new line.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if ${event}; then
    ...
    fi

    while ${event}; do
    ...
    done

    for v in ${list[@]}; do
    ...
    done
  • Never forget that you cannot put a space/blank between a variable name and it’s value during an assignment

    1
    2
    RET1=false   # assign
    RET2 = false # will not assign
  • Always set local function variables local.

  • Write clear code.

    • Never obfuscate what the script is trying to do.
    • Never shorten uncessesarily with a lot of commands per line of code chained with a semicolon.
  • Bash does not have a concept of public and private functions.

    • Public functions get generic names, whereas.
    • Private functions are prepended by two underscores (RedHatconvention).
  • Try to stick to the pushd, popd, and dirs builtins for directory stack manipulation where sensible.

  • Every line must have a maximum of eighty (80) terminal columns

  • Like in other dynamic languages, switch/case blocks should be aligned:

    1
    2
    3
    4
    5
    case ${contenders}; in
    teller) x=4 ;;
    ulam) c=1 ;;
    neumann) v=7 ;;
    esac
  • Only trap / handle signals you actually do care about.

  • Use the builtin readonly when declaring constants and immutable variable.

  • Assign integer variables, arrays, etc. with typeset/declare (see also).

  • Always work with return values instead of strings passed from a function or userland utility (where applicable).

  • Write generic small check functions instead of large init and clean-up code.

    1
    2
    3
    4
    5
    6
    7
    # both functions return non-zero on error
    function is_valid_string?() {
    [[ $@ =~ ^[A-Za-z0-9]*$ ]]
    }
    function is_integer?() {
    [[ $@ =~ ^-?[0-9]+$ ]]
    }
  • Be as modular and plugable as possible. If a project gets bigger, split it up into smaller files with clear and obvious naming scheme.

  • Clearly document code parts that are not easily understood (long chains of piped commands for example).

  • Try to stick to restricted mode where sensible and possible to use.

    • Use set -r with caution: while this flag is very useful for security sensitive environments, scripts have to be written with the flag in mind.
    • Adding restricted mode to an existing script will most likely break it.
  • Scripts should somewhat reflect the following general layout:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #!/usr/bin/env bash
    #
    # AUTHORS, LICENSE and DOCUMENTATION
    #
    set -eu -o pipefail

    Readonly Variables
    Global Variables

    Import ("source scriptname") of external source code

    Functions
    `-. function local variables
    `-. clearly describe interfaces: return either a code or string

    Main
    `-. option parsing
    `-. log file and syslog handling
    `-. temp. file and named pipe handling
    `-. signal traps

    ------------------------------------------------------------------------
    To keep in mind:
    - quoting of all variables passed when executing sub-shells or cli tools
    - testing of functions, conditionals and flow (see style guide)
    - makes restricted mode ("set -r") for security sense here?
  • Silence is golden - like in any UNIX programm, avoid cluttering the terminal with useless output.

Resources

Linting and static analysis

Portability

Test driven development and Unit testing

Profiling