I have the following code in my bash script. Now I wanna use it in POSIX sh. So how to convert it? thanks.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
I have the following code in my bash script. Now I wanna use it in POSIX sh. So how to convert it? thanks.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
@City responded that
works. I used it too.
I found the command at https://stackoverflow.com/questions/760110/can-i-get-the-absolute-path-to-the-current-script-in-kornshell.
The POSIX-shell (
sh
) counterpart of$BASH_SOURCE
is$0
. see bottom for background infoCaveat: 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 belowNote that I've changed
DIR
todir
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 thatcd
never echoes anything.In the simplest case, this will do (the equivalent of the OP's command):
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 thepwd
command: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, butreadlink
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 ifreadlink -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:->
(also rare)With this function, named
rreadlink
, defined, the following determines the script's true directory path of origin: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: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:
zsh
, which, however, doesn't usually act assh
).$0
to the shell executable name/path (except inzsh
, where, as noted$0
is truly the current script's path). By contrast (except inzsh
), a script being sourced from another script that itself was directly invoked, contains that script's path in$0
.bash
,ksh
, andzsh
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, inbash
,$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:
-P
twice is redundant - it's sufficient to use it withpwd
.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 casedirname
would simply return.
(because that's whatdirname
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 casecommand -v -- "$0"
doesn't work as intended (returns an empty string).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:command -v -- "$0"
may return an empty string, depending on what specific shell acts assh
on a given system:bash
,ksh
, andzsh
return an empty string; onlydash
echoes$0
The POSIX spec. for
command
doesn't explicitly say whethercommand -v
, when applied to a filesystem path, should only return executable files - which is whatbash
,ksh
, andzsh
do - but you can argue that it is implied by the very purpose ofcommand
; 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.$PATH
, and the invocation uses its mere filename (e.g.,sh myScript
),command -v -- "$0"
will also return the empty string, except indash
.$0
then doesn't contain that information (except inzsh
, which doesn't usually act assh
) - there's no good solution to this problem.$0
directly:[ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
$0
then simply contains the sourcing script's path.command -v -- "$0"
in sourced scenarios and the fact that it breaks two non-sourced scenarios, my vote is for NOT using it, which leaves us with:$dir
ends up either containing.
, if the shell executable was invoked as a mere filename (applyingdirname
to a mere filename always returns.
), or the shell executable's directory path otherwise..
cannot be reliably distinguished from a non-sourced invocation from the current directory.$0
contains that script's path, and the script being sourced has no way of telling whether that's the case.Background information:
POSIX defines the behavior of
$0
with respect to shell scripts here.Essentially,
$0
should reflect the path of the script file as specified, which implies:$0
containing an absolute path.$0
contains an absolute path only if:~/bin/myScript
(assuming the script itself is executable)sh ~/bin/myScript
$PATH
; behind the scenes, the system transformsmyScript
into an absolute path and then executes it; e.g.:myScript # executes /home/jdoe/bin/myScript, for instance
In all other cases,
$0
will reflect the script path as specified:sh
with a script, this can be a mere filename (e.g.,sh myScript
) or a relative path (e.g.,sh ./myScript
)./myScript
- note that a mere filename would only find scripts in the$PATH
).In practice,
bash
,dash
,ksh
, andzsh
all exhibit this behavior.By contrast, POSIX does NOT mandate the value of
$0
when sourcing a script (using the special built-in utility.
("dot")), so you cannot rely on it, and, in practice, behavior differs across shells.$0
when your script is being sourced and expect standardized behavior.bash
,dash
, andksh
leave$0
untouched when sourcing scripts, meaning that$0
contains the caller's$0
value, or, more accurately, the$0
value of the most recent caller in the call chain that hasn't been sourced itself; thus,$0
may point either to the shell's executable or to the path of another (directly invoked) script that sourced the current one.zsh
, as the lone dissenter, actually does report the current script's path in$0
. Conversely,$0
will provide no indication as to whether the script is being sourced or not.$0
to the current script's path is.bash
,ksh
, andzsh
all offer their own ways of obtaining the running script's path, even when it's being sourced.For the sake of completeness: the value of
$0
in other contexts:$0
remain unchanged; therefore, whatever value it has outside the function, it'll have inside as well.bash
,dash
, andksh
do behave that way.zsh
is the lone dissenter and reports the function's name.-c
option on startup, it's the first operand (non-option argument) that sets$0
; e.g.:sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
bash
,dash
,ksh
, andzsh
all behave that way.$0
is the value of the first argument that the shell's parent process passed - typically, that's the shell's name or path (e.g.sh
, or/bin/sh
); this includes:-
to the shell name before placing it in$0
, so as to signal to the shell that it is a _login shell; thus, by default,$0
reports-bash
, notbash
, in interactive shells on OSX.sh < myScript
)bash
,dash
,ksh
, andzsh
all behave that way.there. that should enable you to change directpry through some magic links as file descriptors.
setting
$OLDPWD
pre-cd
exports the value for the duration of the one change directory (note:cd
can have residual effects onhash
tables, but the onlysh
about which i am aware that actually males any good use of these is kevin ahlmquists - and since herbert xu -dash
, and maybe somebsd
stuff, but what do i know?) but does not carry over anycd
exports as a result of the change.thus,
$OLDPWD
does not change, actually, and if it had any value at all this remains as was.$PWD
is changed as a result of the firstcd
, and the value becomes/dev/fd/0
which points to/proc/self/fd
, where a list of file descriptors for our process should be in.
, to include whatever$0
is on./2
.so we do an
ls ... ?
and have a look at all of the wonderful info we can get, and we go whence we came.yay!