Bash shell basics — scoping
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.
Checkout some other Bash articles here: