The POSIX-shell (sh
) counterpart of $BASH_SOURCE
is $0
. see bottom for background info
Caveat: The crucial difference is that if your script is being sourced (loaded into the current shell with .
), the snippets below will not work properly. explanation further below
Note that I've changed DIR
to dir
in the snippets below, because it's better not to use all-uppercase variable names so as to avoid clashes with environment variables and special shell variables.
The CDPATH=
prefix takes the place of > /dev/null
in the original command: $CDPATH
is set to a null string so as to ensure that cd
never echoes anything.
In the simplest case, this will do (the equivalent of the OP's command):
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
If you also want to resolve the resulting directory path to its ultimate target in case the directory and/or its components are symlinks, add -P
to the pwd
command:
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
Caveat: This is NOT the same as finding the script's own true directory of origin:
Let's say your script foo
is symlinked to /usr/local/bin/foo
in the $PATH
, but its true path is /foodir/bin/foo
.
The above will still report /usr/local/bin
, because the symlink resolution (-P
) is applied to the directory, /usr/local/bin
, rather than to the script itself.
To find the script's own true directory of origin, you'd have to inspect the script's path to see if it's a symlink and, if so, follow the (chain of) symlinks to the ultimate target file, and then extract the directory path from the target file's canonical path.
GNU's readlink -f
(better: readlink -e
) could do that for you, but readlink
is not a POSIX utility.
While BSD platforms, including macOS, have a readlink
utility too, on macOS it doesn't support -f
's functionality. That said, to show how simple the task becomes if readlink -f
is available:
dir=$(dirname "$(readlink -f -- "$0")")
.
In fact, there is no POSIX utility for resolving file symlinks.
There are ways to work around that, but they're cumbersome and not fully robust:
The following, POSIX-compliant shell function implements what GNU's readlink -e
does and is a reasonably robust solution that only fails in two rare edge cases:
- paths with embedded newlines (very rare)
- filenames containing literal string
->
(also rare)
With this function, named rreadlink
, defined, the following determines the script's true directory path of origin:
dir=$(dirname -- "$(rreadlink "$0")")
Note: If you're willing to assume the presence of a (non-POSIX) readlink
utility - which would cover macOS, FreeBSD and Linux - a similar, but simpler solution can be found in this answer to a related question.
rreadlink()
source code - place before calls to it in scripts:
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that `command`
# itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have
# an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that
# to happen.
{ unalias command; unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s
' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s
' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s
' "$(command dirname -- "${targetDir}")"
else
command printf '%s
' "${targetDir%/}/$fname"
fi
)
To be robust and predictable, the function uses command
to ensure that only shell builtins or external utilities are called (ignores overloads in the forms of aliases and functions).
It's been tested in recent versions of the following shells: bash
, dash
, ksh
, zsh
.
How to handle sourced invocations:
tl;dr:
Using POSIX features only:
- You cannot determine the script's path in a sourced invocation (except in
zsh
, which, however, doesn't usually act as sh
).
- You can detect whether or not your script is being sourced ONLY if your script is being sourced directly by the shell (such as in a shell profile/initialization file; possibly via a chain of sourcings), by comparing
$0
to the shell executable name/path (except in zsh
, where, as noted $0
is truly the current script's path). By contrast (except in zsh
), a script being sourced from another script that itself was directly invoked, contains that script's path in $0
.
- To solve these problems,
bash
, ksh
, and zsh
have nonstandard features that do allow determining the actual script path even in sourced scenarios and also detecting whether a script is being sourced or not; for instance, in bash
, $BASH_SOURCE
always contains the running script's path, whether it's being sourced or not, and [[ $0 != "$BASH_SOURCE" ]]
can be used to test whether the script is being sourced.
To show why this cannot be done, let's analyze the command from Walter A's answer:
# NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
- (Two asides:
- Using
-P
twice is redundant - it's sufficient to use it with pwd
.
- The command is missing silencing of
cd
's potential stdout output, if $CDPATH
happens to be set.)
command -v -- "$0"
command -v -- "$0"
is designed to cover one additional scenario: if the script is being sourced from an interactive shell, $0
typically contains the mere filename of the shell executable (sh
), in which case dirname
would simply return .
(because that's what dirname
invariably does when given a argument without a path component).
command -v -- "$0"
then returns that shell's absolute path through a $PATH
lookup (/bin/sh
). Note, however, that login shells on some platforms (e.g., OSX) have their filename prefixed with -
in $0
(-sh
), in which case command -v -- "$0"
doesn't work as intended (returns an empty string).
- Conversely,
command -v -- "$0"
can misbehave in two non-sourced scenarios in which the shell executable, sh
, is directly invoked, with the script as an argument:
- if the script itself is not executable:
command -v -- "$0"
may return an empty string, depending on what specific shell acts as sh
on a given system: bash
, ksh
, and zsh
return an empty string; only dash
echoes $0
The POSIX spec. for command
doesn't explicitly say whether command -v
, when applied to a filesystem path, should only return executable files - which is what bash
, ksh
, and zsh
do - but you can argue that it is implied by the very purpose of command
; curiously, dash
, which is usually the most compliant POSIX citizen, is deviating from the standard here. By contrast, ksh
is the lone model citizen here, because it is the only one that reports executable files only and reports them with an absolute (albeit not normalized) path, as the spec requires.
- if the script is executable, but not in the
$PATH
, and the invocation uses its mere filename (e.g., sh myScript
), command -v -- "$0"
will also return the empty string, except in dash
.
- Given that the script's directory cannot be determined when the script is being sourced - because
$0
then doesn't contain that information (except in zsh
, which doesn't usually