How best to include other scripts?

Viewed 382625

The way you would normally include a script is with "source"

eg:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"

incl.sh:

echo "The included script"

The output of executing "./main.sh" is:

The included script
The main script

... Now, if you attempt to execute that shell script from another location, it can't find the include unless it's in your path.

What's a good way to ensure that your script can find the include script, especially if for instance, the script needs to be portable?

22 Answers

I tend to make my scripts all be relative to one another. That way I can use dirname:

#!/bin/sh

my_dir="$(dirname "$0")"

"$my_dir/other_script.sh"

An alternative to:

scriptPath=$(dirname $0)

is:

scriptPath=${0%/*}

.. the advantage being not having the dependence on dirname, which is not a built-in command (and not always available in emulators)

If it is in the same directory you can use dirname $0:

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"

You need to specify the location of the other scripts, there is no other way around it. I'd recommend a configurable variable at the top of your script:

#!/bin/bash
installpath=/where/your/scripts/are

. $installpath/incl.sh

echo "The main script"

Alternatively, you can insist that the user maintain an environment variable indicating where your program home is at, like PROG_HOME or somesuch. This can be supplied for the user automatically by creating a script with that information in /etc/profile.d/, which will be sourced every time a user logs in.

I'd suggest that you create a setenv script whose sole purpose is to provide locations for various components across your system.

All other scripts would then source this script so that all locations are common across all scripts using the setenv script.

This is very useful when running cronjobs. You get a minimal environment when running cron, but if you make all cron scripts first include the setenv script then you are able to control and synchronise the environment that you want the cronjobs to execute in.

We used such a technique on our build monkey that was used for continuous integration across a project of about 2,000 kSLOC.

Steve's reply is definitely the correct technique but it should be refactored so that your installpath variable is in a separate environment script where all such declarations are made.

Then all scripts source that script and should installpath change, you only need to change it in one location. Makes things more, er, futureproof. God I hate that word! (-:

BTW You should really refer to the variable using ${installpath} when using it in the way shown in your example:

. ${installpath}/incl.sh

If the braces are left out, some shells will try and expand the variable "installpath/incl.sh"!

Personally put all libraries in a lib folder and use an import function to load them.

folder structure

enter image description here

script.sh contents

# Imports '.sh' files from 'lib' directory
function import()
{
  local file="./lib/$1.sh"
  local error="\e[31mError: \e[0mCannot find \e[1m$1\e[0m library at: \e[2m$file\e[0m"
  if [ -f "$file" ]; then
     source "$file"
    if [ -z $IMPORTED ]; then
      echo -e $error
      exit 1
    fi
  else
    echo -e $error
    exit 1
  fi
}

Note that this import function should be at the beginning of your script and then you can easily import your libraries like this:

import "utils"
import "requirements"

Add a single line at the top of each library (i.e. utils.sh):

IMPORTED="$BASH_SOURCE"

Now you have access to functions inside utils.sh and requirements.sh from script.sh

TODO: Write a linker to build a single sh file

According man hier suitable place for script includes is /usr/local/lib/

/usr/local/lib

Files associated with locally installed programs.

Personally I prefer /usr/local/lib/bash/includes for includes. There is bash-helper lib for including libs in that way:

#!/bin/bash

. /usr/local/lib/bash/includes/bash-helpers.sh

include api-client || exit 1                   # include shared functions
include mysql-status/query-builder || exit 1   # include script functions

# include script functions with status message
include mysql-status/process-checker; status 'process-checker' $? || exit 1
include mysql-status/nonexists; status 'nonexists' $? || exit 1

bash-helpers includes status output

Most of the answers I saw here seem to overcomplicate things. This method has always worked reliably for me:

FULLPATH=$(readlink -f $0)
INCPATH=${FULLPATH%/*}

INCPATH will hold the complete path of the script excluding the script filename, regardless of how the script is called (by $PATH, relative or absolute).

After that, one only needs to do this to include files in the same directory:

. $INCPATH/file_to_include.sh

Reference: TecPorto / Location independent includes

Related