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