Consider the following folder structure:

├── test1
│   ├── nested1
│   ├── testfile11
│   └── testfile12
└── test2
    ├── nested1 -> /path/to/dir/test1/nested1
    └── testfile21

test2/nested1 is a symlink to the directory test1/nested1. I would expect, if it were the cwd, .. would resolve to test2. However, I have noticed this inconsistency:

$ cd test2/nested1/
$ ls ..
nested1  testfile11  testfile12
$ cd ..
$ ls
nested1  testfile21

touch also behaves like ls, creating a file in test1.

Why does .. as an argument to cd refer to the parent of the symlink, while to (all?) others refers to the parent of the linked dir? Is there some simple way to force it to refer to paths relative to the symlink? I.e. the "opposite" of readlink?

# fictional command
ls $(linkpath ..)

EDIT: Using bash


The commands cd and pwd have two operational modes.

  • -L logical mode: symlinks are not resolved

  • -P physical mode: symlinks are resolved before doing the operation

The important thing to know here is that cd .. does not call the syscall chdir("..") but rather shortens the $PWD variable of the shell and then chdirs to that absolute path.

If you are in physical mode, this is identical to calling chdir(".."), but when in logical mode, this is different.

The main problem here: POSIX decided to use the less safe logical mode as default.

If you call cd -P instead of just cd, then after a chdir() operation, the return value from getcwd() is put into the shell variable $PWD and a following cd .. will get you to the directory that is physically above the current directory.

So why is the POSIX default less secure?

If you crossed a symlink in POSIX default mode and do the following:

ls ../*.c
cd ..
rm *.c

you will probably remove different files than those that have been listed by the ls command before.

If you like the safer physical mode, set up the following aliases:

alias cd='cd -P'
alias pwd='pwd -P'

Since when using more than one option from -L and -P the last option wins, you still may be able to get the other behavior.

Historical background:

The Bourne Shell and ksh88 did get directory tracking code at the same time.

The Bourne Shell did get the safer physical behavior while ksh88 at the same time did get the less safe logical mode as default and the options -L and -P. It may be that ksh88 used the csh behavior as reference.

POSIX took the ksh88 behavior without discussing whether this is a good decision.

BTW: some shells are unable to keep track of $PWD values that are longer than PATH_MAX and drive you crazy when you chdir into a directory with an absolute path longer than PATH_MAX. dash is such a defective shell.

  • Do you know when exactly that was added to Bourne/Korn? I can see it was in ksh88g, not in ksh88d (SVR4). Note csh didn't have anything like that. tcsh has something similar (not enabled by default) added in the 90s (set symlinks = ignore or set symlinks = expand which goes even further and is even more broken) Aug 1 '18 at 9:31
  • In 1988, the Bourne Shell did have code to handle $PWD but not code to verify it's content using lstat(). This code was added on November 1989
    – schily
    Aug 1 '18 at 10:05
  • Just verified: csh does not have similar code. I was confused by the fact that csh tracks the logical view of the location in a variable cwd. This is why my bsh had a variable $CWD since 1986.
    – schily
    Aug 1 '18 at 10:15
  • Regarding ksh88: The code for -L/-P has been in in Summer 1989. Do you have ksh88 source code from different times?
    – schily
    Aug 1 '18 at 10:26
  • I have ksh88d from the SVR4 source on archive.org, and the changelog on Sven Mascheck's site mentions pwd -P (presumably added in earlier versions) for ksh88g Aug 1 '18 at 10:27

An important paper to read is http://doc.cat-v.org/plan_9/4th_edition/papers/lexnames where Rob Pike talks about this issue.

You also need to specify your shell, for instance bash tracks it's idea of the current directory and if you say cd .. it just cuts off the last component and changes there, rather than actually issuing a chdir("..") system call.

Sorry on my phone at the moment, will edit later...


You might want to give -p a try, which will follow the physical directory structure without following symbolic links


Not the answer you're looking for? Browse other questions tagged or ask your own question.