Skip to main content
Skip to main content

tldr

This document is a brief overview how to structure your bash scripts. To get a more detailed explanation (e.g. arrays, subprocesses, etc.), please read the Styleguide.

Shebang​

The first line of your script should be the shebang. This is the line that tells the system what interpreter to use to run the script. Always use #!/usr/bin/env bash instead of #!/bin/bash or #!/usr/bin/bash because it's more portable.

#!/usr/bin/env bash

Strict mode​

The strict mode is a set of options that make the script more robust and less error-prone. It's a good practice to always use it.

set -euo pipefail

Environment variables​

If you have any environment variables that your script depends on, define/show them at the top of the script.

: "${LOG_LEVEL:="info"}"
: "${LOG_ENABLED}"

Use := to provide a default value for the variable. If don't want that the variable can be set by the user, and is only used internally, declare them directly. If these are imutable, use readonly to make them readonly.

LOG_LEVEL="info"
readonly LOG_LEVEL

::: note

If you want to use the LOG_LEVEL in sub processes, you have to export it with export LOG_LEVEL.

:::

Constants​

If you have any constants, define them at the top of the script.

readonly path="${BASH_SOURCE[0]%/*}"

Sourcing​

If you have any functions or other scripts that you want to use in your script, source them at the top of the script.

source "${path}/functions.sh"

Functions​

Define your functions after the constants and sourcing. This makes it easier to find the main part of the script.

hello_world() {
local name="${1:-world}"
echo "Hello, ${name}!"
}

Declare your function parameters with local to make them local to the function. You may use -r to make them readonly. Use :- to provide a default value for the parameter.

Namespacing​

If you have a lot of functions, you can namespace them by using a prefix.

to::boolean() {
local -r value="${1}"
case "${value}" in
false|no|0) return 1 ;;
* ) return 0 ;;
esac
}

main() {
if to::boolean "${LOG_ENABLED}"; then
echo "Logging is enabled"
else
echo "Logging is disabled"
fi
}

Variables and name conventions​

Use lowercase for variable names. Use _ to separate words. Enclose the variable name in ${} to make it clear where the variable name starts and ends. Additionally, this makes it easier to use Bash expansions.

External commands​

Try to avoid external commands like grep, awk, sed, etc. Use Bash builtins instead, fall back to coreutils if necessary. If you are using external commands, make sure to check if they are available or provide them (e.g. with the binary library).

if ! command -v jq &> /dev/null; then
echo "jq could not be found"
exit 1
fi

Test expressions​

Use [[ instead of [ for test expressions. It's more powerful and has less surprises. Use arithmetic expressions (( for arithmetic operations/tests.

local check=1

(( check == 1 )) || {
echo "Check failed"
exit 1
}
Note

Prefer || over && for assert-like checks — it reads more naturally as "condition or handle failure": [[ <expr> ]] || { echo "failed"; exit 1; }

With set -e, the left-hand side of &&/|| is exempt from errexit — only the command after the final && or || can trigger an exit. In the pattern above, the [[ ]] failure is safe, and the { ...; exit 1; } handles the error explicitly.

Main​

The main part of the script should be at the bottom of the script. This is where you call the functions and do the work.

main() {
echo "Hello, world!"
}

Call main​

Call the main function at the bottom of the script. Check if the script is being run directly or sourced.

[[ "${BASH_SOURCE[0]}" != "${0}" ]] || main "${@}"
Was this section helpful?