← Back to Bash Course | Chapter 5: Functions | Lesson 7 of 11

Local Variables and Scope

By default every variable in Bash is global, so local is what lets a function keep its own working variables private, without accidentally overwriting variables used elsewhere in the script.

Why local Matters

Without local, any variable assigned inside a function becomes (or overwrites) a global variable, which can silently corrupt state elsewhere in the script that happens to use the same name. local varname=value confines the variable strictly to that function call.

Example: Why local Matters

bash
#!/bin/bash
counter=100
increment() {
    local counter=0
    counter=$((counter + 1))
    echo "Inside function, counter is $counter"
}
increment
echo "Outside function, counter is still $counter"

Global Leakage Without local

If a function assigns to a variable without declaring it local, that assignment affects the global variable of the same name (creating it if it didn't exist), and the change persists after the function returns. This is a common source of hard-to-trace bugs in larger scripts.

Warning: Always use local for a function's own working variables unless you specifically intend to modify a global.

Example: Global Leakage Without local

bash
#!/bin/bash
leaky_function() {
    message="set inside function, no local"
}
leaky_function
echo "After call, message is: $message"

Dynamic Scoping: Callees See Caller's Locals

Bash's locals are dynamically scoped, meaning a function called from inside another function can see and use that outer function's local variables, unlike lexically scoped languages where only the enclosing code block matters. This is a distinctive quirk worth knowing even though most scripts don't rely on it directly.

Example: Dynamic Scoping: Callees See Caller's Locals

bash
#!/bin/bash
outer() {
    local shared_value="from outer"
    inner
}
inner() {
    echo "inner sees shared_value as: $shared_value"
}
outer

Separating local Declaration from Command Substitution

Combining local with a command substitution assignment on one line, like local x=$(cmd), can hide cmd's own exit status behind local's exit status. Splitting the declaration and the assignment into two lines preserves the ability to check the substituted command's real exit status.

Example: Separating local Declaration from Command Substitution

bash
#!/bin/bash
check_status() {
    local output
    output=$(echo "data"; exit 3)
    echo "Captured output: $output"
    echo "Exit status of the command substitution: $?"
}
check_status
Common Mistakes
  1. Forgetting to use local for a function's working variables; without it, a variable of the same name used elsewhere in the script (or in another function) gets silently overwritten.
  2. Assuming local makes a variable visible to functions called from within the current function; local scope in Bash is only visible in the function it's declared in and functions it calls (dynamic scoping), not hidden from callees, but it is hidden from the caller after the function returns.
  3. Combining local with command substitution in a single line, e.g. local x=$(cmd), without realizing local's own exit status can mask cmd's exit status -- if you need cmd's status, declare and assign in two separate lines.
Chapter Summary
  • local varname=value inside a function creates a variable that only exists within that function call and disappears when the function returns.
  • Without local, a variable assigned inside a function is global by default and persists/overwrites after the function returns.
  • Bash uses dynamic scoping for locals: a function called from inside another function *can* see the caller's locals, unlike lexical scoping in many other languages.
  • Declaring commonly-named loop or temp variables (i, result, temp) as local inside functions avoids subtle bugs from name collisions.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.