Skip to content

Bash Style Guide

What is a Bash/Shell script

A bash or shell script is a plain file that contains a sequence of commands that are executed by an interpreter, in this case the Bash shell.

Although all the commands in a bash script can be ran at the command line, when a certain task requires more than 5 lines of code and is frequently used, you might aswell automate the process and create a shell script that can be executed in the background and free time for other tasks.

Now, when creating a shell script there are certain best practices to follow to ensure a high standard of security, stability, maintainability and reliability. Below you will find different tips and tricks that will help you achieve the above mentioned aspects and make you a well-seasoned Bash programmer.

When should you use a Bash script

Honestly, you can use bash scripts for anything you want, however in a lot of cases outside of system administration it would probably not be very useful and not make sense.

Therefore, as a rule of thumb I'd suggest to NOT use bash scripts if your tool:

- requires the creation of complex data structures
- exceeds 300 - 400 lines of code (you'd probably find out about that while coding)
- deals with a lot of math
- needs to handle many transactions, very fast and be memory efficient

But I would definitely suggest using bash scripts if your tool:

- wraps around a small set of more poweful command line tools
- handles system administrative tasks that can only be done via the command line
- integrates well into a bigger application, e.g. a software installation script
- does not do much data manipulation

Info

The above list of conditions is not exhaustive and there are always exceptions.

Style Guides

Formatting

Identation

Identation is not crucial like in other interpreted programming languages, e.g. Python. Nevertheless, indent 2 space. No tabs!

Use blank lines between blocks to improve readability. Indentation is two spaces. Whatever you do, don’t use tabs. For existing files, stay faithful to the existing indentation.

Pipelines

Pipelines should be split one per line if they don’t all fit on one line.

If a pipeline all fits on one line, it should be on one line.

If not, it should be split at one pipe segment per line with the pipe on the newline and a 2 space indent for the next section of the pipe. should be consistently used to indicate line continuation. This applies to a chain of commands combined using | as well as to logical compounds using || and &&.

Example:

# All fits on one line
command1 | command2

# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

Control Flow

Put ; then and ; do on the same line as the if, for, or while.

Control flow statements in shell are a bit different, but we follow the same principles as with braces when declaring functions. That is: ; then and ; do should be on the sameline as the if/for/while/until/select. else should be on its own line and closing statements (fi and done) should be on their own line vertically aligned with the opening statement.

Example:

# If inside a function remember to declare the loop variable as
# a local to avoid it leaking into the global environment:
local dir
for dir in "${dirs_to_cleanup[@]}"; do
  if [[ -d "${dir}/${SESSION_ID}" ]]; then
    log_date "Cleaning up old files in ${dir}/${SESSION_ID}"
    rm "${dir}/${SESSION_ID}/"* || error_message
  else
    mkdir -p "${dir}/${SESSION_ID}" || error_message
  fi
done

Comments

It is good practice to always include a header comment in your script after the shebang (#!/bin/bash), that contains the scripts name, description, author, creation date and version.

Example:

#!/bin/bash
#
# Name:         linux-suspend
# Date:         10 September 2024
# Author:       Christian Goeschel Ndjomouo <cgoesc2@wgu.edu>
# Description:  This script bypasses any systemd inhibitor that blocks a system suspend tasks
#               and then forces system suspension via /sys/power.

Aliases

Simply, avoid them and use functions instead.

The reason for this is because aliases require careful quoting and escaping, which reduces readability, is error prone and leads to avoidable debugging sessions.

Example:

# This command simply prints the battery capacity level and its status.
# Notice how this already looks terrible ? Now, imagine you want to add more functionality and logic,
# you will end up with a long command alias that is hardly readable.
alias battery_info='echo -e "Battery Level: $(cat /sys/class/power_supply/macsmc-battery/capacity)%\nBattery Status: $(cat /sys/class/power_supply/macsmc-battery/status)"'

# Instead do this
battery_info() {
    local capacity
    local status
    capacity="$(cat /sys/class/power_supply/macsmc-battery/capacity)"
    status="$(cat /sys/class/power_supply/macsmc-battery/status)"

    printf "Battery Level: %d%\nBattery Status: %s\n" "${capacity}" "${status}"
}

Variables

Functions

Functions are a great way to structure and organize your code and create a streamlined control flow. This is encouraged in more complex scripts that are not just linear and relatively longer.

Naming convention

Lower-case, with underscores to separate words. Separate libraries with ::. Parentheses are required after the function name. The keyword function is optional, but must be used consistently throughout a project.

If you’re writing single functions, use lowercase and separate words with underscore. If you’re writing a package, separate package names with ::. However, functions intended for interactive use may choose to avoid colons as it can confuse bash auto-completion.

Braces must be on the same line as the function name (as with other languages at Google) and no space between the function name and the parenthesis.

Example:

# Single function
my_func() {
  }

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

The function keyword is extraneous when “()” is present after the function name, but enhances quick identification of functions.

Formatting

Here are examples of correct function formatting:

function my_function() {
    command ...
}

my_function() {
    command ...
}

Location

Functions should be placed closely below global variable declarations.