B: Debugging Reference#
Solutions to the most frequent Bash scripting errors and debugging techniques.
Common Errors and Solutions#
1. “Command not found”#
Causes:
Typo in command name
Command not in PATH
Script file not executable
Trying to run system command that doesn’t exist
Solutions:
# Check if command exists
command -v mycommand
which mycommand
type mycommand
# Make script executable
chmod +x script.sh
# Run with explicit interpreter
bash script.sh
2. “Permission denied”#
Causes:
Script not executable
No execute permissions on parent directories
Insufficient permissions for file operation
Solutions:
# Add execute permission
chmod +x script.sh
# Check current permissions
ls -l script.sh
# Make directory traversable
chmod +x /path/to/dir
# Run with sudo if needed (carefully)
sudo ./script.sh
3. “No such file or directory”#
Causes:
File doesn’t exist
Wrong path (relative vs absolute)
File deleted between check and use
Typo in filename
Solutions:
# Verify file exists before using
if [[ -f "$file" ]]; then
cat "$file"
else
echo "File not found: $file" >&2
exit 1
fi
# Check if path is correct
pwd
ls "$file"
# Use absolute paths when possible
cat /full/path/to/file
4. Variable is Empty or Unset#
Causes:
Variable never assigned
Variable in different scope
Export not used for child processes
Variable cleared during pipeline
Solutions:
# Check if variable is set
if [[ -z "$var" ]]; then
echo "Variable is empty"
fi
# Provide default value
value="${var:-default}"
# Export for child processes
export MY_VAR="value"
source script.sh # Uses MY_VAR
# Avoid subshell issues
var="initial"
echo "test" | while read line; do
var="$line"
done
echo "$var" # Still "initial"!
# Fix with process substitution
while read line; do
var="$line"
done < <(echo "test")
echo "$var" # Now "test"
5. Word Splitting Issues#
Causes:
Variables not quoted
Command substitution not quoted
Array expansion without quotes
Solutions:
# WRONG - word splitting occurs
files=$(ls *.txt)
for f in $files; do
echo "$f" # Breaks on spaces in filenames
done
# CORRECT - quote variables
for f in $files; do
echo "$f" # Still breaks - use array instead
done
# BEST - use array
mapfile -t files < <(find . -name "*.txt")
for f in "${files[@]}"; do
echo "$f" # Works with any filename
done
6. Quote and Escaping Problems#
Causes:
Mixed quote types
Special characters not escaped
Regex not properly quoted
Variable expansion when not wanted
Solutions:
# Single quotes prevent all expansion
echo '$VAR' # Literal: $VAR
# Double quotes allow expansion
echo "$VAR" # Expanded value
# Escape special characters
echo "Price is \$100" # Literal: $100
echo "Path: \$HOME" # Literal: $HOME
echo "Backslash: \\" # Literal: \
# Use $'...' for special characters
echo $'Line1\nLine2' # Newline
echo $'Tab\tSeparated' # Tab
7. Exit Code Problems#
Causes:
Not checking exit codes
Using wrong variable ($? gets overwritten)
Pipeline hiding errors
Forgetting to check specific commands
Solutions:
# Check immediately after command
if ! command; then
echo "Command failed with exit code $?"
exit 1
fi
# Save exit code immediately
command
exit_code=$?
if [[ $exit_code -ne 0 ]]; then
echo "Failed with code $exit_code"
fi
# Check all commands in pipeline
set -o pipefail
cat file.txt | grep pattern | sort
# Use || and && for simple cases
command && echo "Success" || echo "Failed"
8. Quoting Arguments with Spaces#
Causes:
Passing arguments with spaces without quoting
Function arguments not quoted
Array subscripts not quoted
Solutions:
# WRONG
function copy_file() {
cp $1 $2 # Breaks if $1 has spaces
}
# CORRECT
function copy_file() {
cp "$1" "$2" # Properly handles spaces
}
# WRONG - word splitting in function call
copy_file my file.txt # Treated as 3 args!
# CORRECT
copy_file "my file.txt" "output file.txt"
9. Iteration Over Arrays#
Causes:
Using
$arrayinstead of${array[@]}Not quoting array expansion
Using wrong array syntax
Solutions:
arr=(file1.txt "file 2.txt" file3.txt)
# WRONG - only first element
for f in $arr; do
echo "$f" # Only: file1.txt
done
# WRONG - word splitting
for f in ${arr}; do
echo "$f" # Breaks on spaces
done
# CORRECT - iterate all elements
for f in "${arr[@]}"; do
echo "$f" # All files properly handled
done
# Correct with indirect variable
mapfile -t lines < file.txt
for line in "${lines[@]}"; do
process "$line"
done
10. Function Return Values#
Causes:
Using return for output (only sets exit code)
Not capturing function output
Mixing stdout and return codes
Solutions:
# WRONG - return is for exit codes only
function get_name() {
return "John" # Sets exit code to "John" (invalid)
}
# For output, use echo
function get_name() {
echo "John"
}
name=$(get_name)
# For exit codes, use return
function is_valid() {
[[ -f "$1" ]] && return 0 || return 1
}
# Combine both
function process_file() {
[[ -f "$1" ]] || return 1 # Return code
echo "$(cat "$1")" # Output
}
output=$(process_file file.txt)
if [[ $? -eq 0 ]]; then
echo "$output"
fi
Debugging Techniques#
1. Verbose Mode#
# Set at top of script
set -x # Print commands as executed
# Or run script with -x flag
bash -x script.sh
# Disable verbose in sections
set +x # Turn off
SOME_COMMANDS
set -x # Turn back on
2. Strict Mode#
#!/bin/bash
set -euo pipefail
# e = exit on any error
# u = error on undefined variables
# o pipefail = pipe fails if any command fails
# Add trap for better error reporting
trap 'echo "Error: Script failed at line $LINENO"' ERR
3. Print Debug Information#
debug() {
if [[ "${DEBUG:-0}" == "1" ]]; then
echo "DEBUG: $@" >&2
fi
}
# Use it
debug "Variable VAR = $VAR"
# Run with debug enabled
DEBUG=1 ./script.sh
4. Validate Input Early#
validate_args() {
if [[ $# -lt 2 ]]; then
echo "Usage: script.sh FILE OUTPUT" >&2
exit 1
fi
if [[ ! -f "$1" ]]; then
echo "Error: File not found: $1" >&2
exit 1
fi
if [[ -z "$2" ]]; then
echo "Error: Output filename cannot be empty" >&2
exit 1
fi
}
validate_args "$@"
5. Test Commands Interactively#
# Use bash's test facilities
$ bash -n script.sh # Syntax check (no execution)
$ bash -x script.sh # Execute with trace
# Test conditions
$ [[ "string" =~ pattern ]] && echo "Match" || echo "No match"
$ (( 5 > 3 )) && echo "True"
# Check exit code
$ false
$ echo $? # Prints: 1
6. Use shellcheck#
# Install shellcheck
apt-get install shellcheck # Debian/Ubuntu
brew install shellcheck # macOS
# Run on script
shellcheck script.sh
# Fix issues
shellcheck -f diff script.sh
# Suppress specific warnings
# shellcheck disable=SC2086 # Disable word splitting warning
7. Break Script into Testable Functions#
# Instead of one long script, break into functions
process_file() {
# Can test independently
echo "Processing: $1"
}
validate_input() {
[[ -f "$1" ]]
}
main() {
for file in "$@"; do
if validate_input "$file"; then
process_file "$file"
fi
done
}
# Test individual functions
# source script.sh
# validate_input "test.txt"
Error Message Best Practices#
# Good error messages go to stderr (>&2)
echo "Error: Input file not found" >&2
# Include context
echo "Error: Cannot find config at $CONFIG_FILE" >&2
# Suggest solution
echo "Error: Missing required argument. Usage: script.sh FILE" >&2
# Use consistent formatting
error() {
echo "ERROR: $*" >&2
exit 1
}
warning() {
echo "WARNING: $*" >&2
}
info() {
echo "INFO: $*"
}
# Usage
[[ -f "$file" ]] || error "File not found: $file"
Common Gotchas#
Issue |
Problem |
Solution |
|---|---|---|
|
Tries to execute var=value as command |
Use |
|
Ambiguous when var is empty |
Use |
|
Breaks on spaces |
Use |
|
Extra parenthesis breaks syntax |
Use |
|
Pipeline hides error |
Use |
|
Syntax error in some forms |
Use newline: |
Comments in strings |
|
Only comments are truly outside strings |
|
Directory change lost |
Save path or use |