1

Suppose I create a symbolic link "file2" to a file "file1" two directories above the current location (e.g., "ln -s ../../file1 file2". But now suppose that "file1" is also a symbolic link to a file "file0" two directories down from its location (say its relative path is dir1/dir2/file0").

I'd prefer if the "file1" symbolic link contains a relative URL going to file0 "../../dir1/dir2/file0" rather than just "../../file1". That latter only indirectly points to the file.

What's a good way of doing this?

I could hack together something with readlink. But I'm still hoping there's an "better" way that I'm not considering or have overlooked.

SO Stinks
  • 3,258
  • 4
  • 32
  • 37
  • This question is related to many using the search terms "absolute", "relative", and "path" such as [BASH: Convert absolute path into relative path given a current directory](http://stackoverflow.com/questions/2564634/bash-convert-absolute-path-into-relative-path-given-a-current-directory). Unfortunately, none of the answers yet feels acceptable to me. – SO Stinks Jul 23 '11 at 13:16
  • Your comment made me remember... see the other answer. – Tomas Jul 23 '11 at 13:45

3 Answers3

1

This will recursively test a file and retrieve a complete relative path, then remove unnecessary "./" and "foo/../"

#!/bin/bash
linkfile="$1"
while test -L "$linkfile" ; do
linkfile="$(dirname ${linkfile})/$(readlink ${linkfile})"
done
perl -e '$x=shift; while ($x =~ s#//+#/#g) {} ; while ($x =~ s#/\./#/#g) {} ; while ($x =~ s#/([^\.]|[^\.][^/]+?|\.[^\.]+?)/\.\./#/#g) {} ; while ($x =~ s#^([^\.]|[^\.][^/]+?|\.[^\.]+?)/\.\./#/#g) {} ; $x =~ s#^\./##; print "$x\n";' "$linkfile"

If you save that as "canonical.bash", then you use it

$ ln -s `canonical.bash foo` bar

The regex won't reduce a few cases like "/..x/../" and "../foo/" (if foo/ is in your $PWD), but should be otherwise sturdy.

brightlancer
  • 2,059
  • 15
  • 7
  • This script seems to answer my question as asked. It does not however delivery the shortest relative path in all cases. It fails for this example: let there be "/home/user/dir1" which contains a directory "dir2" which contains "file.txt". Let "link1" also exist there and point to "dir2/file.txt" and "link2" also exist and point to "../dir1/../dir1/link1". The script returns "../dir1/dir2/file.txt" instead of "dir2/file.txt" as expected. – SO Stinks Aug 03 '11 at 21:08
  • I am unsatisfied by having to resort to perl. A solution without it would be more elegant. – SO Stinks Aug 03 '11 at 21:14
  • As you saw in the other question you linked to, transforming a relative path into a canonical one is challenging. I threw a quick regex to handle some of it and there are Perl modules that can handle it better, but I don't know of a standard shell command to accomplish it. – brightlancer Aug 07 '11 at 03:25
0

No, this is so specific that I think you'll have to use readlink and implement it yourself.

Tomas
  • 57,621
  • 49
  • 238
  • 373
0

Now I remember!! I saw a utility which accomplishes this some 15 years ago! See man symlinks(1)! symlinks -c. If this is not what you want, I suppose your needs are so specific you would have to code it yourself... (if I remember well, I also made some utility for handling symlinks on my disk...). PERL is a great language for that.

Tomas
  • 57,621
  • 49
  • 238
  • 373
  • This is an interesting tool (was in repos for Ubuntu 10.04) but it doesn't do what I am trying to accomplish. I've found a couple bash scripts here at StackOverflow that half solve the problem but nothing that's completely satisfactory. I'm consider investing the time to write my own now. Was hoping not to have to do that. I'll post if I write something that works that I'm proud of. – SO Stinks Jul 23 '11 at 14:01