Returning Values from Bash Functions

Summary: Return exit codes or values from functions.


Bash functions are essential for organizing and reusing code within shell scripts. Like functions in other programming languages, Bash functions allow you to compartmentalize tasks and logic. However, returning values from Bash functions can be confusing for those new to shell scripting, since Bash handles "return values" differently compared to languages like Python or JavaScript.

In this article, you'll learn how Bash functions return exit codes and how you can return actual values for further processing.


Exit Codes vs. Output Values

In Bash:

  1. Exit codes — Used to indicate success (0) or failure (non-zero) of a command or function. Accessed via $?.
  2. Output values — Text data that a function "returns" by printing to stdout, which can be captured using command substitution.

These concepts are central to returning information from Bash functions.


Returning Exit Codes from Bash Functions

By default, when a Bash function ends, its exit status is the exit status of the last command executed within the function.

You can explicitly set the exit status using the return command:

my_function() {
  if [[ -f "$1" ]]; then
    echo "File exists."
    return 0
  else
    echo "File does not exist."
    return 1
  fi
}

my_function "/etc/passwd"
echo "Function exited with status: $?"

Key points:

  • return <number> sets the function's exit code.
  • If you don't use return, the exit code is that of the last command executed in the function.
  • Exit codes should be integer values between 0 and 255.
  • Use exit codes for success/failure signals, not for returning data.

Returning Values from Bash Functions

While you can’t "return" a data value with the return statement, you can produce output within the function and capture that output using command substitution.

Example: Capturing Function Output

get_current_user() {
  echo "$USER"
}

user_name=$(get_current_user)
echo "The current user is: $user_name"

In this example:

  • get_current_user prints the username.
  • $(get_current_user) captures the output and assigns it to user_name.

Example: Function to Add Numbers

add_numbers() {
  local sum=$(( $1 + $2 ))
  echo "$sum"
}

result=$(add_numbers 7 5)
echo "Sum is: $result"

Combining Exit Codes and Output

You can use both exit codes and command output for more complex scripts.

find_file() {
  if [[ -f "$1" ]]; then
    echo "$1"
    return 0
  else
    return 1
  fi
}

if file_path=$(find_file "/tmp/mydata.txt"); then
  echo "File found: $file_path"
else
  echo "File not found!"
fi

Explanation:

  • If the file is found, find_file outputs its path and returns 0.
  • If not, it returns exit code 1 with no output.
  • The if statement checks if the function succeeded and captures output into file_path.

Returning Arrays and Multiple Values

Returning complex data like arrays requires a different approach. You can echo values as a string and parse them, or use Bash features to set global variables.

Example: Return Multiple Values

get_date_time() {
  echo "$(date '+%Y-%m-%d') $(date '+%H:%M:%S')"
}

read current_date current_time <<< "$(get_date_time)"
echo "Date: $current_date, Time: $current_time"

Example: Setting Global/Output Variable

get_files() {
  files=( *.txt )
}

get_files
echo "Text files: ${files[@]}"

In this pattern, the function populates a variable declared outside its scope.


Best Practices

  • Use return to indicate success/failure — not for returning data.
  • Print values to stdout from functions if you want to capture their "return value."
  • Use local variables inside functions to avoid polluting global scope.
  • Document your functions' exit codes and output format for clarity.

Conclusion

While Bash doesn't support returning values from functions in the same way as many programming languages, you can use function output and exit codes effectively for script logic and data transfer. With these techniques, you can write robust, maintainable Bash functions that interact with each other cleanly and predictably.