In this tutorial, we’re going to learn how to include a file in a bash script. This is a way to import environment variables, reuse existing code, or execute one script from within another.
Two illustrative examples concern importing environment variables and building a library of functions.
2. The source Command
The built-in bash source command reads and executes the content of a file. If the sourced file is a bash script, the overall effect comes down to running it. We may use this command either in a terminal or inside a bash script.
To obtain documentation concerning this command, we should type help source in the terminal. We assume that Bash (Bourne again shell) is not in POSIX mode is through the whole tutorial.
2.1. Bash Variables and Definitions of Functions
When we execute the script using the source command, it is run in the same shell where we source it. Consequently, the script accesses all variables from the shell where the source command is issued. This communication works in the opposite direction – all definitions from the sourced file become available in the parent shell.
On the other hand, when we run a script with the bash command or just by typing its name, a new shell instance is created. Therefore, the script can access only variables and functions defined with keyword export in the parent shell. In addition, all definitions of the descendant shell disappear when it exits.
As a simple example, let’s write script test_var:
echo "expVar = $expVar"
echo "myVar = $myVar"
Let’s set the variables in the terminal then run the script in both ways to compare results:
export expVar = "Exported variable"
expVar = Exported variable
expVar = Exported
variable myVar = My variable
This characteristic makes the source command extremely useful to share content and functionality between different files.
2.2. Other Significant Properties
In addition, the source command’s features include:
- it searches for the script in folders listed in the PATH variable of the current shell
- it recognizes an absolute or relative path given to the script
- the script does not need to be executable
- it returns the status of the last executed command in the script
- if sourced script issues the exit command, the sourcing one exits, too
3. Passing Environment Variables From a File
In this example, we’re going to import a set of environment variables into a script. The well-known case is reading and setting variables from the user’s .bash_profile (or .profile).
This is especially useful in the case of cron jobs because the shell of cron usually has a very sparse set of environment variables.
Let’s assume that we want to pass all environment variables of the user’s interactive shell to the cron job script. So, let’s begin the script by sourcing profile files:
# do something useful
Of course, we don’t need to limit ourselves to reusing the user’s profiles. We can collect any useful and meaningful variables in the file and import them into the script.
4. Building a Library of Functions
Let’s create a library of reusable functions, define them in a separate file, and use them in another script afterward.
As an example, suppose our lib_example library contains two functions:
- my_name to find the name of the current user (with the whoami command)
- my_used_disk_space to calculate the disk space occupied by the user’s home directory (uses the du command). In addition, cut removes the folder name from the output of du.
Let’s define the functions:
du -sh ~ | cut -f1
With the help of the source command, we can access these functions from another script, lib_test:
echo "My name is $(my_name), I am using $(my_used_disk_space)"
Let’s start the script and check the result:
My name is joe, I am using 46G
5. Setting a Correct Path
Let’s take a closer look at the structure of the exemplary library from the previous section.
Both scripts, the sourced lib_example and the sourcing lib_test, are located in the same directory. However, command source lib_example tries to find the script in the directory where lib_test is invoked.
As a result, this solution is very error-prone. In fact, it works correctly only if we start the lib_test from the folder where it’s located. After calling the script from another directory, we obtain a bunch of errors.
Let’s examine the ways to improve the structure of our library.
5.1. Providing an Absolute Path
We can precede the name of the sourced script with its full path:
It’s a rock-solid solution, although it demands rewriting the script when we change its location.
5.2. Using the dirname Command
Let’s try to avoid the hardcoding of paths. We know that both scripts are in the same folder. Furthermore, we use dirname to extract the path from the name of the wrapper script.
We should change the argument of the source command in the lib_test script to:
source $(dirname "$0")/lib_example
The dirname argument $0 is just the full name of the sourcing script.
5.3. Make Use of the PATH Environment Variable
In addition, since source searches for files in folders listed in PATH, we should reorganize our library.
Let’s examine the PATH of our exemplary user joe:
Now, we’re going to copy lib_example to the /home/joe/.local/bin folder.
Hence, the sourcing is now in the same form as at the very beginning – source lib_example – but the sourced script may be found on PATH, and the script lib_test works correctly.
6. Passing Arguments With the source Command
Bash scripts accept arguments. Accordingly, the source command allows passing arguments to the script. However, in the case of sourcing from another script, the logic behind is a little more complicated than the use in the terminal.
Let’s start with the simplest example, a script arg_list that outputs all of its bash arguments:
echo "My argument(s): $@"
Let’s check how it works inside another script, arg_test:
source arg_list foo_bar
Afterward, we start the new script with two arguments:
./arg_test foo bar
My argument(s): foo_bar
We should notice that source passes the argument foo_bar to the included script independently of the arguments foo and bar of the wrapping script.
Let’s change arg_test a bit to show an essential feature of the source command. We’re going to skip the argument of the arg_list script:
One more time, let’s examine the result:
./arg_test foo bar
My argument(s): foo bar
So, it’s different from the expected empty string.
In fact, inside another script, source passes the arguments of the wrapping script to the sourced one, unless the latter’s arguments are explicitly given.
This is an important point that we should be aware of – mainly if the operation of the included script relies on the number of its arguments.
7. Availability and Compatibility
The usefulness of the source command is somehow reduced by its prevalence. Hence, we should be aware of different implementations of the bash shell language.
As a matter of fact, only Bourne again shell (Bash) provides this command in this form. Debian Almquist Shell (Dash) or Bourne Shell (sh), to name only a few, offer the . command (‘dot’) instead. Of course, ‘dot’ is present in Bash, too.
The purpose of the ‘dot’ command is the same as that of source, but its functionality is slightly different.
On the other hand, its advantage is the POSIX compliance. As a result, scripts that use ‘dot’ are portable and run with any POSIX compliant bash language implementation.
Moreover, Bash can also work in the POSIX mode, subsequently changing the behavior of source. Without going into details, the presented description of the command concerns Bash in a non-POSIX mode.
In this article, we learned about the source command. We applied it to include files into a bash script. Subsequently, we studied cases of importing shell variables and setting up a library of functions.