How to Do a Code Review of Bash Scripts

karandaid

Karandeep Singh

Posted on May 29, 2024

How to Do a Code Review of Bash Scripts

Conducting a code review for Bash scripts is essential to ensure they are error-free, secure, and easy to maintain. Reviewing Bash scripts helps catch mistakes early, improve code quality, and ensures best practices are followed. Here's a detailed guide on how to review Bash scripts effectively, with explanations and examples of good and bad code for each step.

1. Understand the Purpose of the Script

Before reviewing, understand what the script is supposed to do. This helps in contextualizing the code and spotting deviations.

Good:

# This script backs up the user's home directory to /backup
Enter fullscreen mode Exit fullscreen mode

Bad:

# backup script
Enter fullscreen mode Exit fullscreen mode

2. Check for Shebang and Execution Permissions

Ensure the script starts with a shebang to specify the interpreter and that it has executable permissions.

Good:

#!/bin/bash
chmod +x script.sh
Enter fullscreen mode Exit fullscreen mode

Bad:

#!/bin/sh
Enter fullscreen mode Exit fullscreen mode

3. Syntax and Semantics

Look for syntax errors and semantic issues. Use tools like shellcheck to detect common mistakes.

Good:

if [ -f "$file" ]; then
    echo "File exists."
fi
Enter fullscreen mode Exit fullscreen mode

Bad:

if [ -f "$file" ] then
    echo "File exists."
Enter fullscreen mode Exit fullscreen mode

4. Readability and Maintainability

Check for proper indentation, meaningful variable names, and adequate comments.

Good:

for file in *.txt; do
    echo "Processing $file"
done
Enter fullscreen mode Exit fullscreen mode

Bad:

for f in *.txt; do echo "Processing $f"; done
Enter fullscreen mode Exit fullscreen mode

5. Error Handling

Ensure the script handles errors gracefully using proper error handling mechanisms.

Good:

set -euo pipefail
trap 'echo "Error occurred"; exit 1' ERR
Enter fullscreen mode Exit fullscreen mode

Bad:

# No error handling
Enter fullscreen mode Exit fullscreen mode

6. Security Considerations

Look for potential security issues like unchecked user input and improper handling of sensitive data.

Good:

if [[ "$user_input" =~ ^[a-zA-Z0-9_]+$ ]]; then
    echo "Valid input"
fi
Enter fullscreen mode Exit fullscreen mode

Bad:

eval $user_input
Enter fullscreen mode Exit fullscreen mode

7. Performance and Efficiency

Assess the script for performance bottlenecks and unnecessary use of resources.

Good:

grep "pattern" file.txt
Enter fullscreen mode Exit fullscreen mode

Bad:

cat file.txt | grep "pattern"
Enter fullscreen mode Exit fullscreen mode

8. Adherence to Best Practices

Ensure the script follows best practices for Bash scripting.

Good:

result=$(command)
Enter fullscreen mode Exit fullscreen mode

Bad:

result=`command`
Enter fullscreen mode Exit fullscreen mode

9. Dependency Management

Identify any external dependencies and ensure they are clearly documented.

Good:

# Requires rsync
if ! command -v rsync &> /dev/null; then
    echo "rsync could not be found"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Bad:

rsync -avh source/ destination/
Enter fullscreen mode Exit fullscreen mode

10. Portability

Check if the script uses features or commands specific to a particular shell or system.

Good:

# POSIX compliant
if [ -d "$DIR" ]; then
    echo "Directory exists."
fi
Enter fullscreen mode Exit fullscreen mode

Bad:

[[ -d "$DIR" ]] && echo "Directory exists."
Enter fullscreen mode Exit fullscreen mode

11. Documentation

Verify that the script includes a header comment explaining its purpose and usage instructions.

Good:

# Script to backup user's home directory
# Usage: ./backup.sh
Enter fullscreen mode Exit fullscreen mode

Bad:

# Backup script
Enter fullscreen mode Exit fullscreen mode

12. Testing

