Intro
Bash scripts are an essential tool for any developer or system administrator. They automate repetitive tasks and save time by reducing manual input. However, many developers neglect the quality of their script code, leading to messy, hard-to-maintain files that make your job harder.
In this tutorial, we will explore 20 practical tips for writing clean Bash scripts:
- Start with a clear, descriptive header
- Use meaningful variable names
- Avoid unnecessary repetition with arrays
- Utilize comments and docstrings
- Minimize the use of variables
- Limit input arguments where possible
- Write shell functions instead of inline commands
- Use sed or awk for complex text manipulation
- Handle errors gracefully and exit with a meaningful code
- Keep script size manageable
- Implement error handling in your loops
- Use pipes to streamline multi-command scripts
- Avoid global variables within scripts
- Write shell script comments that describe what the script does
- Utilize conditional statements for multiple scenarios
- Handle complex logic with if-then-else structures
- Keep scripts modular, using subroutines and functions
- Use shebang line to specify interpreter
- Write tests for your scripts before deployment
- Regularly review and refactor scripts
1. Clear Header
Example:
#!/bin/bash
Clean Bash Script: Writing a Simple Bash Script
cat /etc/passwd | grep 'root'
**Output:** On branch main, nothing to commit, working tree clean.
Explanation:
The header is clear and descriptive, indicating the purpose of the script. The grep command looks for lines containing "root" in /etc/passwd.
2. Meaningful Variable Names
Example:
#!/bin/bash
Define variables meaningfully
MY_VAR="Hello World"
echo "$MY_VAR"
**Output:** Hello World
Explanation:
Variable names like MY_VAR are meaningful and easy to understand.
3. Arrays for Repeated Actions
Example:
#!/bin/bash
Array of commands to run multiple times
COMMANDS=("ls" "pwd" "cat /etc/passwd")
for CMD in "${COMMANDS[@]}"
do
$CMD
done
**Output:** On branch main, nothing to commit, working tree clean.
Explanation:
An array of commands can be used to simplify repetitive tasks like ls, pwd, and cat.
4. Comments and Docstrings
Example:
#!/bin/bash
Simple Bash Script: Check File Permissions
echo "Checking file permissions..."
if [ -f "$1" ]; then
echo "File exists"
else
echo "File does not exist"
fi
exit $?
**Output:** Checking file permissions...
Explanation:
Comments like # Simple Bash Script: explain the script's purpose. The shebang line specifies which interpreter to use.
5. Minimize Variables Use
Example:
#!/bin/bash
Minimizing variables usage: Using $1 instead of multiple variables
function check_args() {
echo "Checking arguments..."
if [ -n "$1" ]; then
echo "Argument provided"
else
echo "No argument provided"
fi
}
check_args $1
**Output:** Checking arguments...
Explanation:
Using $1 instead of multiple variables makes the script more readable and maintainable.
6. Error Handling
Example:
#!/bin/bash
Catching Errors: Using if-then structure to handle errors
function check_directory() {
echo "Checking directory..."
if [ -d "$1" ]; then
echo "Directory exists"
cd "$1"
else
echo "Directory does not exist"
fi
}
check_directory $1
**Output:** Checking directory...
Explanation:
A simple if-then structure is used to handle errors and ensure the script runs correctly.
7. Subroutines and Functions
Example:
#!/bin/bash
Writing Bash Script: Using a Function
echo "Checking environment..."
function check_env() {
echo "Environment variables:"
for VAR in $(printenv)
do
echo "$VAR"
done
}
check_env
**Output:** Environment variables:
- PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
- HOME="/root"
Explanation:
A function check_env is defined to print environment variables. The script uses the function instead of nested commands.
8. Conditional Statements
Example:
#!/bin/bash
Conditional Statements: Using if-then structure for different actions
echo "Checking file types..."
if [ -f "$1" ]; then
echo "File exists"
elif [ -d "$1" ]; then
echo "Directory exists"
else
echo "Neither a directory nor a file"
fi
**Output:** Neither a directory nor a file
Explanation:
Using if-then statements allows for different actions based on the input. This script checks if a file or directory exists.
9. Pipes and Multi-Command Scripts
Example:
#!/bin/bash
Using Pipes: Reducing multi-command scripts to one line
echo "Checking environment..."
function check_env() {
echo "Environment variables:"
printenv | grep '^PATH=' | sed -n '$p'
}
check_env
**Output:** Environment variables:
- PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
Explanation:
The script uses echo followed by pipes and commands to check environment variables.
10. Modular Script
Example:
#!/bin/bash
Writing Bash Script: Using Modules for Code Organization
echo "Checking files..."
function list_files() {
echo "Files in current directory:"
ls $1 | grep -vE '^\.$'
}
list_files $PWD
**Output:** Files in current directory:
- README.md
- test.sh
Explanation:
The script is modular, using list_files to handle file listing for different directories.
11. Error Handling with Multiple Scenarios
Example:
#!/bin/bash
Catching Errors: Using multiple if-then-else structures
echo "Checking configuration..."
function check_config() {
echo "Checking configuration..."
if [ -f "$1" ]; then
echo "Configuration file exists"
elif [ -d "$1" ]; then
echo "Directory for configuration does not exist"
else
echo "Neither a directory nor a file"
fi
}
check_config $1
**Output:** Checking configuration...
Explanation:
The script checks different scenarios using multiple if-then-else statements.
12. Complex Logic with Conditional Statements
Example:
#!/