Resolving the path to a Bash script I wrote a function to resolve the full path to the current Bash script, and I'm wondering if it's SOLID:

#!/usr/bin/env bash

scriptpath()
{
    result=${BASH_SOURCE[0]}

    # unfortunately readlink -f isn't available on Macs,
    # so resolve the symlinks manually
    while [ -h "$result" ]
    do
        symdir=$(dirname "$result")
        cd -P "$symdir"
        result=$(readlink "$result")
    done

    echo "$result"
}

echo "The path to the current script is: $(scriptpath)"


Is there anything I may have looked over in the function? I'm not sure because my answer seems quite different than the solutions listed here or here, and I'm pretty fuzzy on symlinks.

I basically want to know if my function (scriptpath) is any different than this on a Linux system:


readlink -f "${BASH_SOURCE[0]}" | xargs -0 dirname
Let's make this testable

With a small twist, the function can become testable.
Instead of finding the real path of the current script,
it would be better to generalize it to find the real path of any path:

real_path()
{
    result=$1

    # ...
}


Fails to follow relative symlinks correctly

Now that the script is testable, here's a test it will not pass:

mkdir -p /tmp/a
touch /tmp/a/b
(cd /tmp/a; test -L /tmp/a/c || ln -s ../a/b c)

echo $(real_path /tmp/a/c)


The output will be ../a/b instead of the expected /tmp/a/b.
Basically if there are relative links in the chain of links recursively followed, the script will break.

Alternative implementation

This is what I use in production, and I haven't seen a corner case yet where it breaks:

real_path() {
  target=$1

  (
  while true; do
    cd "$(dirname "$target")"
    target=$(basename "$target")
    test -L "$target" || break
    target=$(readlink "$target")
  done

  echo "$(pwd -P)/$target"
  )
}


Notice that the portion where the function changes the working directory is wrapped within a (...) sub-shell.
This is probably overkill,
but I think it's good to eliminate side effects as a general principle.