4.6d Functions, return codes, and exit status best practices

📦 Operating System Layer 📖 Core Linux Administration for AI Operators

📘 Context Introduction

When building automation scripts for AI infrastructure, you will often repeat the same logic in multiple places. Functions help you package that logic into reusable blocks, making your scripts cleaner and easier to maintain. But functions alone are not enough — you also need a reliable way to communicate success or failure back to the rest of your script. That is where return codes and exit statuses come in. This section will teach you how to write functions that behave predictably and how to check their results so your automation pipelines run smoothly.

⚙️ What Are Functions in Bash?

A function is a named block of code that you can call multiple times from different parts of your script. Instead of copying and pasting the same commands, you define the function once and invoke it by name.

Key characteristics of functions: - They help reduce duplication and improve readability. - They can accept arguments (inputs) when called. - They can return a status code (0 for success, 1–255 for failure). - They run in the same shell environment as the main script, so variables set inside a function are visible outside unless you use the local keyword.


🛠️ Defining and Calling Functions

To define a function, use the following structure:

Function definition pattern: - Start with the function name, followed by parentheses. - Enclose the body in curly braces. - Place all commands inside the braces, each on its own line.

Calling a function: - Simply write the function name as if it were a command. - Pass arguments by listing them after the function name, separated by spaces.

Example of a simple function:

check_gpu_health() {
    echo "Checking GPU status..."
    nvidia-smi --query-gpu=name --format=csv,noheader
}

Calling it:

check_gpu_health


🔄 Return Codes vs. Exit Status

This is a common point of confusion for new engineers. Let's clarify:

Concept Purpose Range How to Set Effect on Script
Return code Signals success/failure from a function back to the calling code 0–255 Use the return command inside the function Does NOT terminate the script; the calling code can check it
Exit status Signals success/failure of the entire script to the shell or parent process 0–255 Use the exit command anywhere in the script Terminates the script immediately

Important rule: A return code of 0 means success. Any non-zero value means a specific type of failure. By convention, use 1 for general errors, 2 for misuse of shell builtins, and higher values for more specific conditions.

📊 Visual Representation: Functions Return Codes vs. Script Exit Statuses

This flowchart contrasts the execution behavior of a function returning a status code (which allows the calling script to continue) against a script exiting (which terminates the process).

flowchart LR %% Function Return path subgraph "Function Return" call_fn["Call function check_gpu()"] --> ret_val["Return 0 (Success) or 1 (Failure)"] --> continue_script["Continue script execution"] end %% Script Exit path subgraph "Script Exit" error_chk["Fatal error detected"] --> exit_cmd["Exit 1 (Terminate process)"] --> terminate["Stop execution"] end classDef cpu fill:#eafaf1,stroke:#76b900,stroke-width:2px,rx:6px,ry:6px; classDef memory fill:#f0f7ff,stroke:#3498db,stroke-width:1.5px,rx:4px,ry:4px; classDef system fill:#f1f5f9,stroke:#64748b,stroke-width:1.5px; class ret_val,error_chk cpu; class continue_script,terminate memory; class call_fn,exit_cmd system;

🕵️ Best Practices for Return Codes in Functions

  1. Always return a meaningful code. Do not let a function end without an explicit return statement. If the function succeeds, add return 0 at the end.

  2. Use non-zero codes to indicate specific failure types. For example, return 1 if a file is missing, return 2 if a service is down, return 3 if permissions are wrong.

  3. Check the return code immediately after calling the function. Use the special variable $? right after the function call to capture the last return code.

  4. Do not use exit inside a function unless you intend to stop the entire script. Use return instead to keep the script running and handle the error gracefully.

Example of checking a return code:

check_disk_space
if [ $? -ne 0 ]; then
    echo "Disk space check failed. Aborting."
    exit 1
fi


📊 Exit Status Best Practices for Scripts

  1. Always end your script with an explicit exit command. This tells the calling process (like a CI/CD pipeline or another script) whether your automation succeeded.

  2. Use exit 0 at the end of the main script logic. If any critical step fails, use exit 1 (or another code) to signal failure.

  3. Document your exit codes in comments at the top of the script. This helps other engineers understand what each code means without reading the entire script.

  4. Trap unexpected errors. Use the trap command to catch signals like ERR and run a cleanup function before exiting with a failure code.

Example of a well-structured script ending:

# Exit codes:
# 0 = success
# 1 = configuration file missing
# 2 = GPU driver not found
# 3 = network timeout

echo "All checks passed."
exit 0


🧩 Combining Functions with Return Codes — A Complete Pattern

Here is the recommended pattern for any function that performs a check or action:

  1. Define the function with a clear name that describes what it does.
  2. Inside the function, perform the operation.
  3. Check the result of each command inside the function.
  4. Return 0 if everything succeeded, or return N with a specific error code.
  5. In the main script, call the function and immediately check $?.
  6. Handle the error — either retry, log, or exit the script.

For reference:

check_nvidia_driver() {
    if nvidia-smi &> /dev/null; then
        return 0
    else
        return 1
    fi
}

check_nvidia_driver
if [ $? -ne 0 ]; then
    echo "NVIDIA driver is not responding. Exiting."
    exit 2
fi
echo "NVIDIA driver is active."

📤 Output: NVIDIA driver is active. (if the driver is working)


🧼 Common Mistakes to Avoid

  • Using exit instead of return inside a function. This kills your entire script, not just the function.
  • Forgetting to check the return code. A function might fail silently, and your script will continue with bad data.
  • Returning arbitrary values. Stick to 0 for success and small positive integers for failures. Avoid returning negative numbers or strings.
  • Not using local variables inside functions. Without local, your function can accidentally overwrite variables used elsewhere in the script.

✅ Summary Checklist for Engineers

  • [ ] Every function ends with an explicit return statement.
  • [ ] Every script ends with an explicit exit statement.
  • [ ] Return codes are checked immediately after the function call using $?.
  • [ ] Non-zero return codes have documented meanings.
  • [ ] Functions use local variables to avoid side effects.
  • [ ] The trap command is used to catch unexpected errors in critical scripts.

By following these best practices, your Bash scripts will be more reliable, easier to debug, and ready to integrate into larger AI infrastructure automation workflows. Functions with clear return codes are the building blocks of robust operations.