Reputation: 1953
readlink -f
does not exist on MacOS. The only working solution for Mac OS I managed to find on the net goes like this:
if [[ $(echo $0 | awk '/^\//') == $0 ]]; then
ABSPATH=$(dirname $0)
else
ABSPATH=$PWD/$(dirname $0)
fi
Can anyone suggest anything more elegant to this seemingly trivial task?
Upvotes: 35
Views: 45625
Reputation: 125898
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).
Upvotes: 77
Reputation: 771
I use the function below to emulate "readlink -f" for scripts that have to run on both linux and Mac OS X.
#!/bin/bash
# This was re-worked on 2018-10-26 after der@build correctly
# observed that the previous version did not work.
# Works on both linux and Mac OS X.
# The "pwd -P" re-interprets all symlinks.
function read-link() {
local path=$1
if [ -d $path ] ; then
local abspath=$(cd $path; pwd -P)
else
local prefix=$(cd $(dirname -- $path) ; pwd -P)
local suffix=$(basename $path)
local abspath="$prefix/$suffix"
fi
if [ -e $abspath ] ; then
echo $abspath
else
echo 'error: does not exist'
fi
}
# Example usage.
while (( $# )) ; do
printf '%-24s - ' "$1"
read-link $1
shift
done
This is the output for some common Mac OS X targets:
$ ./example.sh /usr/bin/which /bin/which /etc/racoon ~/Downloads
/usr/bin/which - /usr/bin/which
/bin/which - error: does not exist
/etc/racoon - /private/etc/racoon
/Users/jlinoff/Downloads - /Users/jlinoff/Downloads
The is the output for some linux targets.
$ ./example.sh /usr/bin/which /bin/whichx /etc/init.d ~/Downloads
/usr/bin/which - /usr/bin/which
/bin/whichx - error: does not exist
/etc/init.d - /etc/init.d
/home/jlinoff/Downloads - /home/jlinoff/Downloads
Upvotes: 0
Reputation: 9474
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.
Upvotes: 4
Reputation: 6001
Also note that homebrew
's (http://brew.sh) coreutils
package includes realpath
(link created in/opt/local/bin
).
$ realpath bin
/Users/nhed/bin
Upvotes: 3
Reputation: 4094
I've found this to be useful for symlinks / dynamic links - works with GNU readlink only though (because of the -f flag):
# detect if GNU readlink is available on OS X
if [ "$(uname)" = "Darwin" ]; then
which greadlink > /dev/null || {
printf 'GNU readlink not found\n'
exit 1
}
alias readlink="greadlink"
fi
# create a $dirname variable that contains the file dir
dirname=$(dirname "$(readlink -f "$0")")
# use $dirname to find a relative file
cat "$dirname"/foo/bar.txt
Upvotes: 1
Reputation: 1
If you're using ksh, the ${.sh.file}
parameter is set to the absolute pathname of the script. To get the parent directory of the script: ${.sh.file%/*}
Upvotes: 0
Reputation: 1122
Dug out some old scripts from my .bashrc, and updated the syntax a bit, added a test suite.
.
dot operator)foo->dir1/dir2/bar bar->./../doe doe->script
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)
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`]"
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.
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
Upvotes: 10
Reputation: 11436
This works as long as it's not a symlink, and is perhaps marginally less ugly:
ABSPATH=$(dirname $(pwd -P $0)/${0#\.\/})
Upvotes: 0
Reputation: 6001
this is what I use, may need a tweak here or there
abspath ()
{
case "${1}" in
[./]*)
local ABSPATH="$(cd ${1%/*}; pwd)/${1##*/}"
echo "${ABSPATH/\/\///}"
;;
*)
echo "${PWD}/${1}"
;;
esac
}
This is for any file - and of curse you can just invoke it as abspath ${0}
The first case deals with relative paths by cd-ing to the path and letting pwd figure it out
The second case is for dealing with a local file (where the ${1##/} would not have worked)
This does NOT attempt to undo symlinks!
Upvotes: 0
Reputation: 2652
Can you try something like this inside your script?
echo $(pwd)/"$0"
In my machine it shows:
/home/barun/codes/ns2/link_down/./test.sh
which is the absolute path name of the shell script.
Upvotes: 1
Reputation: 212356
If you don't mind using perl:
ABSPATH=$(perl -MCwd=realpath -e "print realpath '$0'")
Upvotes: 1