11

In a script I get in $0 the possible relative path to it. For converting it to absolute I've found this solution which I don't understand:

abspath=$(cd ${0%/*} && echo $PWD/${0##*/})

My problem is the magic inside of ${0%/*} and ${0##*/}. It looks like the former extracts the dirname and the latter extracts the filename, I just don't get how.

3
  • 2
    That uses parameter expansion, but it didn't work for me. If your script is only going to be used on Linux, you can use readlink -f $0 to get the canonical path. Mar 18, 2011 at 13:22
  • 1
    @Shawn: 1 great comment vote because you introduced the right thinking: "That uses parameter expansion, but it didn't work for me". The dirname util is useful here.
    – D4RIO
    Mar 18, 2011 at 14:09
  • mywiki.wooledge.org/BashFAQ/028 and cyberciti.biz/faq/… says BASH_SOURCE is better than $0, as $0 gives the user's typed in command, which might not be the currently executing script.
    – Joel Purra
    Jun 26, 2014 at 11:06

5 Answers 5

11

Shawn had the simplest solution: readlink -f $0. If you want to be absolutely sure to handle weird file names, you can use this:

absolute_path_x="$(readlink -fn -- "$0"; echo x)"
absolute_path="${absolute_path_x%x}"

Documentation

1
  • 1
    Good to see final newlines correctly handled. Unfortunately readlink -fn is specific to Linux, NetBSD and OpenBSD. Mar 18, 2011 at 20:41
8

Definitions:

${string%substring} deletes shortest match of $substring from the end of $string.

${string##substring} deletes longest match of $substring from the start of $string.

Your example:

abspath=$(cd ${0%/*} && echo $PWD/${0##*/})

${0%/*} deletes everything after the last slash, giving you the directory name of the script (which might be a relative path).

${0##*/} deletes everything upto the last slash, giving you just the name of the script.

So, this command changes to the directory of the script and concatenates the current working directory (given by $PWD) and the name of the script giving you the absolute path.

To see what is going on try:

echo ${0%/*}
echo ${0##*/}
1
  • Add double quotes around all variable expansions to cope with (almost all) file names containing shell special characters. Mar 18, 2011 at 20:42
4

Here is a safer and more readable way to do this job:

abspath=$(unset CDPATH && cd "$(dirname "$0")" && echo $PWD/$(basename "$0"))

Notes:

  • If $0 is a bare filename with no preceding path, the original script will fail but the one given here will work. (Not a problem with $0 but could be in other applications.)
  • Either approach will fail if the path to the file doesn't actually exist. (Not a problem with $0, but could be in other applications.)
  • The unset is essential if your user may have CDPATH set.
  • Unlike readlink -f or realpath, this will work on non-Linux versions of Unix (e.g., Mac OS X).
2

If you want to learn Shell Parameter Expansion, you can read it from here, but Expansion isn't always a good choice. In this case, almost every Unix like system has 2 good utils:

basename
dirname

The first will extract the filename, while the second will extract the path, so, if you have $0, say:

dirname $0

And you'ill get the path.

Cheers

1
  • dirname can return relative paths
    – opticyclic
    Apr 18, 2013 at 21:02
0

Introducing pwd, the bash builtin. Also found in the GNU coreutils package.

$ sh ./cygdrive/c/cygwin/home/../../../../home/jaroslav/tmp/somewhere.sh
$0: ./cygdrive/c/cygwin/home/../../../../home/jaroslav/tmp/somewhere.sh
cheeky binary from /home/jaroslav/tmp (/home/jaroslav/tmp)

$ cat /home/jaroslav/tmp/somewhere.sh

abs=$( cd `dirname "$0"` ; pwd -P)
absBin=$( cd `dirname "$0"` ; /bin/pwd -P)
echo \$0: $0
echo cheeky binary from $abs \($absBin\)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .