Statements: Include
Inclusion of a file into a shell script is easy with .
(or source
which is the same thing). .
will be searching along the contents of the
PATH
environment variable for the shell script of the given name. If you gave
an absolute or relative path, it will not search PATH.
The bash allows you to pass arguments to the included code, something the sh can’t do.
cat <<EOF > foo.sh
printf "args: %s\n" "\$1"
EOF
cat <<EOF > bar.sh
#! /usr/bin/env bash
. "./foo.sh" "hello" || exit 1
EOF
chmod 755 foo.sh bar.sh
./bar.sh
The result here will be “hello”.
Lets turn .
into a general purpose include facility. It should be able to
include library script files of the executing script and shared script
files that have already been installed by second or third parties.
Scheme 1
Search for global scripts in /usr/local/lib:/usr/lib:/lib
like the system
linker does. Search for local scripts in a “libexec” folder, that is close to
the executing script (or include file):
# include appends .sh to the name and includes that file
include()
{
local name="$1"; shift
local execname
execname="${0##*/}" # /usr/local/bin/foo -> foo
local libexec
libexec="${0%/*}" # /usr/local/bin/foo -> /usr/local/bin
libexec="${libexec%/*}" # /usr/local/bin -> /usr/local
libexec="${libexec}/libexec" # /usr/local + /libexec -> /usr/local/libexec
local filename
filename="${name}.sh"
PATH="${libexec}:/usr/local/lib:/usr/lib:/lib" . "${filename}" "$@" || exit 1
}
Note
This code is a bit too simplistic. First it needs an absolute path for
$0
. Second the meaning of$0
depends on the shell and the location of where the include code actually is and is executed.
Scheme 2
Libraries are found by an executable placed into bin
. Query the executable for
its “libexec” directory. This has the advantage of being more independent of
platform conventions:
include()
{
local name="$1"; shift
local executable="$1"; shift
# use own executable as fallback
executable="${executable:-$0}"
local libexec
# /usr/local/bin/foo-> /usr/local/libexec/foo
libexec=$( "${executable}" libexec-dir) || exit 1
local filename
filename="${name}.sh"
. "${libexec}/${filename}" "$@" || exit 1
}
This is simpler and more flexible.
Include file template
Taking cues from C headers, a shell library file that protects from double inclusion and that always returns true would look like this for a file “bar.sh” of an executable “foo:
# use `__` to separate executable and file, so foo-bar baz and foo bar-baz are
# distinguishable
if [ -z "${FOO__BAR_SH}" ]
then
FOO__BAR_SH="included"
# library code
fi
: # ensure positive return value
Adhering to this convention, now one can test before the inclusion, if the file is already present:
if [ -v FOO__BAR_SH ]
then
include "foo"
fi
The check for the inclusion guard can be moved into the “include” function
itself, see mulle-bashloader.sh for a real life example. That way you can
omit the if [ -z "${FOO__BAR_SH}" ]
code in the library file entirely