opscape

Shell builtins · bash builtin

local

Declare variables inside a function that disappear when it returns, leaving any global of the same name untouched.

local [OPTION] NAME[=VALUE] ...

8 of 8 examples run in Debian 13.7 on 2026-10-31 · manual: bash(1), SHELL BUILTIN COMMANDS

Variables in bash are global by default, even when they are first assigned inside a function. A helper that uses i or result will overwrite the caller's i or result. local NAME creates a variable that exists only while the function runs; when it returns, the outer value (or the absence of one) comes back.

bash uses dynamic scoping: a local is visible not only in its own function but in every function it calls. So a callee can read, and change, its caller's locals. That is different from most programming languages and occasionally useful, but mostly a reason to give every function-internal variable a local declaration.

local accepts the same options as declare (-a, -A, -i, -r, -n, ...) and is only valid inside a function. One trap deserves attention: local x=$(cmd) hides the exit status of cmd. Declare first, then assign.

Examples

Keep a function's variables to itself

name="global"
without() { name="changed by without"; }
with()    { local name="changed by with"; echo "inside with: $name"; }
with;    echo "after with:    $name"
without; echo "after without: $name"
output
inside with: changed by with
after with:    global
after without: changed by without

The local in with shadows the global only while the function runs. without assigns to the global itself, which is the default and usually a bug.

A missing local clobbers the caller's loop variable

helper() { i=99; }
for ((i = 0; i < 3; i++)); do helper; echo "i=$i"; done
output
i=99

helper uses i for its own purposes, but without local that is the caller's loop counter. It becomes 99 and the loop ends after one round. local i=99 inside helper would fix it. (Calling the function through $(...) would hide the bug, because that runs it in a subshell.)

Locals are visible to called functions

show() { echo "show sees level=$level"; }
outer() { local level=outer; show; }
level=global
outer
show
output
show sees level=outer
show sees level=global

show has no level of its own, so it looks it up at call time and finds outer's local. Called directly, it sees the global. This is dynamic scoping.

local hides the exit status of command substitution

bad()  { local out=$(grep -c CRITICAL logs/app.log); echo "status after local=: $?"; }
good() { local out; out=$(grep -c CRITICAL logs/app.log); echo "status after split: $?"; }
bad; good
output
status after local=: 0
status after split: 1

local itself succeeded, so $? is 0 even though grep found nothing and returned 1. Declaring on one line and assigning on the next keeps grep's status, which set -e and if need to see.

local only works inside a function

local x=1
echo "status $?"
output
/bin/bash: line 1: local: can only be used in a function
status 1

At the top level, bash prints an error and local returns 1. Use a plain assignment, or wrap the code in a function.

Local arrays and integers

stats() {
  local -a nums
  local -i sum=0 n
  mapfile -t nums < data/numbers.txt
  for n in "${nums[@]}"; do sum+=n; done
  echo "${#nums[@]} numbers, sum $sum"
}
stats
echo "outside: nums has ${#nums[@]} elements"
output
10 numbers, sum 324
outside: nums has 0 elements

local takes declare's options: -a for an array, -i for integer arithmetic on assignment (so sum+=n adds). None of the variables leak out of the function.

Restore shell options when the function returns with local -

literal() { local -; set -f; echo "inside:  " *.txt; }
literal
echo "outside: " *.txt
output
inside:   *.txt
outside:  file with spaces.txt notes.txt todo.txt

local - (bash 4.4 and later) saves the set options and puts them back when the function returns. Here set -f turns off globbing inside literal only, so *.txt prints as is there and expands to file names afterward.

Make a variable unset inside one function

export LANG=C.UTF-8
plain() { local LANG; unset LANG; echo "inside: LANG=[${LANG-unset}]"; }
plain
echo "outside: LANG=$LANG"
output
inside: LANG=[unset]
outside: LANG=C.UTF-8

local LANG makes a local copy, and unset then removes only that copy. The caller's value returns when the function ends.

Common pitfalls

  • Forgetting local for loop counters and temporary variables: the function silently overwrites the caller's variables of the same name.

  • local var=$(cmd) always returns 0. Split it into local var and var=$(cmd) when the status matters.

  • Assuming locals are private: functions called from yours can read and modify them (dynamic scoping). Use distinctive names in library code.

  • A local array declared with local arr=(...) is fine, but local -A is required for associative arrays, just as with declare.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • local is not in POSIX, but dash, busybox sh, ksh93 (as typeset) and zsh all have it. dash's local does not take declare-style options such as -a or -i.

Options worth knowing

A selection, described in our own words. For the full list, see bash(1), SHELL BUILTIN COMMANDS.

OptionWhat it does
-aMake a local indexed array.
-AMake a local associative array.
-iGive the local the integer attribute.
-rMake the local read-only for the rest of the function.
-nMake the local a reference to the variable named by its value.
-xExport the local to commands the function runs.
-Save the shell options and restore them when the function returns (bash 4.4+).

Related