| |

Bash Scripting 15 ๐Ÿง Return Values โ€” Handling exit status codes ($?) and function outputs

โฑ๏ธ Estimated read: 13 min | |

Every command in Bash returns a number between 0 and 255 when it finishes. That number is the exit status, and it is the mechanism by which commands, functions, and scripts communicate success or failure to their caller. The previous chapter covered functions and the return command; this chapter covers the exit status in detail: the $? variable that holds it, the if and &&/|| constructs that branch on it, the set -e option that changes its behavior, and the conventions that make exit codes meaningful across a script.

The exit status is not a value in the same sense as a string or a number that a function computes. It is a status: 0 means success, and any non-zero value means failure. The value of a non-zero status can carry information about the type of failure, but the only portable convention is that 0 is success. The return command in a function and the exit command in a script both set the status, and the caller reads it with $? immediately after the call.

Key point: $? is the exit status of the most recently executed foreground command. It is overwritten by every subsequent command, so it must be captured immediately if it is needed. set -e causes the script to exit when a command fails, but it has exceptions that make it unreliable for general error handling. The && and || operators branch on the exit status without checking $? explicitly, and the if statement uses the exit status as its condition.


Why exit statuses require careful handling

The overwrite problem.

$? holds the status of the last command, and every command overwrites it. A script that runs a command, then runs echo, then checks $? is checking the status of echo, not the original command. The status must be captured immediately after the command that produced it, either by assigning it to a variable or by using the command directly in a conditional. The if command; then and command && action forms avoid the problem because they check the status at the moment the command finishes.

The set -e problem.

set -e causes the shell to exit when a command returns a non-zero status. It is a tempting way to handle errors without checking every command, but it has exceptions: commands in an if condition, commands before && or ||, commands whose status is inverted with !, and commands in a pipeline other than the last one are not affected by set -e. A script that relies on set -e for error handling may continue past a failure that was in a condition or a pipeline, and the failure goes undetected. The BashFAQ and the Bash Hackers Wiki recommend against set -e for general error handling and in favor of explicit checks.

The status-range problem.

The exit status is an 8-bit value, so it ranges from 0 to 255. A command that returns 256 is reported as 0, and a command that returns -1 is reported as 255. The return command in a function truncates the value to 8 bits, and the exit command does the same. A function that wants to return a value larger than 255 must print it to stdout instead. The status is for success or failure, not for data.


a. Reading and capturing $?

The $? variable holds the exit status of the last command. It is read immediately after the command, before any other command runs.

#!/usr/bin/env bash
# Run a command and capture the status
ls /nonexistent
status=$?
echo "Status: $status"    # 2 (ls returns 2 for a missing file)

# The status is overwritten by the next command
ls /nonexistent
echo "This is a new command"
echo "Status: $?"         # 0 (the status of echo, not ls)

The ls /nonexistent command fails with status 2, and the status=$? assignment captures it. The second example runs echo between the failing command and the status check, so $? holds the status of echo, which is 0. The status must be captured or used immediately after the command that produces it.

The if statement reads the status directly without $?.

# Use the command as the condition
if grep -q "root" /etc/passwd; then
    echo "Found"
fi

# Use && and || to branch on the status
grep -q "root" /etc/passwd && echo "Found" || echo "Not found"

The if grep -q ... form runs the command and branches on its status. The && operator runs the second command only if the first succeeds, and the || operator runs the third command only if the first fails. Neither form reads $? explicitly; the shell does it automatically.

The status of a pipeline is the status of its last command by default. The pipefail option changes this.

# Without pipefail: status is the last command's status
false | true
echo "$?"    # 0

# With pipefail: status is the first failing command's status
set -o pipefail
false | true
echo "$?"    # 1

The set -o pipefail option makes the pipeline return the status of the last command that failed, rather than the status of the final command. It is the recommended setting for scripts that check the status of pipelines.


b. The && and || operators and the if statement

The && operator runs the right-hand command only if the left-hand command succeeds. The || operator runs the right-hand command only if the left-hand command fails. The two can be combined for a conditional action.

#!/usr/bin/env bash
# Run a command only if the previous one succeeds
mkdir -p /tmp/work && cd /tmp/work || exit 1

# Run a fallback if the command fails
curl -sf https://example.com || echo "Request failed"

# Use the status in a condition
if [ -f "/etc/passwd" ]; then
    echo "File exists"
else
    echo "File missing"
fi

The mkdir -p /tmp/work && cd /tmp/work || exit 1 chain creates the directory and changes into it, and exits if either step fails. The curl -sf ... || echo "Request failed" command prints a message if the request fails. The if [ -f ... ] form uses the test command’s status as the condition.

The ! operator inverts the status.

# Invert the status
if ! grep -q "root" /etc/passwd; then
    echo "root not found"
fi

The ! grep -q form succeeds when grep fails, which is the opposite of the command’s own status.


c. Exit codes in scripts and functions

A script returns the status of its last command unless it calls exit explicitly. The exit command sets the status and terminates the script.

#!/usr/bin/env bash
# Exit with a specific status
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 <arg>" >&2
    exit 1
fi

# The script exits with the status of the last command
echo "Done"
exit 0

The exit 1 terminates the script with status 1, which the caller reads with $?. The exit 0 at the end is optional, because the script exits with the status of its last command, which is echo‘s status, 0. The explicit exit 0 is clearer, and it documents the intent.

A function uses return instead of exit. The return command sets the function’s status and returns control to the caller.

#!/usr/bin/env bash
is_file() {
    if [ -f "$1" ]; then
        return 0
    else
        return 1
    fi
}

if is_file /etc/passwd; then
    echo "File exists"
fi

The is_file function returns 0 or 1 depending on the file’s existence. The caller uses the function in an if statement, which reads the status directly. The return command is the function’s counterpart to exit.

A function can also print a value to stdout and set a status. The caller captures the output with command substitution and checks the status with $?.

# Function that prints output and sets status
get_file_size() {
    if [ ! -f "$1" ]; then
        echo "File not found" >&2
        return 1
    fi
    stat -c %s "$1"
}

size=$(get_file_size /etc/passwd)
status=$?
if [ "$status" -eq 0 ]; then
    echo "Size: $size"
fi

The get_file_size function prints an error to stderr and returns 1 if the file is missing. If the file exists, it prints the size to stdout and returns the status of stat. The caller captures the output and the status separately. The error message goes to stderr, so it is not captured by the command substitution.


Complete Example Session

#!/usr/bin/env bash
# ============================================
# PART 1: CAPTURE $?
# ============================================
ls /nonexistent 2>/dev/null
status=$?
echo "ls status: $status"

# ============================================
# PART 2: THE OVERWRITE PROBLEM
# ============================================
false
echo "Between commands"
echo "Status: $?"    # 0, not 1

# ============================================
# PART 3: IF AND &&/||
# ============================================
if ping -c 1 -W 1 192.168.1.1 > /dev/null 2>&1; then
    echo "Host is up"
fi

ping -c 1 -W 1 192.168.1.1 > /dev/null 2>&1 && echo "Up" || echo "Down"

# ============================================
# PART 4: PIPELINE STATUS
# ============================================
false | true
echo "Without pipefail: $?"

set -o pipefail
false | true
echo "With pipefail: $?"

# ============================================
# PART 5: FUNCTION RETURN STATUS
# ============================================
is_file() {
    [ -f "$1" ]
}

is_file /etc/passwd && echo "File exists"
is_file /nonexistent || echo "File missing"

# ============================================
# PART 6: FUNCTION OUTPUT AND STATUS
# ============================================
get_date() {
    date +%F
}

today=$(get_date)
status=$?
echo "Today: $today (status: $status)"

# ============================================
# PART 7: EXIT IN A SCRIPT
# ============================================
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 <file>" >&2
    exit 1
