Variable Scope and Return Status

ShellBeginner
Practice Now

Introduction

When writing scripts, you need to manage how data is accessed and how functions communicate their success or failure.

If you use the same variable name in two different places, one might accidentally overwrite the other. This is a common bug in programming. We solve this using Scope (keeping variables local to a function).

Additionally, functions need a way to tell the main script if they succeeded or failed. In Bash, this is done using return status codes.

This topic joins two ideas that become critical in larger scripts: keeping variables from leaking into places they should not affect, and using success or failure status to control the flow of execution.

In this lab, you will learn:

  1. Variable Scope: Using local to prevent variable conflicts.
  2. Return Status: Using exit codes (0 for success, non-zero for failure) to control script flow.

Variable Scope: Global vs. Local

By default, variables in Bash are Global. This means if you define a variable inside a function, it can be accessed (and changed) anywhere in the script. This can lead to messy bugs where one part of your script accidentally changes data needed by another part.

To prevent this, we use the local keyword. A local variable exists only inside the function where it is created.

This is one of the tradeoffs of shell scripting. Bash makes it easy to get started quickly, but that convenience can also make it easy for data to leak across your script in unexpected ways. Scope is the rule that limits where a variable is visible.

Let's demonstrate this difference.

Create a script named scope.sh:

cd ~/project
nano scope.sh

Add the following code:

#!/bin/bash

## This is a Global variable
city="New York"

print_city() {
  ## This is a Local variable. It shares the name 'city', but it is a separate variable.
  local city="London"
  echo "Inside function: $city"
}

echo "Before function call: $city"

## Run the function
print_city

## Check if the Global variable was changed
echo "After function call: $city"

Save (Ctrl+O, Enter) and Exit (Ctrl+X).

Make the script executable and run it:

chmod +x scope.sh
./scope.sh

The Result:

Before function call: New York
Inside function: London
After function call: New York

Notice that the city variable outside the function remained "New York". The function used its own local copy of city ("London") and did not affect the global one.

If we had not used local, the function would have overwritten the global variable, and the last line would have printed "London".

This is why local becomes more important as scripts grow. In a short demo, global variables may seem harmless. In a longer maintenance script, a reused variable name can create subtle bugs that are difficult to diagnose.

Return Status Codes

In many programming languages, functions return data (like a string or a number). In Bash, return is used strictly for Status Codes to indicate Success or Failure.

  • 0: Success.
  • 1-255: Failure (Error).

Do not confuse return with echo.

  • echo outputs text to the screen (or to a variable if captured).
  • return sends a hidden number to the system to say "I worked" or "I failed".

That distinction is fundamental in shell programming. Text output and status reporting are two separate channels. A function can print human-readable information with echo, but its return value is meant for control flow, not for carrying general data.

Let's create a function that simulates a failure by returning code 5.

Create status.sh:

nano ~/project/status.sh

Add the following code:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  ## We simulate a specific error by returning Code 5
  return 5
}

check_file

Save and Exit.

Make it executable and run it:

chmod +x status.sh
./status.sh

Output:

Checking for file...

You see the text, but where is the number 5? It was returned silently. In the next step, we will learn how to capture and use it.

Capture the Return Code ($?)

To see the return code of the last executed command or function, Bash provides a special variable: $?.

When check_file runs completely, $? will automatically hold the value 5.

Crucial Rule: You must read $? immediately after the command runs. As soon as you run another command (even a simple echo), $? is updated with the status of that new command.

Let's modify status.sh to capture the code.

Open the file:

nano status.sh

Update your script to:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file

## Capture the return code immediately into a variable
ret_val=$?

echo "The function returned: $ret_val"

Save and Exit. Run it again:

./status.sh

Output:

Checking for file...
The function returned: 5

Now we have captured the error code in the variable ret_val.

Capturing the return code lets the rest of the script react intelligently. A later if statement can check that value and decide whether to continue, retry, or stop. This is how functions begin to cooperate in larger scripts instead of acting as isolated code snippets.

Act on the Return Status

Now that we have the status code, we can use an if statement to handle errors gracefully.

  • If the code is 0, everything is fine.
  • If the code is 5, we know exactly what went wrong (e.g., "File missing").

This is how standard Linux commands behave. For example, grep returns 0 if it finds a match and 1 if it doesn't.

Update status.sh to include logic:

nano status.sh

Add the conditional logic:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file
ret_val=$?

## Logic based on the return code
if [ $ret_val -eq 0 ]; then
  echo "Success: File found."
elif [ $ret_val -eq 5 ]; then
  echo "Error: File missing (Code 5)."
else
  echo "Unknown Error: Code $ret_val"
fi

Save and Exit. Run it:

./status.sh

Output:

Checking for file...
Error: File missing (Code 5).

You have now implemented basic error handling. If checking the file failed, your script knows about it and can print a specific error message instead of crashing or continuing blindly.

Summary

In this lab, you practiced two critical scripting concepts:

  1. Variable Scope (local): Using local ensures that variables inside functions do not overwrite global variables, keeping your script predictable.
  2. Return Status (return, $?): Using exit codes allows functions to communicate success or failure to the main script, enabling you to write scripts that can handle errors intelligently.

Always use local for variables inside functions unless you specifically need to modify a global variable.