Making some sense of shell environment variables

July 29, 2019 - 4 minute read -
shell bash env export

When it comes to shell scripting, although I’ve developed on UNIX-like systems for years and there’s even been times when my primary task was writing shell scripts, I seem to only ever retain a level of understanding that “gets the job done”.

One quickly forgotten topic is the scoping behaviour of shell and environment variables. So I thought I’d refresh my knowledge and write about it.

Simple shell variables

First, some basics.

In the Bash shell (sh/bash), you define variables by declaring a key-value pair:

VAR1=squirrel

This gives you a shell variable, VAR1, whose value you can see with echo command, referencing the variable using the $ prefix:

echo $VAR1
squirrel

You can use single quotes to handle significant spaces and to express reserved symbols. Single quotes provide strong quoting - everything is interpreted as a string.

VAR1='red squirrel'
echo $VAR1
red squirrel
VAR2='I set $VAR1'
echo $VAR2
I set $VAR1

Similary, you can use double quotes to handle significant spaces but also to perform variable expansion and command substitution. Double quotes provide weak quoting.

VAR1="red squirrel"
VAR2="I can see a $VAR1"
echo $VAR2
I can see a red squirrel

This quoting behaviour applies generally to Bash.

Variable scoping

The variable scope is characterised by the processes in which they are visible.

Processes are arranged in a tree structure with parent and child relationships between them: a process that starts another is the parent, and the newly created one is the child or sub-process.

Consequently variables are classified into two categories:

  • Shell variables
  • Environment variables

Shell variables are only visible to the current shell whereas environment variables are also visible to sub-processes of the shell.

Exporting shell variables to make environment variables

Previously section, we were defining shell variables which, by their nature, are only visible inside the current shell.

In Bash, you use the export command to make a shell variable available to child processes - i.e. to make it an environment variable.

Let’s define a little script: print_env.sh with execute permissions.

#!/bin/sh
echo "VAR1 is:" $VAR1

Running a script starts a child process which inherits its environment variables from the variables exported by the parent shell.

So if we don’t export the variable VAR1, then the child process won’t see it.

Case in point:

VAR1="fish"
echo $VAR1
fish
./print_env.sh 
VAR1 is:

VAR1 isn’t defined in the script’s shell.

So let’s make VAR1 visible by exporting it from the original shell and then rerunning the script:

export VAR1
./print_env.sh 
VAR1 is: fish

Typically the definition and exporting are combined into the one command:

export VAR1=dog

Some important caveats:

  • The value received by the child is a copy of the parent’s variable taken at the time when the child process starts.
  • You can’t modify the enviroment of a parent shell.

Unsetting and demoting

You can use the unset command to delete a variable.

VAR1=tiger
echo $VAR1
tiger

unset VAR1
echo $VAR1

If you no longer want to export a variable, but still keep it defined in the current shell use export -n

export -n VAR1

Working with environment files

You can define an environment by adding the variable definitions to a file and using the source command to apply it.

source is a Bash shell built-in command that executes the content of the file passed as argument, in the current shell. The file can set shell variables and environment variables.

Let’s create a file, shell.env

VAR1=abc
export VAR2=def
export VAR3=ghi

Then initialise our current shell via source shell.env

source shell.env
echo $VAR1
abc
echo $VAR2
def
echo $VAR3
ghi

If we’d instead put these variable definitions into an executable script and run it, then it would have initiated its own short-lived environment, rather than our current shell.

Environment wrappers

If you want to run a script with a specific environment without affecting the current shell, then use the env command to supply a short-lived environment to a process.

For example:

env $(cat shell.env) ./print_env.sh` 
VAR1 is: abc

echo $VAR1

That’s it for now. I hope you find these notes helpful!

This post is Creative Commons Attribution 4.0 International (CC BY 4.0) licensed.