fi

# ============================================
# PART 8: EXPLICIT ERROR CHECKS
# ============================================
if ! cp /etc/passwd /tmp/passwd.bak; then
    echo "Copy failed" >&2
    exit 1
fi
echo "Copy succeeded"

# ============================================
# PART 9: EXIT CODE CONVENTIONS
# ============================================
# 0 = success
# 1 = general error
# 2 = misuse of shell builtins (e.g., invalid option)
# 126 = command not executable
# 127 = command not found
# 128+N = killed by signal N
# 130 = terminated by Ctrl+C (SIGINT)

# ============================================
# PART 10: TRAP FOR CLEANUP
# ============================================
cleanup() {
    echo "Cleaning up..." >&2
    rm -f /tmp/workfile
}
trap cleanup EXIT
touch /tmp/workfile
echo "Script running"

The ten parts demonstrate the exit status surface: the $? capture, the overwrite problem, the if and &&/|| forms, the pipeline status, the function return, the function output, the script exit, the explicit error check, the exit-code conventions, and the trap handler. The critical distinction is between Part 1 and Part 2: the capture must happen immediately, and the intervening echo overwrites the status.


Quick Reference

Status Variables and Operators

ElementMeaning
$?Status of the last command
if cmd; thenBranch on the status
cmd && nextRun next if cmd succeeds
cmd || nextRun next if cmd fails
! cmdInvert the status
exit NExit the script with status N
return NReturn from a function with status N

Common Exit Codes

CodeMeaning
0Success
1General error
2Misuse of shell builtins
126Command not executable
127Command not found
128+NKilled by signal N
130Terminated by Ctrl+C

Pipeline Options

OptionEffect
set -o pipefailPipeline returns the first failing status
set -eExit on first failure (with exceptions)
set -uExit on unset variable
set -xTrace commands

Best Practices

โœ… Do This:

# Capture the status immediately
cmd; status=$?                                                # โœ…

# Use the command as a condition
if cmd; then ...; fi                                          # โœ…

# Use && and || for short branches
cmd && echo "ok" || echo "failed"                             # โœ…

# Use pipefail for pipelines
set -o pipefail                                               # โœ…

# Use return in functions
is_file() { [ -f "$1" ]; }                                    # โœ…

# Explicit error check
if ! cp src dst; then echo "failed" >&2; exit 1; fi           # โœ…

# Trap for cleanup
trap cleanup EXIT                                             # โœ…

โŒ Don’t Do This:

# Check $? after another command
cmd; echo "done"; if [ $? -eq 0 ]; then ...; fi               # โŒ

# Rely on set -e for all errors
set -e    # Conditions and pipelines bypass it               # โŒ

# Use return for a string
get_name() { return "Alice"; }                                # โŒ

# Ignore the status
cmd    # Failure goes undetected                             # โŒ

# Use $* instead of "$@"
for arg in $*; do ...; done                                   # โŒ

Common Pitfalls

PitfallWhy It HappensFix
$? is always 0Checked after another commandCapture immediately
Pipeline hides a failureDefault status is the last commandset -o pipefail
set -e does not exitCommand in a condition or pipelineExplicit checks
Function returns wrong valueUsed return for a stringUse echo and capture
Status truncatedValue exceeds 255Use stdout for data

Real-World Examples

1. Capture status

cmd; status=$?; echo "$status"

2. If condition

if ping -c 1 host; then echo "up"; fi

3. && and ||

mkdir -p /tmp/x && cd /tmp/x || exit 1

4. Pipeline status

set -o pipefail; grep pattern file | wc -l

5. Function return

is_root() { [ "$(id -u)" -eq 0 ]; }; is_root && echo "root"

6. Function output

name=$(get_name); echo "$name"

7. Explicit exit

[ -f "$config" ] || { echo "missing" >&2; exit 1; }

8. Trap cleanup

trap 'rm -f /tmp/workfile' EXIT

