Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

What is a portable way for a (zsh) script to determine its absolute path?

On Linux I use something like

mypath=$(readlink -f $0)

...but this is not portable. (E.g., readlink on darwin does not recognize the -f flag, nor has any equivalent.) (Also, using readlink for this is, admittedly, a pretty obscure-looking hack.)

What's a more portable way?

share|improve this question
add comment

5 Answers 5

up vote 7 down vote accepted

With zsh, it's just:

mypath=$0:A

Now for other shells, though realpath() and readlink() are standard functions (the latter being a system call), realpath and readlink are not standard command, though some systems have one or the other or both with various behaviour and feature set.

As often, for portability, you may want to resort to perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

That would behave more like GNU's readlink -f than realpath() (GNU readlink -e) in that it will not complain if the file doesn't exist as long as its dirname does.

share|improve this answer
add comment

Ive been using this for several years now.

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
share|improve this answer
    
i like it! so far, that's pretty portable. works on Solaris, OmniOS, Linux, Mac, and even Cygwin on Windows 2008. –  Tim Kennedy May 21 '13 at 17:18
    
Where it's not equivalent to GNU's readlink -f is when the script itself is a symlink. –  Stéphane Chazelas Jun 11 at 10:26
add comment

Assuming you really meant the absolute path, i.e. a path from the root directory:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

This works in any Bourne-style shell, by the way.

If you meant a path with all symbolic links resolved, that's a different matter. readlink -f works on Linux (excluding some stripped-down BusyBox systems), FreeBSD, NetBSD, OpenBSD and Cygwin, but not on OS/X, AIX, HP/UX or Solaris. If you have readlink, you can call it in a loop:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

If you don't have readlink, you can approximate it with ls -n, but this only works if ls doesn't mangle any non-printable character in the file name.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(The extra z is in case the link target ends in a newline, which command substitution would otherwise eat up. The realpath function doesn't handle that case for directory names, by the way.)

share|improve this answer
1  
Do you know of any ls implementation that mangles non-printable characters when the output doesn't go to a terminal. –  Stéphane Chazelas Jun 11 at 10:09
1  
@StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane (that's if the name is in UTF-8, latin1 gives you a single ?). I think I've seen that on older commercial Unices too. –  Gilles Jun 11 at 18:11
    
@StéphaneChazelas I've fixed several bugs but not tested extensively. Let me know if it still fails in some cases (other than lack of execution permissions in some directories, I'm not going to deal with that edge case). –  Gilles Jul 4 at 15:45
add comment

In zsh you can do the following:

mypath=${0:a}

Or, to get the directory in which the script resides:

mydir=${0:a:h}

Source: zshexpn(1) man page, section HISTORY EXPANSION, subsection Modifiers (or simply info -f zsh -n Modifiers).

share|improve this answer
    
Sweet! I've been looking for somethink like this for ages and read the whole bunch of the zsh manpages searching for it, but it would never have occurred to me to look under 'History expansion'. –  Vucar Timnärakrul May 20 at 20:04
    
The equivalent of GNU's readlink -f would rather be $0:A. –  Stéphane Chazelas Jun 11 at 10:23
add comment

This should be quite portable:

mypath=$(exec 2>/dev/null;cd $(dirname $0);unset PWD;/usr/bin/pwd||/bin/pwd||pwd)
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.