Search code examples
bashshposixcdash

How POSIX compliant is "/path/file/.."?


I wanted to change current directory into shell script into directory, containing specific regular file. I found that following trick works in mksh and busybox sh:

path=/path/to/regular/file
cd $path/.. 

but not in GNU Bash:

bash: cd: /path/to/regular/file/..: Not a directory

Is this trick not posix-compatible, or Bash is too pedantic?


Solution

  • Latest edition of the standard doesn't allow that. POSIX.1-2017 cd spec. says that if the pathname component preceding dot-dot is not a directory, cd shall consider that an error.

    From cd § DESCRIPTION - step 8.b:

    b. For each dot-dot component, if there is a preceding component and
       it is neither root nor dot-dot, then:
    
        i. If the preceding component does not refer (in the context of
           pathname resolution with symbolic links followed) to a
           directory, then the cd utility shall display an appropriate
           error message and no further steps shall be taken.
    

    When cd is invoked with -P option, this step is omitted; but then chdir() fails if one of the pathname components names an existing file that is neither a directory nor a symbolic link to a directory.

    Besides, permitting that trick also allows inconsistent behavior in cd. For example, when run in a directory containing a regular file named bar, and a directory named foo containing another directory named bar, the following two commands do different things in a shell where cd ignores non-directory components preceding a dot-dot, despite that CDPATH contains the empty string (i.e. the current working directory) in both cases.

    CDPATH= cd bar/..
    
    CDPATH=:foo cd bar/..
    

    Below transcripts visualize the difference between non-conforming and conforming implementations clearly.

    $ tree -F
    .
    ├── bar
    └── foo/
        └── bar/
    
    2 directories, 1 file
    
    $ ash
    $ CDPATH= cd bar/..
    $ pwd
    /home/oguz
    $ CDPATH=:foo cd bar/..
    /home/oguz/foo
    
    $ bash
    $ CDPATH= cd bar/..
    bash: cd: bar/..: Not a directory
    $ CDPATH=:foo cd bar/..
    /home/oguz/foo
    

    bosh, gwsh, ksh93u+m, and yash are other actively maintained shells that implement the same behavior as bash.