7

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

3 Answers3

6

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.

schily
  • 19,173
  • 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) – Stéphane Chazelas Aug 01 '18 at 09: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 01 '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 01 '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 01 '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 – Stéphane Chazelas Aug 01 '18 at 10:27
  • My impression is that these options for cd have been present in ksh88c already. – schily Aug 01 '18 at 10:58
  • sorry, my bad ksh88d from https://archive.org/download/ATTUNIXSystemVRelease4Version2/sysvr4.tar.bz2 did have them indeed, I didn't check properly earlier. – Stéphane Chazelas Aug 01 '18 at 11:15
  • To the last part of the question, is there a simple way to get the parent of the symlink without cd and pwd? Something equivalent to: echo $(cd test2/nested1/; dirname \pwd -L`)` – Eric Haynes Aug 01 '18 at 22:47
  • Oof. It's nice that pwd and cd have -L options, but mv doesn't!

    Is there a shell that consistently interprets .. as a logical path? I often have my CWD set to a symlink, because I like keeping repo files and more private data files in separate directories, and thus symlink the repo as a project subdirectory. (i.e. cd ~/git git clone foo mkdir -p ~/proj/foo cd ~/proj/foo ln ../git/foo git curl me@foo.net/private.key cd git ./foo ../private.key.) But I've ended up with weird junk in my repo root directory, and occasionally overwritten files, and this explains why.

    – Michael Scheper Mar 25 '24 at 15:54
4

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...

icarus
  • 17,920
  • 5½ years have passed, so I assume you're waiting on hold for a bank. It's a helpful answer as it is, but I still hope you get a chance to make the edit. – Michael Scheper Mar 25 '24 at 15:37
  • @MichaelScheper How did you know it was a bank? Sorry I have lost the context, Rob has retired from Google, ... What more information can I help with? – icarus Mar 26 '24 at 01:22
1

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

http://linuxcommand.org/lc3_man_pages/cdh.html

Nur
  • 131
  • 4