122

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
2

16 Answers 16

78

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"
21
  • I just had occasion to use this in tcsh, and noticed that it doesn't work without the shebang. Seems a bit odd for the behavior to change if you're just sourcing it, not executing it...
    – Cascabel
    Commented Apr 20, 2011 at 16:28
  • 1
    The tcsh version also doesn't seem to work if the script is sourced noninteractively (e.g. from a cshrc). I can't seem to find a way to get the information in that case. Any thoughts?
    – Cascabel
    Commented May 9, 2011 at 18:04
  • Sourcing it works for me without the shebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. As far as sourcing it non-interactively, the source file is included into the parent file as if it were actually a part of it (indistinguishably so) as you mention in your original question. I think your positional parameter workaround is probably the best approach. However, the usual question is "why do you want to do that" and the usual answer to the reply is "don't do that - do this instead" where "this" is often to store... Commented May 10, 2011 at 15:33
  • 2
    @clacke: I find that in all the versions of Bash that I tested from 2.05b to 4.2.37, including 4.1.9, that . and source worked identically in this regard. Note that $_ must be accessed in the first statement in the file, otherwise it will contain the last argument of the previous command. I like to include the shebang for my own reference so I know what shell it's supposed to be for and for the editor so it uses syntax highlighting. Commented Mar 4, 2013 at 11:51
  • 1
    Haha. Obviously I was testing by first doing source, then doing .. I apologize for being incompetent. They are indeed identical. Anyway, $BASH_SOURCE works.
    – clacke
    Commented Mar 5, 2013 at 7:35
43

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.

5
  • 3
    And note that source searches in $PATH if the given name doesn't contain a /. The search order depends on shell options, refer to the manual for details. Commented Dec 8, 2010 at 19:09
  • 1
    So, something like mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)" would work? Commented Dec 8, 2010 at 19:36
  • Thanks, a quick and helpful answer. Dennis wins the green check mark for giving a tcsh answer too. @Gilles: Right, I did find that in the documentation. Fortunately for my use case I almost certainly don't have to worry about it.
    – Cascabel
    Commented Dec 8, 2010 at 19:46
  • @KevinCantu, the output of cd should be redirected to /dev/null. Normally it doesn't output anything but it will if CDPATH is set.
    – Noel Yap
    Commented Jun 9, 2021 at 17:53
  • It looks like BASH_SOURCE is actually an array, so it's better to use its last element, via "${BASH_SOURCE[-1]}", as @gkb0986 explains here and as I show in my brand new answer here, so that it works when the script is called from within bash functions as well. Commented Feb 28, 2022 at 22:23
32

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]

2
  • This solution worked for me in bash while the selected answer worked only intermittently. I never did figure out why it worked sometimes and not others (maybe I wasn't paying close enough attention to the sourcing shell).
    – Jim2B
    Commented Sep 9, 2015 at 15:45
  • Thank you for this answer! It was very helpful. I've improved upon it in my own answer here, and cited your answer in the references. Commented Feb 28, 2022 at 22:10
25

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.

21

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

$
4
  • 1
    I don't understand: why called=$_; echo $called; echo $_? Won't this print $_ twice? Commented Sep 14, 2014 at 7:17
  • 5
    @CiroSantilli: Not always, read the Bash manual on the $_ special parameter: "At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list. Subsequently, expands to the last argument to the previous command, after expansion. Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command. When checking mail, this parameter holds the name of the mail file." Commented Sep 17, 2014 at 19:14
  • Problem with this is the sourced file has header #! /bin/sh which makes it useless to source. That would start a new instance of /bin/sh, set variables, then exit that instance, leaving the calling instance unchanged. Commented Mar 6, 2019 at 5:39
  • 6
    @JamesThomasMoon1979: What are you talking about?  Anything beginning with # in a shell script is a comment.  #! (shebang) has its special meaning only as the first line of a script that is executed.  As the first line of a file that is sourced, it’s just a comment. Commented Jun 9, 2019 at 4:17
4

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.
1
4

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.

1

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
1

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)
1
  • Really the best solution to be POSIX compliant. Simply defining the path in the main script, so there is no need to figure it out in the sourced script. Commented Jan 19, 2023 at 22:51
1

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]}.

0

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)
0

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
0

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)
}
0

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.

-1

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

$(readlink -f ${BASH_SOURCE[0]})
-2
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.

1
  • 1
    It was explained in the question that $0 gets you information about the currently running script, not a sourced one. Commented Jun 9, 2019 at 4:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .