Why does the "ln" command need an absolute path?

A symbolic link stores the path you give when you create it. Paths do not work (symlinks are broken), when the file is not actually in that path. Let's make a symlink with a relative path...

zanna@toaster:~/playground$ mkdir linkyland anotherplace
zanna@toaster:~/playground$ cd linkyland
zanna@toaster:~/playground/linkyland$ ln -s sauce target
zanna@toaster:~/playground/linkyland$ file *
target: broken symbolic link to sauce

ln doesn't care whether the source file exists (so if you make a typo in the path, it won't complain). Let's create the file we want to link to and see if that helps:

zanna@toaster:~/playground/linkyland$ > sauce
zanna@toaster:~/playground/linkyland$ file target
target: symbolic link to sauce

Now the link works. We can use only the basename (the last element of the path) because sauce is in the same directory as target, so target can store the path sauce and that is enough information to find the sauce when we need it.

zanna@toaster:~/playground/linkyland$ cd ../anotherplace
zanna@toaster:~/playground/anotherplace$ ln -s sauce target
zanna@toaster:~/playground/anotherplace$ file target
target: broken symbolic link to sauce

That symlink doesn't work because there's no sauce here. The path sauce isn't enough information. (From this point onward I've removed the user@host part of my prompt for easier reading, but I'm showing the part that indicates the current working directory as this shows how the commands work.). We can fix that by using an absolute path to make the symlink:

~/playground/anotherplace$ rm target
~/playground/anotherplace$ ls -s /home/zanna/playground/linkyland/sauce target
~/playground/anotherplace$ file target
target: symbolic link to /home/zanna/playground/linkyland/sauce

However, we could also fix it by making a correct relative path:

~/playground/anotherplace$ rm target 
~/playground/anotherplace$ ln -s ../linkyland/sauce target
~/playground/anotherplace$ file target
target: symbolic link to ../linkyland/sauce

So the idea that we need absolute paths is... just wrong. We need a correct path, absolute or relative.

If paths change, symlinks with absolute paths to files in the same directory break, but those with relative paths do not:

~/playground/anotherplace$ cd ../linkyland
~/playground/linkyland$ ln -s /home/zanna/playground/linkyland/sauce target2
~/playground/linkyland$ cd ..
~/playground$ mv linkyland elsewhere
~/playground$ file elsewhere/target*
elsewhere/target: symbolic link to sauce
elsewhere/target2: broken symbolic link to /home/zanna/playground/linkyland/sauce

So it is often preferable to use relative paths. However, if the location of the source file is unlikely to change, but the location of the symlink is likely to change, it would be preferable to use an absolute path:

~/playground$ cd anotherplace 
~/playground/anotherplace$ ln -s ../elsewhere/sauce target-rel
~/playground/anotherplace$ ln -s /home/zanna/playground/elsewhere/sauce target-abs
~/playground/anotherplace$ cd ..
~/playground$ mv anotherplace ..
~/playground$ cd ..
~$ file anotherplace/*
anotherplace/target-abs: symbolic link to /home/zanna/playground/elsewhere/sauce
anotherplace/target-rel: broken symbolic link to ../elsewhere/sauce

Symbolic links don't need absolute paths. It works just fine with relative paths:

$ ls -l /usr/bin/X11
lrwxrwxrwx 1 root root 1 May 11  2017 /usr/bin/X11 -> .

See, here's a symbolic link to a relative path, which works perfectly fine:

$ realpath /usr/bin/X11/yes
/usr/bin/yes
$ file /usr/bin/X11/yes
/usr/bin/X11/yes: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=add2c9ee02a98b5066d08d5ba2e79697880b2662, stripped

I believe I ran into the same issue as the poster and this was the first post I came to, I have since realized what I was doing wrong so will post here in case it helps others (at the time of writing no other answers or comments here mentioned the key piece of info I was after).

From the official docs for ln:

When creating a relative symlink in a different location than the current directory, the resolution of the symlink will be different than the resolution of the same string from the current directory. Therefore, many users prefer to first change directories to the location where the relative symlink will be created, so that tab-completion or other file resolution will find the same target as what will be placed in the symlink.

So the key thing to know is that what you write as your source/file/path is basically just placed into the link file verbatim, as text. When the link file is later interpreted, this text of the path is where your computer tries to navigate to from the directory of the symlink.

I was getting broken links because the directory I was running the command from (and using tab completion from) was different to the directory I was creating the link in. And the tab completion was working to give me a valid relative path but only from my current directory, that same relative path is no good in the context of interpreting it from any other directory (obviously because it's relative) including the other directory where I created the symlink.

My mistake was assuming that the ln command would do some clever reinterpretation of the path for me, I thought that since --relative is an option, that then if that option was not supplied, ln would resolve my relative tab completed path into an absolute path, but it doesn't work like that.

Playing around a bit more I've discovered that the --relative option is not about interpreting the link as a relative path or an absolute path, but actually it seems to grant the behavior I was originally expecting!

So I've now come to the understanding that if you use ln -s without --relative or -r then whatever you write for the path/to/sourcefile will be retained unchanged in the link file, and you can use a relative path here but whatever path you put will be interpreted in the context of from the directory that the symlink resides in.

If however you use ln -sr then you can use tab completion to navigate to your source file from your current directory even if you're not creating the symlink in that directory, and ln will do some clever stuff to translate that tab completed path you wrote in the command, into one that is valid when interpreted from the location of your symlink.

So for example:

alex@pc:/run/shm/training$ echo hello > sourcefile.txt
alex@pc:/run/shm/training$ ls
sourcefile.txt
alex@pc:/run/shm/training$ ln -s sourcefile.txt sameDirNotRelative
alex@pc:/run/shm/training$ ln -sr sourcefile.txt sameDirRelative
alex@pc:/run/shm/training$ cd ..
alex@pc:/run/shm$ ln -s training/sourcefile.txt training/oneUpNotRelative
alex@pc:/run/shm$ ln -sr training/sourcefile.txt training/oneUpRelative
alex@pc:/run/shm$ cd ..
alex@pc:/run$ ln -s shm/training/sourcefile.txt shm/training/twoUpNotRelative
alex@pc:/run$ ln -sr shm/training/sourcefile.txt shm/training/twoUpRelative
alex@pc:/run$ cd shm/training/
alex@pc:/run/shm/training$ ls -l
total 4
lrwxrwxrwx 1 alex alex 23 Jul  6 09:10 oneUpNotRelative -> training/sourcefile.txt
lrwxrwxrwx 1 alex alex 14 Jul  6 09:10 oneUpRelative -> sourcefile.txt
lrwxrwxrwx 1 alex alex 14 Jul  6 09:10 sameDirNotRelative -> sourcefile.txt
lrwxrwxrwx 1 alex alex 14 Jul  6 09:10 sameDirRelative -> sourcefile.txt
-rw-r--r-- 1 alex alex  6 Jul  6 09:08 sourcefile.txt
lrwxrwxrwx 1 alex alex 27 Jul  6 09:11 twoUpNotRelative -> shm/training/sourcefile.txt
lrwxrwxrwx 1 alex alex 14 Jul  6 09:11 twoUpRelative -> sourcefile.txt

Inspecting the resulting links reveals that two are broken, the ones created without the relative flag and from outside their resident directory (/run/shm/training), The non relative one created in the same training directory just happens to work because the path is valid when navigating from the resident directory of the link.

alex@pc:/run/shm/training$ file sameDirNotRelative 
sameDirNotRelative: symbolic link to sourcefile.txt
alex@pc:/run/shm/training$ file oneUpNotRelative 
oneUpNotRelative: broken symbolic link to training/sourcefile.txt
alex@pc:/run/shm/training$ file twoUpNotRelative 
twoUpNotRelative: broken symbolic link to shm/training/sourcefile.txt