Ensure the script has been tested in different environments and scenarios.

Good:

# Test script
./test_backup.sh
Enter fullscreen mode Exit fullscreen mode

Bad:

# No testing
Enter fullscreen mode Exit fullscreen mode

13. Variable Naming

Use meaningful and descriptive variable names to improve readability.

Good:

file_count=0
Enter fullscreen mode Exit fullscreen mode

Bad:

fc=0
Enter fullscreen mode Exit fullscreen mode

14. Avoid Hardcoding Values

Use variables instead of hardcoding values to make the script more flexible.

Good:

backup_dir="/backup"
Enter fullscreen mode Exit fullscreen mode

Bad:

cd /backup
Enter fullscreen mode Exit fullscreen mode

15. Use Functions for Reusable Code

Encapsulate reusable code in functions to improve modularity and readability.

Good:

backup_files() {
    tar -czf backup.tar.gz /home/user
}
Enter fullscreen mode Exit fullscreen mode

Bad:

tar -czf backup.tar.gz /home/user
Enter fullscreen mode Exit fullscreen mode

16. Check Command Success

Always check if a command succeeded and handle the failure case appropriately.

Good:

if ! cp source.txt destination.txt; then
    echo "Copy failed"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Bad:

cp source.txt destination.txt
Enter fullscreen mode Exit fullscreen mode

17. Use Meaningful Exit Codes

Use appropriate exit codes to indicate the script's status.

Good:

exit 0
Enter fullscreen mode Exit fullscreen mode

Bad:

exit 1
Enter fullscreen mode Exit fullscreen mode

18. Avoid Useless Use of cat

Combine commands to avoid unnecessary use of cat.

Good:

grep "pattern" file.txt
Enter fullscreen mode Exit fullscreen mode

Bad:

cat file.txt | grep "pattern"
Enter fullscreen mode Exit fullscreen mode

19. Quotes Around Variables

Always quote variables to prevent word splitting and globbing issues.

Good:

echo "File: $file"
Enter fullscreen mode Exit fullscreen mode

Bad:

echo File: $file
Enter fullscreen mode Exit fullscreen mode

20. Avoid Global Variables

Use local variables within functions to avoid side effects.

Good:

main() {
    local file_count=0
}
Enter fullscreen mode Exit fullscreen mode

Bad:

file_count=0
Enter fullscreen mode Exit fullscreen mode

21. Proper Use of Arrays

Use arrays for lists of items to simplify the code.

Good:

files=(file1.txt file2.txt)
for file in "${files[@]}"; do
    echo "Processing $file"
done
Enter fullscreen mode Exit fullscreen mode

Bad:

file1=file1.txt
file2=file2.txt
for file in $file1 $file2; do
    echo "Processing $file"
done
Enter fullscreen mode Exit fullscreen mode

22. Avoiding Command Substitution in Loops

Avoid using command substitution within loops for better performance.

Good:

while read -r line; do
    echo "$line"
done < file.txt
Enter fullscreen mode Exit fullscreen mode

Bad:

for line in $(cat file.txt); do
    echo "$line"
done
Enter fullscreen mode Exit fullscreen mode

23. Proper Use of printf

Use printf instead of echo for better formatting control.

Good:

printf "File: %s\n" "$file"
Enter fullscreen mode Exit fullscreen mode

Bad:

echo "File: $file"
Enter fullscreen mode Exit fullscreen mode

24. Check for Unset Variables

Use set -u to treat unset variables as an error.

Good:

set -u
echo "Variable: ${var:-default}"
Enter fullscreen mode Exit fullscreen mode

Bad:

echo "Variable: $var"
Enter fullscreen mode Exit fullscreen mode

25. Proper Use of trap

Use trap to handle cleanup tasks and ensure they run even if the script exits unexpectedly.

Good:

trap 'rm -f temp.txt; exit' INT TERM
Enter fullscreen mode Exit fullscreen mode

Bad:

# No cleanup
Enter fullscreen mode Exit fullscreen mode

