Either I've completely misunderstood something about symlinks (most likely), or their behaviour has changed at some point and I'm now catching up.

I have a script directory that resides somewhere on my file system, let's say /tmp/scripts for the sake of this explanation. This script directory is intended be added to an arbitrary project by creating a symlink to it, within the project - e.g. ln -s /tmp/scripts $(PROJECT)/scripts. The scripts within this directory expect to find some project-specific info in ../config (i.e. a subdirectory of the project). However, what I'm finding is that when scripts are run from within the script directory they look in /tmp/config (which doesn't exist) rather than $(PROJECT)/config, resolving the relative path against the true location of the script directory, and ignoring the symlink context. This is true even though cd $(PROJECT)/scripts; pwd shows the symlink context.

Here's a simple example of this, on Ubuntu 16.04, with Bash 4.3.48:

$ mkdir a
$ touch a/apples
$ mkdir -p b/a
$ touch b/a/bananas
$ mkdir -p b/c
$ ln -s b/c c
$ cd c
$ pwd
/home/user/c      # note this does not show '/home/user/b/c'
$ ls ../a
$ cd ../a
$ ls

This surprises me, because it suggests that a process with a working directory behind a symlink does not have the same environment as the process that invoked it.

Perhaps it's always been like this? Why does the script process know that it's not really in $(PROJECT)/scripts yet pwd does not? Is pwd faking it? Can anyone shed some light please?

EDIT: here's a simple practical example, that first sets up the relative directories, then creates a script (in this case a simple Makefile) and then invokes it:

mkdir -p a
echo "A:=42" > a/Config
mkdir -p b/a
echo "A:=77" > b/a/Config
mkdir -p b/c
ln -sfn b/c c
echo -e "include ../a/Config\nall:\n\t@echo \${A}" > c/Makefile
cd c

This will output 77 whereas I had hoped it that it might output 42, as that would be the value set by the project for use by the symlinked Makefile et al. In reality, the directory b/a doesn't actually exist, so instead:

mkdir -p a
echo "A:=42" > a/Config
mkdir -p b/c
ln -sfn b/c c    
echo -e "include ../a/Config\nall:\n\t@echo \${A}" > c/Makefile    
cd c

Resulting in:

$ ./setup
Makefile:1: ../a/Config: No such file or directory
make: *** No rule to make target '../a/Config'.  Stop.
  • cd is evaluated by your shell, it's not a good test case. ls is reading the actual .. entry. Commented Jun 21, 2018 at 0:06
  • Could you give an example of an actual script that exhibits the behaviour you're asking about? Show what happens, and what you expect/what you think used to happen, and if there are any cases that do behave as expected (which it sounds like there are) include those too. Commented Jun 21, 2018 at 0:07
  • A related question is of course unix.stackexchange.com/questions/413204 .
    – JdeBP
    Commented Jun 21, 2018 at 0:22
  • @MichaelHomer good idea - I've added an example that shows this behaviour using make.
    – davidA
    Commented Jun 21, 2018 at 2:06
  • @JdeBP thanks for the link, that's led to some good explanations and helped demystify things a bit. Now I'm wondering how to make all my scripts consistent to use either the Logical path all the time, or the Physical path all the time.
    – davidA
    Commented Jun 21, 2018 at 2:07

1 Answer 1


If you like cd to behave as you expect, call:

cd -P c

and pwd will print:


What you see is a problem that is caused by the fact that your shell follows an unhappy POSIX decision and makes cd -L the default. This causes the shell to remember the apparent $PWD /home/user/c. Since the shell command cd .. does not call chdir("..") but rather shortens the $PWD value by one element and then chdir()s to that shortened path, cd .. gets you back from where you did come.

This is a security risk, as people tend to run e.g.:

ls ../

and after being happy with the result, then may run:

cd ../; rm *.c

and remove different files than expected.

Note that the POSIX default is modeled after the default behavior of ksh88 and at the same time when ksh introduced that default behavior, AT&T decided to implement the safer variant cd -P as the default in the Bourne Shell.

You must log in to answer this question.

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