How best to include other scripts?

Bash

Bash Problem Overview


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?

Bash Solutions


Solution 1 - Bash

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"

Solution 2 - Bash

I know I am late to the party, but this should work no matter how you start the script and uses builtins exclusively:

DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"

. (dot) command is an alias to source, $PWD is the Path for the Working Directory, BASH_SOURCE is an array variable whose members are the source filenames, ${string%substring} strips shortest match of $substring from back of $string

Solution 3 - Bash

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)

Solution 4 - Bash

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

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

Solution 5 - Bash

I think the best way to do this is to use the Chris Boran's way, BUT you should compute MY_DIR this way:

#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh

To quote the man pages for readlink:

> readlink - display value of a symbolic link > > ... > > -f, --canonicalize > canonicalize by following every symlink in every component of the given > name recursively; all but the last component must exist

I've never encountered a use case where MY_DIR is not correctly computed. If you access your script through a symlink in your $PATH it works.

Solution 6 - Bash

A combination of the answers to this question provides the most robust solution.

It worked for us in production-grade scripts with great support of dependencies and directory structure:

#!/bin/bash

Full path of the current script

THIS=readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0

The directory where current script resides

DIR=dirname "${THIS}"

'Dot' means 'source', i.e. 'include':

. "$DIR/compile.sh"

The method supports all of these:

  • Spaces in path
  • Links (via readlink)
  • ${BASH_SOURCE[0]} is more robust than $0

Solution 7 - Bash

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

Solution 8 - Bash

This works even if the script is sourced:

source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"

Solution 9 - Bash

1. Neatest

I explored almost every suggestion and here is the neatest one that worked for me:

script_root=$(dirname $(readlink -f $0))

It works even when the script is symlinked to a $PATH directory.

See it in action here: https://github.com/pendashteh/hcagent/blob/master/bin/hcagent

2. The coolest

# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e 's/^.\+-> //')

This is actually from another answer on this very page, but I'm adding it to my answer too!

3. The most reliable

Alternatively, in the rare case that those didn't work, here is the bullet proof approach:

# Copyright http://stackoverflow.com/a/7400673/257479
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

script_root=$(dirname $(whereis_realpath "$0"))

You can see it in action in taskrunner source: https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner

Hope this help someone out there :)

Also, please leave it as a comment if one did not work for you and mention your operating system and emulator. Thanks!

Solution 10 - Bash

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.

Solution 11 - Bash

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.

Solution 12 - Bash

Shell Script Loader is my solution for this.

It provides a function named include() that can be called many times in many scripts to refer a single script but will only load the script once. The function can accept complete paths or partial paths (script is searched in a search path). A similar function named load() is also provided that will load the scripts unconditionally.

It works for bash, ksh, pd ksh and zsh with optimized scripts for each one of them; and other shells that are generically compatible with the original sh like ash, dash, heirloom sh, etc., through a universal script that automatically optimizes its functions depending on the features the shell can provide.

[Fowarded example]

start.sh

This is an optional starter script. Placing the startup methods here is just a convenience and can be placed in the main script instead. This script is also not needed if the scripts are to be compiled.

#!/bin/sh

# load loader.sh
. loader.sh

# include directories to search path
loader_addpath /usr/lib/sh deps source

# load main script
load main.sh

main.sh

include a.sh
include b.sh

echo '---- main.sh ----'

# remove loader from shellspace since
# we no longer need it
loader_finish

# main procedures go from here

# ...

a.sh

include main.sh
include a.sh
include b.sh

echo '---- a.sh ----'

b.sh

include main.sh
include a.sh
include b.sh

echo '---- b.sh ----'

output:

---- b.sh ----
---- a.sh ----
---- main.sh ----

What's best is scripts based on it may also be compiled to form a single script with the available compiler.

Here's a project that uses it: http://sourceforge.net/p/playshell/code/ci/master/tree/. It can run portably with or without compiling the scripts. Compiling to produce a single script can also happen, and is helpful during installation.

I also created a simpler prototype for any conservative party that may want to have a brief idea of how an implementation script works: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype.bash. It's small and anyone can just include the code in their main script if they want to if their code is intended to run with Bash 4.0 or newer, and it also doesn't use eval.

Solution 13 - Bash

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"!

Solution 14 - Bash

This should work reliably:

source_relative() {
 local dir="${BASH_SOURCE%/*}"
 [[ -z "$dir" ]] && dir="$PWD"
 source "$dir/$1"
}

source_relative incl.sh

Solution 15 - Bash

I put all my startup scripts in a .bashrc.d directory. This is a common technique in such places as /etc/profile.d, etc.

while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE

The problem with the solution using globbing...

for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done

...is you might have a file list which is "too long". An approach like...

find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done

...runs but doesn't change the environment as desired.

Solution 16 - Bash

Using source or $0 will not give you the real path of your script. You could use the process id of the script to retrieve its real path

ls -l 		/proc/$$/fd 	      | 
grep 		"255 ->"			|
sed -e		's/^.\+-> //'

I am using this script and it has always served me well :)

Solution 17 - Bash

Of course, to each their own, but I think the block below is pretty solid. I believe this involves the "best" way to find a directory, and the "best" way to call another bash script:

scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh

echo "The main script"

So this may be the "best" way to include other scripts. This is based off another "best" answer that tells a bash script where it is stored

Solution 18 - Bash

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

Solution 19 - Bash

we just need to find out the folder where our incl.sh and main.sh is stored; just change your main.sh with this:

main.sh

#!/bin/bash

SCRIPT_NAME=$(basename $0)
SCRIPT_DIR="$(echo $0| sed "s/$SCRIPT_NAME//g")"
source $SCRIPT_DIR/incl.sh

echo "The main script"

Solution 20 - Bash

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

Solution 21 - Bash

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

Solution 22 - Bash

You can also use:

PWD=$(pwd)
source "$PWD/inc.sh"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAaron H.View Question on Stackoverflow
Solution 1 - BashChris BoranView Answer on Stackoverflow
Solution 2 - BashsaciiView Answer on Stackoverflow
Solution 3 - BashtardateView Answer on Stackoverflow
Solution 4 - BashdsmView Answer on Stackoverflow
Solution 5 - BashMat131View Answer on Stackoverflow
Solution 6 - BashBrian CannardView Answer on Stackoverflow
Solution 7 - BashMax ArnoldView Answer on Stackoverflow
Solution 8 - BashAlessandro PezzatoView Answer on Stackoverflow
Solution 9 - BashAlexarView Answer on Stackoverflow
Solution 10 - BashSteve BakerView Answer on Stackoverflow
Solution 11 - BashRob WellsView Answer on Stackoverflow
Solution 12 - BashkonsoleboxView Answer on Stackoverflow
Solution 13 - BashRob WellsView Answer on Stackoverflow
Solution 14 - BashPSkocikView Answer on Stackoverflow
Solution 15 - BashphreedView Answer on Stackoverflow
Solution 16 - BashfrancoisrvView Answer on Stackoverflow
Solution 17 - BashmodulitosView Answer on Stackoverflow
Solution 18 - BashXaqronView Answer on Stackoverflow
Solution 19 - BashfastrizwaanView Answer on Stackoverflow
Solution 20 - BashAlexander YancharukView Answer on Stackoverflow
Solution 21 - BashJoaommpView Answer on Stackoverflow
Solution 22 - BashDjangoView Answer on Stackoverflow