When Your Bash Script Fails: A Systematic Troubleshooting Approach
Debug Bash script failures by checking exit codes, tracing commands, isolating environment issues, and logging unattended runs.
When Your Bash Script Fails: A Systematic Troubleshooting Approach
Bash script failure troubleshooting starts with one question: did the script fail because the command was wrong, the environment was different, or the input was not what you expected? A repeatable debugging routine keeps you from guessing when a cron job, deployment hook, or backup script breaks.
Use the steps below to read the failure signal, trace what Bash actually ran, and narrow the problem to a specific command.
Phase 1: Preparation and Initial Assessment
Before diving into complex debugging flags, ensure you have the foundational elements in place. A structured initial assessment saves significant time.
1. Review the Error Message and Exit Code
The most immediate clue is the error message reported by the shell. Pay close attention to the line number mentioned, if provided.
Exit Codes: In shell scripting, the special variable
$?holds the exit status of the most recently executed foreground command. A successful command returns0. Any non-zero value indicates failure.some_command echo "Command exited with status: $?" # If $? is 127, it often means "command not found".
2. Verify Script Execution Mode
Ensure the script is being executed as intended, especially concerning the interpreter specified by the shebang line.
Shebang: Always start your script with a proper shebang line to define the interpreter.
#!/bin/bashis standard, but#!/usr/bin/env bashis often preferred for portability.Permissions: Confirm the script has execution permissions set:
chmod +x your_script.sh
3. Isolate the Execution Environment
Environment differences are a major source of intermittent failures. Always test in the environment where the script is supposed to run, or confirm variables that differ between development and production.
Test Directly: Run the script directly using the interpreter, bypassing potential PATH issues if executing just by name:
/bin/bash ./your_script.sh
Phase 2: Enabling Bash Debugging Flags
Bash provides powerful built-in flags that can trace execution flow and variable evaluation, which are crucial for pinpointing logic errors or unexpected expansion.
1. The Essential Debugging Flags
These flags are typically added to the shebang line or enabled/disabled within the script using set.
| Flag | Command | Purpose |
|---|---|---|
| -n | set -n |
Read commands but do not execute them (syntax check only). |
| -v | set -v |
Print shell input lines as they are read (verbose mode). |
| -x | set -x |
Print commands and their arguments as they are executed (trace mode). This is the most powerful for logic errors. |
2. Using Trace Mode (set -x)
set -x prepends the output of every executed command with a + sign, showing exactly what Bash is interpreting, including variable expansions.
Example of Tracing:
Consider a script that fails due to incorrect quoting:
# Original Script Snippet
USER_INPUT="Hello World"
echo $USER_INPUT # Fails if USER_INPUT contained spaces and was passed to another command
When running with set -x enabled (either via #!/bin/bash -x or set -x at the start):
+ USER_INPUT='Hello World'
+ echo Hello World
Hello World
If you suspect quoting issues, you can enable trace mode selectively around the problematic section:
set -x
# Commands that work fine
# Trace only the problematic section
COMMAND_THAT_FAILS_DUE_TO_EXPANSION
set +x
# Rest of script
Best Practice: For debugging the entire script, use #!/bin/bash -x or place set -x immediately after the shebang.
3. Debugging Variable Expansion
Many failures stem from how variables are expanded (or not expanded). Use double quotes around variables liberally ("$VAR") to prevent word splitting and glob expansion, but use tracing (set -x) to see if the expansion is happening as expected.
If you want to see the literal value of a variable including whitespace, you can echo it wrapped in quotes and surrounded by delimiters:
VAR="a b c"
printf '[%s]\n' "$VAR"
# Output: [a b c]
Phase 3: Handling Common Error Types
Once debugging flags are active, errors usually fall into predictable categories.
1. Command Not Found (Exit Code 127)
This error, often appearing as your_command: command not found, indicates that the shell cannot locate the executable.
- Check PATH: Ensure the directory containing the command is listed in the
$PATHenvironment variable within the script's execution context. - Use Absolute Paths: When in doubt, use the full path to the command (e.g.,
/usr/bin/curlinstead of justcurl).
2. Syntax Errors
These often involve unmatched delimiters, incorrect use of control structures (if, for, while), or missing semicolons/newlines.
set -n(No Execution): Running the script withset -nforces Bash to parse everything without executing, often revealing unclosed brackets or missingfi/donestatements immediately.Conditional Syntax: Pay close attention to
[[ ... ]]vs[ ... ]. For instance, testing arithmetic requires(( ... ))orlet, not standard test structures.Example (Arithmetic Context):
# Correct way to check if A is greater than B A=10 B=5 if (( A > B )); then echo "A is greater" fi
3. Permissions and Input/Output Issues
If the script runs but fails when interacting with files or external processes, check permissions and file descriptors.
Input Redirection: If you are redirecting input from a file, ensure that file exists and is readable.
Output Redirection: Check if the destination directory exists and if the script user has write permissions.
Warning on SUDO: If you run a script with
sudo, environment variables like$PATHand user-specific configurations (like.bashrc) are often reset or changed. Commands that work when run as a regular user might fail undersudodue to missing context or paths.
Phase 4: Logging and System Checks
For scripts running in the background (e.g., via Cron), direct terminal output is unavailable. Robust logging is essential.
1. Redirecting Output for Debugging
When executing unattended, redirect both standard output (stdout, descriptor 1) and standard error (stderr, descriptor 2) to a log file. Combining them is common:
# Redirect all output to debug.log
./your_script.sh >> debug.log 2>&1
If using set -x, the trace output will go to the same log file, providing a complete record of execution flow and errors.
2. Checking System Health
Sometimes the script itself is fine, but the system environment is the issue:
- Disk Space: Is the system running out of disk space (
df -h)? This will halt writing operations. - Memory: Check memory usage (
free -m). High memory pressure can cause external commands to fail or hang. - Cron Environment: If scheduled via Cron, remember Cron jobs execute with a highly restricted environment. Always explicitly define necessary environment variables at the top of the script if they are not guaranteed by the Cron job setup.
Keep the Debugging Path Short
When your Bash script fails, capture the exit code and exact error first. Then confirm the shebang, permissions, $PATH, input files, and user context. If the cause is still unclear, enable set -x only around the suspicious block and send unattended output to a log.
That order keeps troubleshooting practical. You move from the clearest evidence to the most detailed trace without flooding yourself with noise.