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, &&, ||).