26. Avoiding Multiple Redirections

Combine redirections to avoid multiple file handles.

Good:

{
    echo "Line 1"
    echo "Line 2"
} > output.txt
Enter fullscreen mode Exit fullscreen mode

Bad:

echo "Line 1" > output.txt
echo "Line 2" >> output.txt
Enter fullscreen mode Exit fullscreen mode

27. Using Built-in Shell Commands

Prefer built-in shell commands over external utilities where possible.

Good:

files=$(ls)
Enter fullscreen mode Exit fullscreen mode

Bad:

files=$(ls -1)
Enter fullscreen mode Exit fullscreen mode

28. Avoiding the Use of eval

Avoid eval to prevent potential security risks.

Good:

cmd="ls"
$cmd
Enter fullscreen mode Exit fullscreen mode

Bad:

eval $cmd
Enter fullscreen mode Exit fullscreen mode

29. Proper Use of read

Use read with proper options to handle input safely.

Good:

read -r user_input
Enter fullscreen mode Exit fullscreen mode

Bad:

read user_input
Enter fullscreen mode Exit fullscreen mode

30. Using || and && for Command Chaining

Use || and && for conditional command execution.

Good:

command1 && command2
command1 || echo "Command1 failed"
Enter fullscreen mode Exit fullscreen mode

Bad:

if command1; then
    command2
fi
if ! command1; then
    echo "Command1 failed"
fi
Enter fullscreen mode Exit fullscreen mode

31. Using case Instead of Multiple if Statements

Use case for multiple conditions to improve readability.

Good:

case $var in
    pattern1) echo "Pattern 1";;
    pattern2) echo "Pattern 

2";;
esac
Enter fullscreen mode Exit fullscreen mode

Bad:

if [ "$var" == "pattern1" ]; then
    echo "Pattern 1"
elif [ "$var" == "pattern2" ]; then
    echo "Pattern 2"
fi
Enter fullscreen mode Exit fullscreen mode

32. Properly Handling File Descriptors

Use file descriptors to manage input/output streams efficiently.

Good:

exec 3< input.txt
while read -r line <&3; do
    echo "$line"
done
exec 3<&-
Enter fullscreen mode Exit fullscreen mode

Bad:

while read -r line; do
    echo "$line"
done < input.txt
Enter fullscreen mode Exit fullscreen mode

33. Using select for Menu Options

Use select to create simple menus.

Good:

select option in "Option 1" "Option 2" "Quit"; do
    case $option in
        "Option 1") echo "You chose Option 1";;
        "Option 2") echo "You chose Option 2";;
        "Quit") break;;
    esac
done
Enter fullscreen mode Exit fullscreen mode

Bad:

echo "1. Option 1"
echo "2. Option 2"
echo "3. Quit"
read -r choice
case $choice in
    1) echo "You chose Option 1";;
    2) echo "You chose Option 2";;
    3) exit;;
esac
Enter fullscreen mode Exit fullscreen mode

34. Using dirname and basename

Use dirname and basename to handle file paths.

Good:

dir=$(dirname "$file_path")
file=$(basename "$file_path")
Enter fullscreen mode Exit fullscreen mode

Bad:

dir=${file_path%/*}
file=${file_path##*/}
Enter fullscreen mode Exit fullscreen mode

35. Using mktemp for Temporary Files

Use mktemp to create temporary files securely.

Good:

tmpfile=$(mktemp)
echo "Temporary file: $tmpfile"
Enter fullscreen mode Exit fullscreen mode

Bad:

tmpfile="/tmp/tempfile.$$"
echo "Temporary file: $tmpfile"
Enter fullscreen mode Exit fullscreen mode

By following these guidelines and using these examples, you can conduct a thorough and effective code review of Bash scripts, ensuring they are robust, secure, and maintainable.

For more advanced Bash scripting tips, check out this article on Advanced String Operations in Bash: Building Custom Functions.

💖 💪 🙅 🚩
karandaid
Karandeep Singh

Posted on May 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related