Create a gist now

Instantly share code, notes, and snippets.

@tvlooy /unit.sh
Last active Apr 11, 2017

What would you like to do?
Bash test: get the directory of a script
#!/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
@tvlooy
Owner
tvlooy commented Jun 9, 2015
[tvl@yoga /tmp]
$ bash unit.sh 

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
  Expected: 34
✘ Tested with space in file
  Received: /tmp/1234
  Expected: .

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
  Expected: 34
✔︎ Tested with space in file

Test 3: overcomplicated stackoverflow solution
✔︎ 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

Test 4: via readlink
✔︎ Tested absolute call
✔︎ Tested via symlinked dir
✔︎ Tested via symlinked file
✔︎ Tested via multiple symlinked dirs
✔︎ Tested relative call
✘ Tested with space in dir
  Received: /tmp
  Expected: /tmp/1234
✘ Tested with space in file
  Received: /tmp/1234
  Expected: /home/tvl/Desktop

Test 5: via readlink with space
✔︎ 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
@udalov
udalov commented Jun 14, 2015

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

@tvlooy
Owner
tvlooy commented Jul 14, 2015

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
@gouessej

@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.

@tvlooy
Owner
tvlooy commented Oct 29, 2015

maybe because of tests 6 and 7 that I added, I updated the script

@vanburg
vanburg commented Dec 1, 2015

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
}
@JoshuaGross

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment