Shell Symlinks – Understanding Directory Symlink Traversals and Parent Directory

cd-commanddirectoryshellsymlink

Let's say you have directories /dir1 and /dir2/linked, where the latter is a symlink to the former.

When you cd to linked and pwd, you get the output /dir2/linked. If you then cd .., you'll be put on /dir2. This behaviour is consistent with the concept of you being in /dir2/linked before. However, as I understand it, the parent directory (..) of any directory is stored in the directory inode (i.e.:physically in the disk). Obviously, since /dir2/linked is really /dir1, the parent directory on the inode must be /

To further complicate matters, while inside /dir2/linked, the outputs of ls .. and cd .. ; ls . are different! It seems like cd honors the symlinked path, while ls honors the "physical" path. As mentioned in this question, there's cd -P for this use case, though.

man pwd mentions "physical" and "logical" working directories, but I still have a few questions at this point:

  • Is this behaviour always provided by the PWD environment variable, as mentioned in man pwd?
  • Why do default cd and ls have different behaviours, if they're both shell commands (i.e.: not programs)?
  • Does the typical program (not shell command) use PWD instead of the physical path? I realize it's up to the implementation, but is there any rule of thumb?

Best Answer

bash "knows" about symlinks and tracks this info when you use a symlink to enter a directory.

You can check this by doing the following in your example:

$ cd /dir2
$ cd linked
$ pwd
/dir2/linked
$ PWD='' bash -c pwd
/dir1

You need to start the bash with an empty PWD variable, otherwise it uses that trick to display the "fake" path.

Note that ls is a separate program and as such doesn't have bash's knowledge of how you arrived at the current directory, so ls .. will just show the contents of the real parent directory, not relative to the symlink you followed.

Most programs will not depend on the environment variable CWD as there are many ways of starting programs, via the bash shell is just one so it's not reliable to expect CWD to contain the correct value (try setting CWD to something wrong before doing bash -c pwd, you can see it checks the value for sanity).