With the symlinks
utility by Mark Lord (offered by many distributions; if yours doesn't have it, build it from source):
chroot /home/user/system symlinks -cr .
Alternatively, on systems that have a readlink
command and a -lname
predicate to find
(warning: untested code):
cd /home/user/system &&
find . -lname '/*' -exec ksh -c '
for link; do
target=$(readlink "$link")
link=${link#./}
root=${link//+([!\/])/..}; root=${root#/}; root=${root%..}
rm "$link"
ln -s "$root${target#/}" "$link"
done
' _ {} +
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 changes a/b/..
to a
regardless of whether a/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
if x
itself is a symlink.
In your case:
/tmp/example/A/B/C/D/symlink-0 -> ../../symlink-1/b/c/d/target
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:
zmodload -F zsh/stat b:zstat
canonicalize1() {
if [ -L "$1" ]; then
local link
zstat -A link +link -- "$1" || return
case $link in
(/*) ;;
(*)
case $1:h in
(*/) link=$1:h$link;;
(*) link=$1:h/$link;;
esac;;
esac
printf '%s\n' $link:h:A/$link:t
else
printf '%s\n' $1:A
fi
}
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:
canonicalize1 /tmp/x/y
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?
Best Answer
For the one that doesn't work, if we look at the
ls -l
result, we get the following:Now to understand what is going on here. Let's look at the command you called:
According to the Man Page, there are two possible matches for this format
It will match on the first form (since its first). Now, the "target name" or
client
in your case, can be (according to the completeln
manual) arbitrary strings. They don't have to resolve to anything right now, but can resolve to something in the future. What you are creating with your invocation is a "dangling symlink" and the system does not keep you from creating these.Now your second invocation
ln -s ../client build/client
is what is called a "relative symlink" (as you noted in your own post). There is a second type and that is an "absolute symlink" which would be called by doingln -s /home/user/client build/client
.This is not a bug. According to the manual it states:
That said, you MUST use either the relative or absolute path to the target.