121

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
Kusalananda
  • 333,661
Cascabel
  • 1,651

16 Answers16

77

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"
  • 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 Apr 20 '11 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 May 09 '11 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... – Dennis Williamson May 10 '11 at 15:33
  • ...the data file in a fixed location rather than trying to come up with a method of determining where the script lives and accessing the data (or config, etc.) relative to that. – Dennis Williamson May 10 '11 at 15:35
  • @Dennis: Yeah, I'd certainly prefer not to deal with this. But my workplace uses a simulation framework which is completely dependent on environment variables for building (OUR_CFLAGS kinds of things), so you source a script to get them set up (or you'd never have a prayer of getting it to build), and everything uses absolute paths, and.... I think you get the idea. – Cascabel May 10 '11 at 16:55
  • If the file is meant to be sourced, it is probably best to not have the shebang and to not set the file executable. – clacke Mar 04 '13 at 11:08
  • Interesting. In bash 4.1.2, sourcing with source does not set $_, but sourcing with . does. – clacke Mar 04 '13 at 11:14
  • 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. – Dennis Williamson Mar 04 '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 05 '13 at 07:35
  • This doesn't work to find the location of a script sourced by another script! – reinierpost Mar 28 '14 at 12:59
  • @reinierpost: You don't say which one doesn't work (Bash or tcsh) or in what way it doesn't. In Bash, the $BASH_SOURCE array contains the chain of callers. – Dennis Williamson Mar 28 '14 at 15:29
  • @Dennis Williamson: The tcsh solution prints the pathname of the executing/sourcing script, not the sourced one. The bash solution doesn't work ($_ and $BASH_SOURCE do not seem to exist) if the executing/sourcing script is run with /bin/sh, even when /bin/sh is bash. – reinierpost Apr 01 '14 at 08:19
  • If you use #/usr/bin/env bash instead of #!/bin/bash in caller script, this wouldn't work. – Dilawar Jul 11 '15 at 12:48
  • I had to use [[ "${called##*/}" != "${0##*/}" ]] for bash because $_ contains the absolute path. This hack essentially compares basenames. – KingPong Nov 13 '15 at 23:03
  • Using bash 4.3.11, "echo $" as the first line of my script prints the last line of my bashrc when running in a fresh shell. I see no mention of $ in the bash manpage except in regard to MAILDIR. In zsh, $_ seems to always be equal to $0. – Paul Brannan Mar 15 '17 at 13:58
  • @PaulBrannan: It's documented in the Special Parameters section of the manual and in the corresponding section of the man page. It's shown as an underscore without the dollar sign which makes it hard to search for. The reason you get the last line of your ~/.bashrc is because that file is effectively sourced so the "Subsequently, expands to the last argument to the previous command, after expansion." behavior comes into play (which would be the command itself if there are no arguments). – Dennis Williamson Mar 15 '17 at 23:40
  • The bash example works when being executed from bash, but not from tcsh. For an example, try tcsh -c '/path/to/myscript.sh' – nispio Aug 24 '18 at 23:56
  • @nispio That's why there are two separate examples - one for Bash and one for tcsh. – Dennis Williamson Aug 25 '18 at 02:25
  • @DennisWilliamson I am talking about a tcsh user who decides to execute my bash script. This is a very normal use case, but is not supported by the bash example above. – nispio Aug 27 '18 at 22:08
  • @nispio: I've made a change that seems to work in all cases (except sourcing it from tcsh which won't work because the syntax is completely different). Thanks for the report. – Dennis Williamson Aug 28 '18 at 00:27
  • It seems like this answer isn't answering the question at all (determining the path to a sourced shell script--I've just added an answer on that here), but instead is answering this totally other question: How to detect if a script is being sourced [versus run]. If that's what is desired, here is a summary I have written of ways to do that too. – Gabriel Staples Feb 28 '22 at 22:14
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.

pbm
  • 25,387
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]

gkb0986
  • 2,181
  • 2
  • 14
  • 6
  • 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 Sep 09 '15 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. – Gabriel Staples Feb 28 '22 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

$
loxaxs
  • 1,924
Shawn J. Goff
  • 46,081
  • 1
    I don't understand: why called=$_; echo $called; echo $_? Won't this print $_ twice? – Ciro Santilli OurBigBook.com Sep 14 '14 at 07: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." – Adam Rosenfield Sep 17 '14 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. – JamesThomasMoon Mar 06 '19 at 05: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. – Scott - Слава Україні Jun 09 '19 at 04: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.
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.

dols3m
  • 141
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
Matt
  • 813
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]}.

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)
Hutch
  • 29
  • 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. – Martin Braun Jan 19 '23 at 22:51
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)
}
maoizm
  • 103
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.

gen1us
  • 1
-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.