How to determine the path to a sourced tcsh or bash shell script from within the script

Is there a way for a sourced shell script to find the path to itself? I’m mainly concerned with bash, though I have some coworkers who use tcsh.

I’m guessing I may not have much luck here since sourcing causes commands to be executed in the current shell, so $0 is still the current shell’s invocation, not the sourced script. My best thought is to do source $script $script so that the first positional parameter contains the necessary information. Does anyone have a better way?

To be clear, I am sourcing the script, not running it:

source foo.bash
Asked By: Cascabel

||

I think that you could use $BASH_SOURCE variable. It returns path that was executed:

Example script:

print_script_path.sh:

#!/usr/bin/env bash

echo "$BASH_SOURCE"

Make it executable:

chmod +x print_script_path.sh

Example runs and their output, for different locations of this script:

pbm@tauri ~ $ /home/pbm/print_script_path.sh 
/home/pbm/print_script_path.sh
pbm@tauri ~ $ ./print_script_path.sh
./print_script_path.sh
pbm@tauri ~ $ source /home/pbm/print_script_path.sh 
/home/pbm/print_script_path.sh
pbm@tauri ~ $ source ./print_script_path.sh
./print_script_path.sh

So in next step we should check if path is relative or not. If it’s not relative everything is ok. If it is we could check path with pwd, concatenate with / and $BASH_SOURCE.

Answered By: pbm

In tcsh, $_ at the beginning of the script will contain the location if the file was sourced and $0 contains it if it was run.

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

In Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Answered By: Dennis Williamson

For thoroughness and the sake of searchers, here is what these do…
It is a community wiki, so feel free to add other shell’s equivalents (obviously, $BASH_SOURCE will be different).

test.sh:

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh:

#! /bin/sh
source ./test.sh

Bash:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

Dash

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

Zsh

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$
Answered By: Shawn J. Goff

For the bash shell, I found @Dennis Williamson’s answer most helpful, but it didn’t work in the case of sudo. This does:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi
Answered By: Matt

This solution applies only to bash and not tcsh. Note that the commonly supplied answer ${BASH_SOURCE[0]} won’t work if you try to find the path from within a function.

I’ve found this line to always work, regardless of whether the file is being sourced or run as a script.

echo "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"

If you want to follow symlinks use readlink on the path you get above, recursively or non-recursively.

The double quotes prevent spaces in the paths from splitting the result to an array and are required not only for echo, but in any context this is used. Since echo inserts a single space between array items, the difference is hidden unless there are two or more consecutive spaces in the path.

Here’s a script to try it out and compare it to other proposed solutions. Invoke it as source test1/test2/test_script.sh or bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo "${BASH_SOURCE}"
echo "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo "${BASH_SOURCE}"
    echo "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside
#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo "${BASH_SOURCE}"
    echo "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
}

The reason the one-liner works is explained by the use of the BASH_SOURCE environment variable and its associate FUNCNAME.

BASH_SOURCE

An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.

FUNCNAME

An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

[Source: Bash manual]

Answered By: gkb0986

This worked for me in bash, dash, ksh, and zsh:

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

Output for these shells:

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

I tried to make it work for csh/tcsh, but it’s too hard; I’m sticking to POSIX.

Answered By: Paul Brannan

I was a bit confused by the community wiki answer (from Shawn J. Goff), so I wrote a script to sort things out. About $_, I found this: Usage of _ as an environment variable passed to a command. It’s an environment variable so it’s easy to test its value incorrectly.

Below is the script, then it’s output. They also are in this gist.

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "e[1;36m$@e[0m"
}

tell_file() {
    echo File `"$1"` is:
    echo ```
    cat "$1"
    echo ```
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "`$cmd` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "`$cmd` (when executable name is `sh`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "`$cmd` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "`$cmd` (via sourcing, when name is `sh`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "`$cmd` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "`$cmd` (via symlink, when name is `sh`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

Output of ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

What did we learn ?

$BASH_SOURCE

  • $BASH_SOURCE works in bash and only in bash.
  • The only difference with $0 is when the current file was sourced by
    another file. In that case, $BASH_PROFILE contains the name of the
    sourced file, rather than that of the souring file.

$0

  • In zsh, $0 has the same value as $BASH_SOURCE in bash.

$_

  • $_ is left untouched by dash and ksh.
  • In bash and zsh, $_ decays to the last argument of the last call.
  • bash initializes $_ to “bash”.
  • zsh leaves $_ untouched.
    (when sourcing, it`s just the result of the “last argument” rule).

Symlinks

  • When a script is called through a symlink, no variable contains any
    reference to the destination of the link, only its name.

ksh

  • Regarding those tests, ksh behaves like dash.

sh

  • When bash or zsh is called through a symlink named sh, regarding those tests, it behave like dash.
Answered By: Mathieu CAROFF

To make your script both bash- and zsh-compatible instead of using if statements you can simply write ${BASH_SOURCE[0]:-${(%):-%x}}. The resulting value will be taken from BASH_SOURCE[0] when it’s defined, and ${(%):-%x}} when BASH_SOURCE[0] is not defined.

Answered By: dols3m

tl;dr script=$(readlink -e -- "${BASH_SOURCE}") (for bash obviously)

$BASH_SOURCE test cases

given file /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'
     "($(readlink -e -- "${BASH_SOURCE}"))"

source the file in different manners

source from /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source from /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source from different relative paths /tmp/a and /var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

regarding $0

in all cases, if the script had the added command

echo '$0 '"(${0})"

then source the script always printed

$0 (bash)

however, if the script was run, e.g.

$> bash /tmp/source1.sh

then $0 would be string value /tmp/source1.sh.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)
Answered By: JamesThomasMoon

this answer describes how lsof and a bit of grep magic is the only thing that seems to stand a chance of working for nested sourced files under tcsh:

/usr/sbin/lsof +p $$ | grep -oE /.*source_me.tcsh
Answered By: Patrick Maupin
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

Maybe this will not work with symlinks or sourced files but work for normal files.
Taken as reference fro. @kenorb
No dirname, readlink, BASH_SOURCE.

Answered By: HemanthJabalpuri

The trickiest part is finding currently sourced file is for dash shell used as sh replacement in Ubuntu. The following code snippet can be used in the script being sourced to determine its absolute path. Tested in bash, zsh and dash invoked both as dash and sh.

NB: depends on modern realpath(1) utility from GNU coreutils package

NB: lsof(1) options should be verified as well because similar advices both from this and other pages did not work for me on Ubuntu 18 and 19 thus I had to reinvent this.

getShellName() {
    [ -n "$BASH" ] && echo ${BASH##/*/} && return
    [ -n "$ZSH_NAME" ] && echo $ZSH_NAME && return
    echo ${0##/*/}
}

getCurrentScript() {
    local result
    case "$(getShellName)" in
        bash )  result=${BASH_SOURCE[0]}
                ;;
        zsh )   emulate -L zsh
                result=${funcfiletrace[1]%:*}
                ;;
        dash | sh )
                result=$(
                    lsof -p $$ -Fn  
                    | tail --lines=1  
                    | xargs --max-args=2  
                    | cut --delimiter=' ' --fields=2
                )
                result=${result#n}
                ;;
        * )     result=$0
                ;;
    esac
    echo $(realpath $result)
}
Answered By: maoizm

If you need absolute path to the directory containing the script, you can use this snippet:

BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]:-$0}" )" >/dev/null 2>&1 && pwd )"

If you only need a relative path to the script:

SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"

These snippets should work on the different bash versions and zsh.

Answered By: gen1us

I use the code above to get the path of sourced bash file:

$(readlink -f ${BASH_SOURCE[0]})
Answered By: Pablo Almaguer

Here is the best answer I think. It solves a lot of problems not solved in the other answers, namely:

  1. realpath can find the absolute path from a relative path, and will expand symbolic links (use realpath -s instead to NOT expand symbolic links)
  2. "${BASH_SOURCE[-1]}" is cleaner and shorter than "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}", and allows this to work whether the script is run OR sourced, and even if the script being called gets called from within another bash function.
  3. I also demonstrate obtaining the script directory and filename, as shown below.
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
# OR, in case of **nested** source calls, it's possible you actually want
# the **first** index:
# FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[0]}")"
# OR, use `-s` to NOT expand symlinks:
# FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "${BASH_SOURCE[-1]}")"

SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

# Now print it all out
echo "FULL_PATH_TO_SCRIPT = "$FULL_PATH_TO_SCRIPT""
echo "SCRIPT_DIRECTORY    = "$SCRIPT_DIRECTORY""
echo "SCRIPT_FILENAME     = "$SCRIPT_FILENAME""

For a lot more details on this, including some notes on nested source calls and which index you may want from the BASH_SOURCE array, see my main answer on this in the first reference link just below.

References:

  1. My main answer on this: Stack Overflow: How can I get the source directory of a Bash script from within the script itself?
  2. How to retrieve absolute path given relative
  3. taught me about the BASH_SOURCE variable: Unix & Linux: determining path to sourced shell script
  4. taught me that BASH_SOURCE is actually an array, and we want the last element from it for it to work as expected inside a function (hence why I used "${BASH_SOURCE[-1]}" in my code here): Unix & Linux: determining path to sourced shell script
  5. man bash –> search for BASH_SOURCE:

    BASH_SOURCE

    An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.

Answered By: Gabriel Staples

mention these two lines inside your main script,

SCRIPT=`realpath $0`
SCRITPATH=`dirname $SCRIPT`

other script should be in the same folder

to use them,

bash $SCRITPATH/your_sub_script.sh #(this line also inside the main script)
Answered By: Hutch
Categories: Answers Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.