Bash shell basics — scoping

Paul Guerin
5 min readMar 10, 2023

Programming languages follow either:

  • Lexical scoping, or
  • Dynamic scoping

Most languages follow the lexical scoping. But the Bash shell is one of the few languages that follows the dynamic scoping rules.

The shell uses dynamic scoping to control a variable’s visibility within functions.

Let’s explore what this actually means for your Bash shell script.

Passing of arguments

Even though Bash follows the dynamic scoping rules, the rules for passing arguments also still apply.

Passing arguments is easy to do in Bash, and can even pass arguments from the command line.

#!/bin/bash

function func {
# display the arguments that were passed from main
echo 'func:' $1 $2 $3
}

main() {
# display the arguments that were passed from global
echo 'main:' $1 $2 $3

#
func x y z
}

# display the arguments that were passed from the command line
echo 'global:' $1 $2 $3

main u v w

exit

Run the script like this to pass 3 arguments from the command line:

./test.sh 1 2 3

With Bash, it’s easy to pass arguments from the command line to the global area. Also from the global area to the main function, and from the main function to any called function.

Arguments can be used to pass values to functions, but we can use dynamic scoping to make the passing of values easier.

Dynamic scoping

Dynamic scoping allows variables not passed as arguments, to be made available to functions.

For example, if a variable var is declared as local in function func1, and func1 calls another function func2, references to var made from within func2 will resolve to the local variable var from func1, shadowing any global variable named var.

Effectively, it means a variable just needs to be defined, and it will be accessible by any called function after that.

Let’s see this behaviour in action:

Environment variables

Dynamic scoping allows any environment variable to be made available to the Bash script.

#!/bin/bash

function func {
# reference the command line environment variable from here too
echo 'func:' $test
}

main() {
# reference the command line environment variable from here too
echo 'main:' $test

# Note: no need to specify arguments for globals
func
}

# reference the command line environment variable from here
echo 'global:' $test

main

exit

We just need to define the environment variable at the Bash prompt, and run the Bash script.

test='this is an environment variable' ./test.sh

So the environment variable is available to the Bash script globally, also inside the main function, and any function called after that.

Main variables

Dynamic scoping applies to variables defined in the main function too.

The following script defines an variable in the main function, but doesn’t pass an argument to the called function.

#!/bin/bash

function func {
echo 'func:' $y
}

main() {
# define y globally inside here for the first time
y='main variable'
echo 'main: '$y

# Note: no need to specify arguments for globals
func
}

main

# reference global variable y from outside main
echo 'global: '$y

exit

However, dynamic scoping allows the variable defined in the main function, to be available globally, and also inside the called function.

So variables can be accessed anywhere.

But what about changing the variable from anywhere?

Let’s use the following script to change the variable, that was defined inside main, from somewhere else.

#!/bin/bash

function func {
echo 'func:' $y
}

main() {
# define y globally inside here
y='main variable'

# Note: no need to specify arguments for globals
func
}

main

# global variable y can be changed from anywhere
y='main variable can be changed'

echo 'global: '$y

exit

So the main variable can be changed from outside of main, and that could lead to confusion about about how a script works.

It would be less confusing to prevent a variable to be made available everywhere else in the script.

Fortunately, Bash allows a way to assign variables as local.

Dynamic scope with local variables

Let’s examine a script that uses a local variable defined inside the main function.

#!/bin/bash

function func {
echo 'func:' $x
}

main() {
# define x locally
local x='local main variable'

# Note: no need to specify arguments for locals either
func
}

main

exit

As expected variable x was declared in the main function, and is available to any function called from main.

Let’s test the scope by attempting to access the variable globally.

#!/bin/bash

function func {
echo 'func:' $x
}

main() {
# define x locally
local x='local main variable'

# Note: no need to specify arguments for locals either
func
}

main

# attempt to reference a local variable from here
echo 'global: '$x

exit

The script proves that defining a local variable in main, makes the variable available to any function called, but is not available globally.

--

--