-
-
Save tvlooy/cbfbdb111a4ebad8b93e to your computer and use it in GitHub Desktop.
#!/bin/bash | |
function test { | |
MESSAGE=$1 | |
RECEIVED=$2 | |
EXPECTED=$3 | |
if [ "$RECEIVED" = "$EXPECTED" ]; then | |
echo -e "\033[32m✔︎ Tested $MESSAGE" | |
else | |
echo -e "\033[31m✘ Tested $MESSAGE" | |
echo -e " Received: $RECEIVED" | |
echo -e " Expected: $EXPECTED" | |
fi | |
echo -en "\033[0m" | |
} | |
function testSuite { | |
test 'absolute call' `bash /tmp/1234/test.sh` /tmp/1234 | |
test 'via symlinked dir' `bash /tmp/current/test.sh` /tmp/1234 | |
test 'via symlinked file' `bash /tmp/test.sh` /tmp/1234 | |
test 'via multiple symlinked dirs' `bash /tmp/current/loop/test.sh` /tmp/1234 | |
pushd /tmp >/dev/null | |
test 'relative call' `bash 1234/test.sh` /tmp/1234 | |
popd >/dev/null | |
test 'with space in dir' `bash /tmp/12\ 34/test.sh` /tmp/1234 | |
test 'with space in file' `bash /tmp/1234/te\ st.sh` /tmp/1234 | |
echo | |
} | |
function setup { | |
DIR=/tmp/1234 | |
FILE=test.sh | |
if [ -e $DIR ]; then rm -rf $DIR; fi; mkdir $DIR | |
if [ -f $DIR/$FILE ]; then rm -rf $DIR/$FILE; fi; touch $DIR/$FILE | |
if [ -f /tmp/$FILE ]; then rm /tmp/$FILE; fi; ln -s $DIR/$FILE /tmp | |
if [ -f /tmp/current ]; then rm /tmp/current; fi; ln -s $DIR /tmp/current | |
if [ -f /tmp/current/loop ]; then rm /tmp/current/loop; fi; ln -s $DIR /tmp/current/loop | |
DIR2="/tmp/12 34" | |
FILE2="te st.sh" | |
if [ -e "$DIR2" ]; then rm -rf "$DIR2"; fi; mkdir "$DIR2" | |
if [ -f "$DIR/$FILE2" ]; then rm -rf "$DIR/$FILE2"; fi; ln -s $DIR/$FILE "$DIR/$FILE2" | |
if [ -f "$DIR2/$FILE" ]; then rm -rf "$DIR2/$FILE"; fi; ln -s $DIR/$FILE "$DIR2/$FILE" | |
if [ -f "$DIR2/$FILE2" ]; then rm -rf "$DIR2/$FILE2"; fi; ln -s $DIR/$FILE "$DIR2/$FILE2" | |
} | |
function test1 { | |
echo 'Test 1: via dirname' | |
cat <<- EOF >/tmp/1234/test.sh | |
echo \`dirname \$0\` | |
EOF | |
testSuite | |
} | |
function test2 { | |
echo 'Test 2: via pwd' | |
cat <<- EOF >/tmp/1234/test.sh | |
CACHE_DIR=\$( cd "\$( dirname "\${BASH_SOURCE[0]}" )" && pwd ) | |
echo \$CACHE_DIR | |
EOF | |
testSuite | |
} | |
function test3 { | |
echo 'Test 3: overcomplicated stackoverflow solution' | |
cat <<- EOF >/tmp/1234/test.sh | |
SOURCE="\${BASH_SOURCE[0]}" | |
while [ -h "\$SOURCE" ]; do | |
DIR="\$( cd -P "\$( dirname "\$SOURCE" )" && pwd )" | |
SOURCE="\$(readlink "\$SOURCE")" | |
[[ \$SOURCE != /* ]] && SOURCE="\$DIR/\$SOURCE" | |
done | |
DIR="\$( cd -P "\$( dirname "\$SOURCE" )" && pwd )" | |
echo \$DIR | |
EOF | |
testSuite | |
} | |
function test4 { | |
echo 'Test 4: via readlink' | |
cat <<- EOF >/tmp/1234/test.sh | |
echo \`dirname \$(readlink -f \$0)\` | |
EOF | |
testSuite | |
} | |
function test5 { | |
echo 'Test 5: via readlink with space' | |
cat <<- EOF >/tmp/1234/test.sh | |
echo \`dirname \$(readlink -f "\$0")\` | |
EOF | |
testSuite | |
} | |
echo | |
setup | |
if [ "$1" != "" ]; then | |
$1 | |
else | |
test1 | |
test2 | |
test3 | |
test4 | |
test5 | |
fi |
Thanks for the feedback. I didn't check on a Mac.
You can get the readline behaviour of GNU on Mac like this:
brew install coreutils
alias readlink=greadlink
@tvlooy I don't want to force the end user to install coreutils on her/his Mac. I agree with udalov. However, the test 3 doesn't work under Ubuntu when called in a desktop file when clicking on the icon of the application.
maybe because of tests 6 and 7 that I added, I updated the script
For OS X: TEST 3 as a ready-to-use function.
# TEST 3
# http://stackoverflow.com/a/246128
# https://gist.github.com/tvlooy/cbfbdb111a4ebad8b93e
function abs_script_dir_path {
SOURCE=${BASH_SOURCE[0]}
while [ -h "$SOURCE" ]; do
DIR=$( cd -P $( dirname "$SOURCE") && pwd )
SOURCE=$(readlink "$SOURCE")
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
DIR=$( cd -P $( dirname "$SOURCE" ) && pwd )
echo $DIR
}
So that you can correctly get the directory when running source dir/to/script.sh
in Bash and ZSH:
function abs_script_dir_path {
SOURCE=$(if [ -z "${BASH_SOURCE[0]}"]; then echo $1; else echo ${BASH_SOURCE[0]}; fi)
while [ -h "$SOURCE" ]; do
DIR=$( cd -P $( dirname "$SOURCE") && pwd )
SOURCE=$(readlink "$SOURCE")
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
DIR=$( cd -P $( dirname "$SOURCE" ) && pwd )
echo $DIR
}
DIR=$(abs_script_dir_path $0)
@tvlooy, I don't have readlink
in my system (AIX 7.1
) but the following seems to pass all test scenarios:
#!/usr/bin/bash
__SOURCE__="\${BASH_SOURCE[0]}"
while [[ -h "\${__SOURCE__}" ]]; do
__SOURCE__=\$(find "\${__SOURCE__}" -type l -ls | sed -n 's/^.* -> \(.*\)/\1/p');
done;
echo \$( cd -P "\$( dirname "\${__SOURCE__}" )" && pwd )
I'm using the fact find -ls
is returning the string:
lrwxrwxrwx 1 <user> <group> <timestamp> <symlink> -> <symlinked source>
so sed
should be able to handle this (I even tried using filenames with " -> "
).
I should note that my test results are different:
Test 1: via dirname
✔︎ Tested absolute call
✘ Tested via symlinked dir
Received: /tmp/current
Expected: /tmp/1234
✘ Tested via symlinked file
Received: /tmp
Expected: /tmp/1234
✘ Tested via multiple symlinked dirs
Received: /tmp/current/loop
Expected: /tmp/1234
✘ Tested relative call
Received: 1234
Expected: /tmp/1234
✘ Tested with space in dir
Received: /tmp/12 34
Expected: /tmp/1234
✔︎ Tested with space in file
Test 2: via pwd
✔︎ Tested absolute call
✘ Tested via symlinked dir
Received: /tmp/current
Expected: /tmp/1234
✘ Tested via symlinked file
Received: /tmp
Expected: /tmp/1234
✘ Tested via multiple symlinked dirs
Received: /tmp/current/loop
Expected: /tmp/1234
✔︎ Tested relative call
✘ Tested with space in dir
Received: /tmp/12 34
Expected: /tmp/1234
✔︎ Tested with space in file
Test 3: overcomplicated stackoverflow solution
✔︎ Tested absolute call
✔︎ Tested via symlinked dir
✘ Tested via symlinked file
Received:
Expected: /tmp/1234
✔︎ Tested via multiple symlinked dirs
✔︎ Tested relative call
✘ Tested with space in dir
Received:
Expected: /tmp/1234
✘ Tested with space in file
Received:
Expected: /tmp/1234
Test 4: via readlink
✘ Tested absolute call
Received: .
Expected: /tmp/1234
✘ Tested via symlinked dir
Received: .
Expected: /tmp/1234
✘ Tested via symlinked file
Received: .
Expected: /tmp/1234
✘ Tested via multiple symlinked dirs
Received: .
Expected: /tmp/1234
✘ Tested relative call
Received: .
Expected: /tmp/1234
✘ Tested with space in dir
Received: .
Expected: /tmp/1234
✘ Tested with space in file
Received: .
Expected: /tmp/1234
Test 5: via readlink with space
✘ Tested absolute call
Received: .
Expected: /tmp/1234
✘ Tested via symlinked dir
Received: .
Expected: /tmp/1234
✘ Tested via symlinked file
Received: .
Expected: /tmp/1234
✘ Tested via multiple symlinked dirs
Received: .
Expected: /tmp/1234
✘ Tested relative call
Received: .
Expected: /tmp/1234
✘ Tested with space in dir
Received: .
Expected: /tmp/1234
✘ Tested with space in file
Received: .
Expected: /tmp/1234
Test 6: as Test 2 but with cd -P
✔︎ Tested absolute call
✔︎ Tested via symlinked dir
✘ Tested via symlinked file
Received: /tmp
Expected: /tmp/1234
✔︎ Tested via multiple symlinked dirs
✔︎ Tested relative call
✘ Tested with space in dir
Received: /tmp/12 34
Expected: /tmp/1234
✔︎ Tested with space in file
Test 7: via cd -P and pwd, testing for symlinked file first
✔︎ Tested absolute call
✔︎ Tested via symlinked dir
✔︎ Tested via symlinked file
✔︎ Tested via multiple symlinked dirs
✔︎ Tested relative call
✔︎ Tested with space in dir
✔︎ Tested with space in file
My code is test 7
I add some guards on tests 3, 4 and 5 so I wouldn't get any errors.
If you want, you can merge my fork from https://gist.github.com/gvlx/0adfb1137937ad443df2eeaa73dc0ba7/44efc8c3b18e9dc3495afa7358be3ea0aa53365f .
No portable POSIX version here. This should do:
function test6 {
echo 'Test 6: posix compliant'
cat <<- EOF >/tmp/1234/test.sh
SOURCE="\$0"
script_dir() {
mysource=\${SOURCE}
while [ -h "\${mysource}" ]; do
DIR="\$( cd -P "\$( dirname "\${mysource}" )" > /dev/null && pwd )"
mysource="\$(readlink "\${mysource}")"
[ "\${mysource%\${mysource#?}}"x != '/x' ] && mysource="\${DIR}/\${mysource}"
done
DIR="\$( cd -P "\$( dirname "\${mysource}" )" > /dev/null && pwd )"
echo "\${DIR}"
unset mysource DIR
}
echo \$(script_dir)
EOF
testSuite
}
greadlink -f
unfortunately doesn't work effectively when source
ing the script on Mac :(
New fork https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371 includes:
- @glvx changes
- macOS support
- new python-based implementation (motivation: https://stackoverflow.com/a/68056148/133106)
- improved quoting
Test 4 doesn't work on Mac OS X because
readlink
behaves differently there (no-f
option). That's why the solution from test 3 is preferable