NB: In the original title of this post, I used the word standard in the everyday sense of "well-established" (and therefore time-tested, as a contrast to quick solutions I could roll myself). In the context of Unix-talk, however, the word standard has a very specific (and very different) technical meaning. This alternative, more correct, interpretation of the word standard in the title rendered the rest of my post inconsistent. (Thanks to Stéphane Chazelas for pointing this out.) Therefore, I've revised the title, replacing standard with tested.
As an example of the problem I refer to in the title, suppose I have the directory structure shown below
/tmp/example
├── a/
│ └── b/
│ └── c/
│ └── d/
│ └── target
└── A/
└── B/
├── C/
│ └── D/
│ └── symlink-0 -> ../../symlink-1/b/c/d/target
│
└── symlink-1 -> /tmp/example/a
Note that /tmp/example/A/B/C/D/symlink-0
is a symbolic link whose immediate target is a relative path:
$ readlink /tmp/example/A/B/C/D/symlink-0
../../symlink-1/b/c/d/target
I want to get the absolute path corresponding to this immediate target. IOW, I want to perform the partial resolution
/tmp/example/A/B/C/D/symlink-0 -> /tmp/example/A/B/symlink-1/b/c/d/target
Is there a standard (or at least well-established and time-tested) Unix utility to do this?
Note that readlink -f
resolves paths fully (to a symlink-free path); for the case discussed here, for example:
$ readlink -f /tmp/example/A/B/C/D/symlink-0
/tmp/example/a/b/c/d/target
This means that readlink -f
is not the answer to the question I'm asking here.
I could roll my own, by adding adequate error-checking, etc., to something like the following zsh
function:
canonicalize () {
local abspath=$(dirname $1)/$(readlink $1)
printf -- '%s\n' $abspath:a
}
…but I've learned (the hard way) not to underestimate the difficulty of implementing this sort of utility robustly, so I'd prefer to use existing tools, if possible.
FWIW, the script below generates this post's example:
mkdir -p /tmp/example/a/b/c/d /tmp/example/A/B/C/D
touch /tmp/example/a/b/c/d/target
ln -s /tmp/example/a /tmp/example/A/B/symlink-1
ln -s ../../symlink-1/b/c/d/target /tmp/example/A/B/C/D/symlink-0
Best Answer
I'm not aware of any such utility. However, your approach is flawed (and I agree it's very hard to get it right).
The
:a
modifier computes an absolute path without any access to the file system. In particular, it changesa/b/..
toa
regardless of whethera/b
is a symlink or not.If you have a
/a/b -> ../foo
symlink, that is/foo
only if/a
is not a symlink itself. Even if you canonicalize the$(dirname $1)
, that would still not work for symlinks like/a/b -> x/../../foo
ifx
itself is a symlink.In your case:
resolves to
/tmp/example/A/B/symlink-1/b/c/d/target
only because neither/tmp/example/A/B/C/D
nor/tmp/example/A/B/C
are symlinks.In other words, to get an absolute path to the target of a symlink that is free of
..
components, you may have to resolve more than one symlink and you cannot do it alone by combining the path of the file and the target of the symlink.If you want such a path, the easiest (and I'd say only reasonable and/or useful) approach is to get the canonical path of that target of the symlink, that is where all the path components are neither
..
,.
nor symlinks except possibly for the last. For that, you could do:On your
/tmp/example/A/B/C/D/symlink-0
symlinks, that still gives/tmp/example/a/b/c/d/target
, as the target of the symlink is not a symlink, but if that's not what you want, then I'd ask: what result would you want when doing:Where
/tmp/x/y
is a symlink to../foo
and/tmp/x
a symlink to/a/b/c
and/a
,/a/b
,/a/b/c
are also themselves symlinks?