Ubuntu Script Debugging
Introduction
Debugging is an essential skill for any programmer, and shell script debugging is no exception. When writing shell scripts in Ubuntu, you'll inevitably encounter errors and unexpected behaviors that need troubleshooting. This guide will walk you through various techniques and tools to effectively debug your Ubuntu shell scripts, helping you identify and fix issues efficiently.
Understanding Shell Script Errors
Before diving into debugging techniques, it's important to understand the common types of errors you might encounter in shell scripts:
- Syntax Errors: Issues with the script's syntax that prevent it from executing
- Runtime Errors: Errors that occur while the script is running
- Logical Errors: The script runs without errors but doesn't produce the expected results
Basic Debugging Techniques
1. Adding the -x
Option
One of the simplest ways to debug a shell script is by using the -x
option. This enables "tracing mode," which displays each command and its arguments as they are executed.
You can use it in two ways:
Method 1: Add it to the shebang line:
#!/bin/bash -x
# Your script
echo "Hello World"
name="John"
echo "My name is $name"
Method 2: Use it when executing the script:
bash -x myscript.sh
Output example:
+ echo 'Hello World'
Hello World
+ name=John
+ echo 'My name is John'
My name is John
The +
sign at the beginning of each line indicates commands being executed, allowing you to follow the script's flow.
2. Using set
Commands for Selective Debugging
Sometimes you may want to debug only specific parts of your script. The set
command allows you to turn debugging on and off within your script.
#!/bin/bash
echo "Regular execution"
set -x # Turn on debugging
echo "Debugging started"
name="John"
echo "My name is $name"
set +x # Turn off debugging
echo "Back to normal execution"
Output:
Regular execution
+ echo 'Debugging started'
Debugging started
+ name=John
+ echo 'My name is John'
My name is John
+ set +x
Back to normal execution
3. Adding Echo Statements
A straightforward approach is to add echo
statements throughout your script to track variable values and execution flow:
#!/bin/bash
filename="data.txt"
echo "DEBUG: filename = $filename"
if [ -f "$filename" ]; then
echo "DEBUG: File exists, continuing..."
# process file
else
echo "DEBUG: File does not exist, exiting..."
exit 1
fi
Advanced Debugging Options
1. Using -e
for Error Handling
The -e
option makes your script exit immediately if any command returns a non-zero status:
#!/bin/bash -e
echo "This will execute"
nonexistentcommand # This will cause the script to exit
echo "This will never execute"
2. Combining Options with set
You can combine multiple debugging options:
#!/bin/bash
# Enable error tracing and immediate exit on error
set -ex
echo "Command tracing is on"
# Your script commands here
Common useful options include:
-e
: Exit immediately on error-u
: Treat unset variables as errors-o pipefail
: Return the exit status of the last command in a pipe that failed-v
: Print shell input lines as they are read
3. Using trap
for Error Handling
The trap
command allows you to capture signals and execute code when they occur:
#!/bin/bash
trap 'echo "Line $LINENO: Command failed with exit code $?"' ERR
echo "This command will succeed"
nonexistentcommand # This will trigger the trap
echo "This might not execute depending on your script configuration"
Practical Debugging Tools
1. shellcheck
- Static Analysis Tool
shellcheck
is a powerful static analysis tool that identifies potential issues in your shell scripts before you execute them.
Installation:
sudo apt-get install shellcheck
Usage:
shellcheck myscript.sh
Example output:
In myscript.sh line 10:
if [ $count -eq 0 ]
^----^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ "$count" -eq 0 ]
2. Using PS4
for Enhanced Debugging Output
You can customize the debug output prefix by setting the PS4
variable:
#!/bin/bash
# Customize debug output to include line numbers
export PS4='+(${BASH_SOURCE}:${LINENO}): '
set -x
echo "Hello World"
name="John"
echo "My name is $name"
Output:
+(myscript.sh:7): echo 'Hello World'
Hello World
+(myscript.sh:8): name=John
+(myscript.sh:9): echo 'My name is John'
My name is John
3. Creating a Debugging Function
For larger scripts, you might want to create a dedicated debugging function:
#!/bin/bash
# Debug level: 0=none, 1=errors, 2=warnings, 3=info
DEBUG_LEVEL=2
debug() {
level=$1
shift
message="$@"
if [ $level -le $DEBUG_LEVEL ]; then
case $level in
1) echo "[ERROR] $message" ;;
2) echo "[WARNING] $message" ;;
3) echo "[INFO] $message" ;;
esac
fi
}
# Using the debug function
debug 3 "Script started"
filename="data.txt"
debug 3 "Processing file: $filename"
if [ ! -f "$filename" ]; then
debug 1 "File not found: $filename"
exit 1
fi
debug 3 "Script completed successfully"
Real-World Debugging Example
Let's apply these techniques to debug a practical script that processes log files:
#!/bin/bash
log_processor() {
set -x # Enable debugging for this function
local log_dir=$1
local output_file=$2
debug 3 "Processing logs in directory: $log_dir"
debug 3 "Output will be saved to: $output_file"
# Create or clear output file
> "$output_file"
# Check if directory exists
if [ ! -d "$log_dir" ]; then
debug 1 "Directory does not exist: $log_dir"
return 1
fi
# Process each log file
for log_file in "$log_dir"/*.log; do
debug 3 "Processing file: $log_file"
# Skip if glob doesn't match any files
if [ ! -f "$log_file" ]; then
debug 2 "No log files found in $log_dir"
break
fi
# Extract ERROR lines and append to output file
grep "ERROR" "$log_file" >> "$output_file" || {
debug 2 "No ERROR entries found in $log_file"
}
done
set +x # Disable debugging for this function
return 0
}
# Setup debug function
DEBUG_LEVEL=3
debug() {
level=$1
shift
message="$@"
if [ $level -le $DEBUG_LEVEL ]; then
timestamp=$(date "+%Y-%m-%d %H:%M:%S")
case $level in
1) echo "[$timestamp] [ERROR] $message" ;;
2) echo "[$timestamp] [WARNING] $message" ;;
3) echo "[$timestamp] [INFO] $message" ;;
esac
fi
}
# Main script execution
debug 3 "Script started"
log_processor "/var/log" "error_summary.txt"
exit_code=$?
if [ $exit_code -ne 0 ]; then
debug 1 "Log processing failed with exit code: $exit_code"
exit 1
fi
debug 3 "Script completed successfully"
Debugging Shell Script Flow
Understanding the flow of your script is crucial for effective debugging. Here's a simple flowchart showing a typical debugging process:
Common Debugging Scenarios and Solutions
Scenario 1: Script Fails Silently
Problem: Your script exits without any error message
Solution:
#!/bin/bash
set -e # Exit on error
set -o pipefail # Catch pipe errors
set -u # Catch unset variables
# Add error trap
trap 'echo "Error on line $LINENO. Exit code: $?"' ERR
# Your script here
Scenario 2: Debugging Variables
Problem: You're unsure if variables contain expected values
Solution:
#!/bin/bash
# Print all script variables
dump_vars() {
echo "--- Variable Dump ---"
echo "USER_INPUT = $USER_INPUT"
echo "COUNT = $COUNT"
echo "FILENAME = $FILENAME"
echo "---------------------"
}
USER_INPUT="test"
COUNT=5
FILENAME="data.txt"
dump_vars
# Later in the script
process_data "$USER_INPUT" "$COUNT"
dump_vars # Check if variables changed
Scenario 3: Isolating Problems in Complex Scripts
Problem: A complex script has an issue, but you're not sure where
Solution: Comment out sections of code to isolate the problem
#!/bin/bash
# Section 1
echo "Executing Section 1"
# ...code for section 1...
# Section 2
echo "Executing Section 2"
# Comment out to isolate problems
# if [ some_condition ]; then
# ...problematic code...
# fi
# Section 3
echo "Executing Section 3"
# ...code for section 3...
Summary
Debugging shell scripts in Ubuntu involves a combination of built-in tools, custom functions, and systematic approaches to problem-solving. The key techniques we covered include:
- Using bash options like
-x
,-e
, and-u
for tracing and error handling - Implementing selective debugging with
set -x
andset +x
- Customizing debug output with
PS4
- Creating custom debugging functions
- Utilizing external tools like
shellcheck
- Implementing error trapping with
trap
By mastering these techniques, you'll be able to identify and fix issues in your shell scripts more efficiently, improving your productivity and the reliability of your scripts.
Exercises for Practice
- Debug a script that fails due to unquoted variables
- Create a custom debugging function with different verbosity levels
- Use
shellcheck
to identify potential issues in an existing script - Implement error trapping to handle specific error conditions
- Debug a script that processes files in a directory, handling edge cases
Additional Resources
- The bash manual (
man bash
) - The "Advanced Bash-Scripting Guide" available in the Ubuntu documentation
- ShellCheck documentation: shellcheck.net
- Bash Debugger (bashdb) for more advanced debugging needs
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)