1

Premise

I've read what's listed in Bibliography regarding cd, pwd, set -P.

By default, or when the -L option is supplied, symbolic links in directory are resolved after cd processes an instance of ‘..’ in directory. (ref. Bash reference manual - sec. "4 Shell Builtin Commands" - cd paragraph)

If ‘..’ appears in directory, it is processed by removing the immediately preceding pathname component, back to a slash or the beginning of directory. (ibid)

My version of Bash is

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

Problem

To test things out I have the following file scheme in my current working directory that is the root of my experiment (dir111 is a symbolic link to dir010).

dir010/
dir020/
dir110/dir111 -> ../dir010
my_dir="$PWD"
cd dir110/dir111              # 1
cd ..                         # 2
cd dir111                     # 3
cd ../dir020                  # 4
cd "$my_dir"                  # 5
cd dir110/dir111/../dir020    # 6
cd -                          # 7
cd dir110/dir111/../../dir020 # 8
  1. sets the current working directory to "$my_dir"/dir110/dir111, as expected[^1].
  2. sets the current working directory to "$my_dir"/dir110, as expected[^1].
  3. back to 1.
  4. sets the current working directory to "$my_dir"/dir020, unexpected. I would expect cd to fail at "$my_dir"/dir110/dir020 (see below for explanation).
  5. back to the root of my experiment.
  6. same as 4.
  7. back to the root of my experiment.
  8. sets the current working directory to "$my_dir"/dir020, as expected[^1], but in contrast with 6.

Line 4 should fail for the reason in the following paragraph.

The reference manual and the man page (see quote in the Premise) both tell that .. make cd only remove the preceding pathname component, instead of looking at the .. of the inode entry. So the parent directory of "$my_dir/dir110/dir111 as seen by cd (in default behaviour) is "$my_dir/dir110, which does not contain dir020.

I thought that this behaviour may be an exception caused by .. being the prefix of the «dirname» provided to cd «dirname». However, using .. as an infix (line 6) contrast with this.

cd is behaving as if cd -P has been executed (or set -P (or similar) command issued before), that is by not following symbolic links. With cd -P, at 1., the current working directory would be set to $my_dir/dir010.

set -o tells me

[omissis]
physical        off
pipefail        off
posix           off
[omissis]

Is my (and the ones like me in other posted questions) interpretation of the reference manual correct? What am I missing? How can the behaviour of cd be described?

[^1]: The default behaviour of cd is to follow symlinks

Bibliography

6
  • Can you explain why you would expect 4 to fail? And note that $PWD is set again every time you change directories, so you will never be in $PWD/some_other_dir, your $PWD will always be set to your current directory, so I don't understand what "sets the current working directory to "$PWD"/dir020, unexpected." means. Do you mean it sets it to dir110/dir020?
    – terdon
    Commented Jul 24 at 16:42
  • With $PWD/some_other_dir I meant $my_dir/some_other_dir. I'm going to correct that right now.
    – the_eraser
    Commented Jul 24 at 16:47
  • Thanks. Please also explain why you expected 4 to fail since that is the main issue here and I don't understand what makes you think it would fail.
    – terdon
    Commented Jul 24 at 16:49
  • yes I'm doing that (from my mobile)
    – the_eraser
    Commented Jul 24 at 16:53
  • I added some more explanation.
    – the_eraser
    Commented Jul 24 at 17:22

1 Answer 1

3

The source code notes that this is a special case, where bash retries the cd if the logical directory didn't work out:

  /* We're not in physical mode (nolinks == 0), but we failed to change to
     the canonicalized directory name (TDIR).  Try what the user passed
     verbatim. If we succeed, reinitialize the_current_working_directory.
     POSIX requires that we just fail here, so we do in posix mode. */

To test this, I made another directory in dir110 called dir120, and ran strace to see the chdir(2) calls:

% strace -e chdir bash -c 'cd dir110/dir111; cd ../dir020'
chdir("/home/muru/foo/dir110/dir111")   = 0
chdir("/home/muru/foo/dir110/dir111/../dir020") = 0
+++ exited with 0 +++
% mkdir dir110/dir120
% strace -e chdir bash -c 'cd dir110/dir111; cd ../dir120'
chdir("/home/muru/foo/dir110/dir111")   = 0
chdir("/home/muru/foo/dir110/dir120")   = 0
+++ exited with 0 +++

You can see the difference in the form of the path given to chdir().

And of course, as the comment says, the behaviour is different in POSIX mode:

% POSIXLY_CORRECT=1 strace -e chdir bash -c 'cd dir110/dir111; cd ../dir020'
chdir("/home/muru/foo/dir110/dir111")   = 0
bash: line 1: cd: ../dir020: No such file or directory
+++ exited with 1 +++
8
  • Tomorrow I'll start my day reading your answer. Thanks for the link to the source code (I couldn't find where cd was defined but it was not that difficult).
    – the_eraser
    Commented Jul 24 at 17:25
  • 1
    hmm but those traces don't actually show any chdir() calls failing, right?
    – ilkkachu
    Commented Jul 24 at 18:51
  • 1
    @ilkkachu it doesn't even get to chdir. Bash tries to canonicalise the path, and then checks if canonical path exists (adding statx to the list of syscalls, you'll see a failed one for /home/muru/foo/dir110/dir020). There's a comment a bit further up which hints at this: /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath returns NULL (because it checks the path, it will return NULL if the resolved path doesn't exist), fail immediately. */
    – muru
    Commented Jul 25 at 1:35
  • So, in the first strace command, Bash invokes the system call chdir("/home/muru/foo/dir110/dir111/../dir020"), which changes directory looking at the .. of the inode entry of dir010, because it checked by itself that /home/muru/foo/dir110/dir020 was not existing.
    – the_eraser
    Commented Jul 25 at 11:29
  • 1
    Basically, since Bash wants you to cd into something, it falls back to physical mode. I found a description of this in the Bash Reference Manual. It is in "6.11 Bash POSIX Mode": "When the cd builtin is invoked in logical mode, and the pathname constructed from $PWD and the directory name supplied as an argument does not refer to an existing directory, cd will fail instead of falling back to physical mode.".
    – the_eraser
    Commented Jul 25 at 11:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .