Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Is there a way for a sourced shell script to find out 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 a ton of 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 currently is to do source $script $script, so that the first positional parameter contains the necessary information. Anyone have a better way?

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

source foo.bash
share|improve this question

6 Answers 6

up vote 14 down vote accepted

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
called=$_
[[ $called != $0 ]] && echo "Script is being sourced" || echo "Script is being run"
echo "\$BASH_SOURCE ${BASH_SOURCE[@]}"
share|improve this answer
    
Wow, that's exactly what I was looking for, and I can barely find any reference to it in the documentation. Way to know your stuff. –  Jefromi Dec 8 '10 at 19:42
    
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... –  Jefromi Apr 20 '11 at 16:28
    
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? –  Jefromi May 9 '11 at 18:04
1  
@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. –  Dennis Williamson Mar 4 '13 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 Mar 5 '13 at 7:35

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.

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]

share|improve this answer
    
Can't see why this was downvoted. The accepted answer doesn't let you find out what directory the script is in from within a function. But the proposed solution does. Knee-jerk downvotes don't help the community. –  gkb0986 23 hours ago
    
@mikeserv I wouldn't be so sure. The question says "I'm mainly concerned with bash, though I have some coworkers who use tcsh". It also includes the suggestive invocation source foo.bash. I've edited my answer to include at the top that this is a bash only solution. –  gkb0986 23 hours ago

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
share|improve this answer

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

$

Replacing $BASH_SOURCE with $DASH_SOURCE resulted in the same thing.

share|improve this answer

Actually, "dirname $0" will get you the path to the script, but you have to interpret it a bit:

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

You have to prepare to handle "." as the directory name under some common circumstances. I'd experiment a bit, as I remember the dirname built-in to ksh doing things a bit differently when "." appears in PATH.

share|improve this answer
4  
This is a sourced script, not an executed script. $0 simply contains "bash" for an interactive shell, and that's all the sourced script sees. –  Jefromi Dec 8 '10 at 16:32

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

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.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.

share|improve this answer
1  
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. –  Gilles Dec 8 '10 at 19:09
    
So, something like mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)" would work? –  Kevin Cantu Dec 8 '10 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. –  Jefromi Dec 8 '10 at 19:46

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.