How exit status is defined

POSIX specifies that the exit status of a command is determined by the last simple command executed unless otherwise stated. Interactive shells may continue after certain errors while non-interactive shells typically exit. The standard table lists conditions such as syntax errors, command-not-found cases, and unrecoverable read errors, each with rules on whether the shell exits and whether a diagnostic message is required.

In practice, most utilities follow the convention that zero indicates normal completion and values from 1 to 255 report problems. Scripts should treat any non-zero result as a signal to stop or handle an error path.

  • Exit status comes from the last command unless the shell uses set -e or similar options.
  • Special built-in utilities may cause immediate exit on error in non-interactive mode.
  • No size limit exists on commands beyond system constraints such as ARG_MAX.

Pipelines and the pipefail option

A pipeline connects commands with the | operator. The exit status of the pipeline normally comes from the last command. When the pipefail option is enabled, the pipeline status becomes the exit status of the rightmost command that failed, or zero if all succeeded. The setting active when the pipeline starts governs the result, not any later change.

The ! reserved word negates the status of the pipeline. These rules matter in automation because a failing early stage can be masked by a later successful command unless pipefail is active.

  • Without pipefail, only the final command status is visible.
  • With pipefail enabled, any failure in the chain sets a non-zero status.
  • The shell records the pipefail setting at pipeline start time.

Checking codes reliably in scripts

After any command, the special variable $? holds the exit status. Scripts should capture and test this value immediately because subsequent commands overwrite it. In CI systems such as GitLab CI or GitHub Actions, a non-zero exit from any script line fails the job unless the line is explicitly allowed to fail.

Some utilities return non-zero values that do not represent failure. Robocopy, for example, uses codes 0-7 for success with varying levels of file activity. Scripts must therefore compare against an acceptable range rather than simply checking for zero.

  • Capture $? on the line immediately following the command.
  • Use if statements or &&/|| operators for concise checks.
  • Document any utility-specific ranges that are treated as success.

Practical patterns and cautions

When a command must succeed for later steps to be valid, combine it with explicit tests or use set -e to exit on the first error. For commands whose non-zero codes are acceptable, wrap the call so the overall script still returns zero. PowerShell and bash examples both show the pattern of running the tool then testing its code against a threshold before deciding the script result.

Never rely on output text alone to determine success. Logs can be incomplete or misleading when processes are killed or redirected. Exit codes remain the only portable signal recognized by shells and CI runners.

  • set -e stops execution on the first non-zero exit in many shells.
  • Explicit range checks are required for tools like Robocopy.
  • CI jobs fail on any unhandled non-zero code from the script section.

Putting it into practice

Review existing automation for places where exit codes are ignored or where pipefail is missing. Add immediate checks after critical commands and document any allowed non-zero ranges. Test the updated scripts both interactively and inside the target CI environment to confirm behavior matches expectations.

Consistent use of exit codes makes failures visible early, reduces debugging time, and prevents downstream steps from operating on bad data.