How to Write Cleaner Bash Scripts (20 Examples)

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:

  1. Start with a clear, descriptive header
  2. Use meaningful variable names
  3. Avoid unnecessary repetition with arrays
  4. Utilize comments and docstrings
  5. Minimize the use of variables
  6. Limit input arguments where possible
  7. Write shell functions instead of inline commands
  8. Use sed or awk for complex text manipulation
  9. Handle errors gracefully and exit with a meaningful code
  10. Keep script size manageable
  11. Implement error handling in your loops
  12. Use pipes to streamline multi-command scripts
  13. Avoid global variables within scripts
  14. Write shell script comments that describe what the script does
  15. Utilize conditional statements for multiple scenarios
  16. Handle complex logic with if-then-else structures
  17. Keep scripts modular, using subroutines and functions
  18. Use shebang line to specify interpreter
  19. Write tests for your scripts before deployment
  20. 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:

#!/