9. Signal exit code

kill -INT $$; echo "$?"    # 130

10. Command not found

nonexistent_command; echo "$?"    # 127

Visual

The $? Overwrite Problem

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  $? IS OVERWRITTEN BY EVERY COMMAND                          โ”‚
โ”‚                                                              โ”‚
โ”‚  โŒ Incorrect:                                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ ls /nonexistent    # status 2                          โ”‚  โ”‚
โ”‚  โ”‚ echo "checking"    # status 0                          โ”‚  โ”‚
โ”‚  โ”‚ if [ $? -eq 0 ];   # checks echo, not ls               โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  โœ… Correct:                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ if ls /nonexistent; then ...; fi                       โ”‚  โ”‚
โ”‚  โ”‚ # or                                                   โ”‚  โ”‚
โ”‚  โ”‚ ls /nonexistent; status=$?                             โ”‚  โ”‚
โ”‚  โ”‚ echo "checking"                                        โ”‚  โ”‚
โ”‚  โ”‚ if [ "$status" -eq 0 ]; then ...; fi                   โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Pipeline Status

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PIPE FAILS ARE HIDDEN WITHOUT pipefail                      โ”‚
โ”‚                                                              โ”‚
โ”‚  Without pipefail:                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ false | true                                           โ”‚  โ”‚
โ”‚  โ”‚ echo $?    โ†’ 0 (the status of true, the last command)  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  With pipefail:                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ set -o pipefail                                        โ”‚  โ”‚
โ”‚  โ”‚ false | true                                           โ”‚  โ”‚
โ”‚  โ”‚ echo $?    โ†’ 1 (the status of false, the first failure)โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Return vs Exit

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  RETURN IS FOR FUNCTIONS; EXIT IS FOR SCRIPTS                โ”‚
โ”‚                                                              โ”‚
โ”‚  Function:                                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ is_file() { [ -f "$1" ]; return $?; }                  โ”‚  โ”‚
โ”‚  โ”‚ is_file /etc/passwd; echo $?    โ†’ 0                    โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  Script:                                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ [ -f "$1" ] || { echo "missing" >&2; exit 1; }         โ”‚  โ”‚
โ”‚  โ”‚ # the script terminates with status 1                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Status variable$?
Success0
FailureNon-zero (1โ€“255)
Function returnreturn N
Script exitexit N
Conditionif cmd; then
Short branchcmd && next / cmd || next
Pipeline statusLast command by default
pipefailFirst failing command in the pipeline
Common codes1 general, 2 misuse, 126 not executable, 127 not found, 130 SIGINT

Key takeaways:

  • $? holds the status of the last command and is overwritten by every command. It must be captured immediately or used directly in a conditional. The if cmd and cmd && next forms avoid the overwrite problem because they check the status at the moment the command finishes.
  • 0 is success; non-zero is failure. The status range is 0 to 255. The value can carry information about the failure type, but the only portable convention is that 0 means success.
  • set -o pipefail makes pipelines report the first failure. Without it, the pipeline’s status is the status of its last command, and a failure in an earlier command goes undetected.
  • set -e is not reliable for error handling. Commands in conditions, commands before && or ||, and commands in pipelines other than the last one are not affected by set -e. Explicit checks are more reliable.
  • return is for functions; exit is for scripts. Both set the status, but return returns control to the caller and exit terminates the script. The function’s status is read by the caller with $? or a conditional.
  • A function that returns data uses stdout, not return. The return command only sets the status. A string or a computed value is printed to stdout and captured with command substitution, and the status is checked separately.

Remember: The exit status is the shell’s success-or-failure signal. $? holds it, but it is overwritten immediately, so it must be captured or used at the moment the command finishes. The if statement and the &&/|| operators check it directly. The pipefail option makes pipelines report failures. The return command sets a function’s status, and the exit command terminates a script. The status is not data; it is the answer to the question “did the command succeed?” A function that needs to return data prints it to stdout.


Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!