For many years I have been using a snippet that worked well for all purposes I tried:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd $a; pwd)

But today I realized it does not play well with /, i.e. if the script is placed in /script.sh, the BINDIR will contain current directory (whatever it is) instead of /.

TL;DR; the answer is

a="/$0"; a=${a%/*}; a=${a:-.}; a=${a#/}/; BINDIR=$(cd $a; pwd)

Tested with busybox sh (1.26.2), loksh (OpenBSD 6.1 ksh), mksh (R55), as well as /bin/sh from NetBSD 7.0.1_PATCH.

If you want to learn more read on.

In order to play with it properly, I wrote a test script:

#!/bin/sh

a="/$0"; a=${a%/*}; a=${a:-.}; a=${a#/}/; BINDIR=$(cd $a; pwd)

test "$1" = "-d" && DEBUG=1

debug() {
  test -n "$DEBUG" && echo $* 1>&2
}

oldf() {
  a="/$1"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd $a; pwd)
  echo $BINDIR
}

gdir() {
  a="/$1";
  debug $a
  a=${a%/*};
  debug $a
  a=${a:-.};
  debug $a
  a=${a#/};
  debug $a
  a=${a:-/}
  echo $a
}

# grdir = Get Real Directory
# works only on existing directories because it uses 'cd' in the last step
grdir() {
  #a="/$1"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd $a; pwd)
  a=$(gdir $1)
  echo $(cd $a; pwd)
}

BINDIR=$(grdir $0)

expectit() {
  func=${3:-grdir}
  A=$($func $1)
  if
    test "$A" = "$2"
  then
    echo "OK: $func $1 == $2"
  else
    echo "FALED: $func $1 != $A"
  fi
}

expectit /a.sh /
expectit /usr/bin/a.sh /usr/bin
expectit ./a.sh $PWD
expectit a.sh $PWD

expectit /a.sh / gdir
expectit /made/up/a.sh /made/up gdir
expectit ./a.sh . gdir
expectit a.sh . gdir

# Here you can see what went wrong with the old one
expectit /a.sh / oldf
expectit /usr/bin/a.sh /usr/bin oldf
expectit ./a.sh $PWD oldf
expectit a.sh $PWD oldf

Let it be in public domain.

Ján Sáreník

Helping machines do their job while enabling people focus on things machines can't do.

jsarenik nisanitto


Published