In this lesson, you will learn how to capture and propagate exit codes to determine exactly why a complex pipeline succeeded or failed.
The Language of Completion
When a process finishes, it doesn't just disappear; it sends a small integer back to its parent (the shell) called an exit code. By convention, a value of 0 means "success," while any value from 1 to 255 indicates an error. The shell stores this value in a special parameter called $?.
The Challenge of Pipelines
A pipeline—like cat file.txt | grep "search" | sort—is a sequence of processes. By default, the shell only reports the exit code of the last command in that chain. If cat fails because the file is missing, but grep happens to run successfully, your shell might report a 0, masking the real error.
To ensure errors aren't swallowed, we use the pipefail option. When enabled, the shell treats the entire pipeline as a single unit and assigns it the exit code of the first command that failed.
Putting It Into Practice
You can enable this behavior in your current shell session like this:
# Enable the pipeline error tracking feature
set -o pipefail
# Run a pipeline where the first command fails
cat non_existent_file | grep "pattern"
# Check the resulting exit code
echo $?
In the example above, cat will fail because the file is missing. Even though grep might technically "succeed" by not finding anything, pipefail ensures that the exit code of the entire command becomes the error code from cat (usually 1).
By mastering $? and pipefail, you transform your shell scripts from "blind" executors into robust automation tools that can detect precisely where a breakdown occurred. This allows your scripts to react intelligently—such as stopping execution immediately if a critical step in a data pipeline fails.
Key Takeaway: Exit codes are the communication bridge between a finished process and the shell, and using set -o pipefail is essential for surfacing errors within complex command chains.