How to get absolute path name of shell script on MacOS?
Get absolute path of shell script
Dug out some old scripts from my .bashrc, and updated the syntax a bit, added a test suite.
Supports
- source ./script (When called by the
.
dot operator) - Absolute path /path/to/script
- Relative path like ./script
- /path/dir1/../dir2/dir3/../script
- When called from symlink
- When symlink is nested eg)
foo->dir1/dir2/bar bar->./../doe doe->script
- When caller changes the scripts name
It has been tested and used in real projects with success, however there may be corner cases I am not aware of.
If you were able to find such a situation, please let me know.
(For one, I know that this does not run on the sh shell)
Code
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]) do
cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd =[`pwd`]"
Known issuse
Script must be on disk somewhere, let it be over a network. If you try to run this script from a PIPE it will not work
wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Technically speaking, it is undefined.
Practically speaking, there is no sane way to detect this.
Test case used
And the current test case that check that it works.
#!/bin/bash
# setup test enviroment
mkdir -p dir1/dir2
mkdir -p dir3/dir4
ln -s ./dir1/dir2/foo bar
ln -s ./../../dir3/dir4/test.sh dir1/dir2/foo
ln -s ./dir1/dir2/foo2 bar2
ln -s ./../../dir3/dir4/doe dir1/dir2/foo2
cp test.sh ./dir1/dir2/
cp test.sh ./dir3/dir4/
cp test.sh ./dir3/dir4/doe
P="`pwd`"
echo "--- 01"
echo "base =[${P}]" && ./test.sh
echo "--- 02"
echo "base =[${P}]" && `pwd`/test.sh
echo "--- 03"
echo "base =[${P}]" && ./dir1/dir2/../../test.sh
echo "--- 04"
echo "base =[${P}/dir3/dir4]" && ./bar
echo "--- 05"
echo "base =[${P}/dir3/dir4]" && ./bar2
echo "--- 06"
echo "base =[${P}/dir3/dir4]" && `pwd`/bar
echo "--- 07"
echo "base =[${P}/dir3/dir4]" && `pwd`/bar2
echo "--- 08"
echo "base =[${P}/dir1/dir2]" && `pwd`/dir3/dir4/../../dir1/dir2/test.sh
echo "--- 09"
echo "base =[${P}/dir1/dir2]" && ./dir1/dir2/test.sh
echo "--- 10"
echo "base =[${P}/dir3/dir4]" && ./dir3/dir4/doe
echo "--- 11"
echo "base =[${P}/dir3/dir4]" && ./dir3/dir4/test.sh
echo "--- 12"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir3/dir4/doe
echo "--- 13"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir3/dir4/test.sh
echo "--- 14"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir1/dir2/../../dir3/dir4/doe
echo "--- 15"
echo "base =[${P}/dir3/dir4]" && `pwd`/dir1/dir2/../../dir3/dir4/test.sh
echo "--- 16"
echo "base s=[${P}]" && source test.sh
echo "--- 17"
echo "base s=[${P}]" && source `pwd`/test.sh
echo "--- 18"
echo "base s=[${P}/dir1/dir2]" && source ./dir1/dir2/test.sh
echo "--- 19"
echo "base s=[${P}/dir3/dir4]" && source ./dir1/dir2/../../dir3/dir4/test.sh
echo "--- 20"
echo "base s=[${P}/dir3/dir4]" && source `pwd`/dir1/dir2/../../dir3/dir4/test.sh
echo "--- 21"
pushd . >/dev/null
cd ..
echo "base x=[${P}/dir3/dir4]"
./`basename "${P}"`/bar
popd >/dev/null
PurpleFox aka GreenFox
Another (also rather ugly) option:
ABSPATH=$(cd "$(dirname "$0")"; pwd -P)
From pwd
man page,
-P Display the physical current working directory (all symbolic links resolved).
Using bash I suggest this approach. You first cd to the directory, then you take the current directory using pwd. After that you must return to the old directory to ensure your script does not create side effects to an other script calling it.
cd "$(dirname -- "$0")"
dir="$PWD"
echo "$dir"
cd - > /dev/null
This solution is safe with complex path. You will never have troubles with spaces or special charaters if you put the quotes.
Note: the /dev/null is require or "cd -" print the path its return to.
Also note that homebrew
's (http://brew.sh) coreutils
package includes realpath
(link created in/opt/local/bin
).
$ realpath bin
/Users/nhed/bin