8.3. Return Values#

8.3.1. Common Pitfalls#

1. Forgetting that return codes must be 0-255

# Wrong: return value too large
calculate_result() {
  return 12345  # Only the low 8 bits used (return 57)
}

# Better: use echo for large numbers
calculate_result() {
  echo 12345
  return 0
}

2. Using return for actual data instead of echo

# Bad: can't return the string
get_name() {
  return "Alice"  # Interpreted as return 1
}

# Good: use echo
get_name() {
  echo "Alice"
}

3. Not checking return codes

# Risky: ignores errors
process_file "$file"
echo "Done"

# Better: check result
if process_file "$file"; then
  echo "Done"
else
  echo "Error processing file"
fi

8.3.2. Real-World Example: Robust Error Handling#

#!/bin/bash

# Function with comprehensive error handling
process_log_file() {
  local logfile=$1
  local line_count
  
  # Check prerequisites
  if [[ -z "$logfile" ]]; then
    echo "Error: No logfile specified" >&2
    return 2  # Invalid argument
  fi
  
  if [[ ! -f "$logfile" ]]; then
    echo "Error: Logfile '$logfile' not found" >&2
    return 1  # File not found
  fi
  
  if [[ ! -r "$logfile" ]]; then
    echo "Error: Logfile '$logfile' is not readable" >&2
    return 3  # Permission denied
  fi
  
  # Process and return result
  line_count=$(wc -l < "$logfile")
  echo "$line_count"
  return 0  # Success
}

# Error handling in caller
if output=$(process_log_file "$1"); then
  echo "Logfile has $output lines"
else
  status=$?
  case $status in
    1) echo "File not found" ;;
    2) echo "Invalid argument" ;;
    3) echo "Permission denied" ;;
  esac
fi

This pattern: clear error messages to stderr, meaningful return codes, data to stdout.

8.3.3. Combining Return Codes and Output#

Often functions return both a status code and data.

8.3.3.1. Return Status + Output Pattern#

#!/bin/bash

# Function returns both status and data
find_user_email() {
  local username=$1
  local email
  
  email=$(grep "^$username:" /etc/users 2>/dev/null | cut -d: -f3)
  
  if [[ -z "$email" ]]; then
    return 1  # User not found
  fi
  
  echo "$email"  # Return the email
  return 0       # Success
}

# Usage: check status AND capture output
if email=$(find_user_email "alice"); then
  echo "Email: $email"
else
  echo "User not found"
fi

8.3.4. Returning Values via Output#

For non-numeric or complex return values, functions should output the value and let the caller capture it.

8.3.4.1. Returning Strings or Multiple Values#

#!/bin/bash

# Return a string
get_user_home() {
  local user=$1
  echo $(getent passwd "$user" | cut -d: -f6)
}

# Call and capture output
home=$(get_user_home "root")
echo "Root home directory: $home"

# Return multiple values (comma-separated or space-separated)
get_file_info() {
  local file=$1
  local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
  local lines=$(wc -l < "$file")
  echo "$size $lines"  # Return as space-separated
}

# Capture and parse
read bytes lines < <(get_file_info "/etc/passwd")
echo "File has $lines lines, $bytes bytes"

Convention: Use return (0-255) for success/failure, use echo for actual data.

8.3.5. Accessing Exit Status: $?#

The special variable $? contains the exit status of the last command.

8.3.5.1. Using $?#

#!/bin/bash

validate_user() {
  if id "$1" > /dev/null 2>&1; then
    return 0
  else
    return 1
  fi
}

validate_user "root"
status=$?

if [[ $status -eq 0 ]]; then
  echo "User exists"
else
  echo "User not found"
fi

Important: $? is overwritten by each command. Save it immediately if needed.

command1
status=$?  # Save before running command2
command2   # This overwrites $?

8.3.6. Exit Status (Return Code)#

Every command in bash returns an exit status (also called return code or exit code): 0 means success, any non-zero value means failure.

8.3.6.1. The return Command#

#!/bin/bash

check_file() {
  if [[ -f "$1" ]]; then
    return 0  # Success: file exists
  else
    return 1  # Failure: file doesn't exist
  fi
}

# Use the return code
if check_file "/etc/passwd"; then
  echo "File found"
else
  echo "File not found"
fi

Return codes are used in conditionals (if, while, &&, ||).