absolute path if PWD itself is a symlink

58 Views Asked by At

I have following folder structure created via mkdir -p foo subdir/bar && ln -s subdir/bar bar

├── bar -> subdir/bar
├── foo
└── subdir
    └── bar

when working in bar folder: How can I resolve/expand a relative path (../foo)?

user@mypc:/work/bar$ realpath -e ../foo     # result: "realpath: ../foo: No such file or directory"
user@mypc:/work/bar$ realpath -m ../foo     # result: "/work/subdir/foo"
user@mypc:/work/bar$ realpath -L ../foo     # result: "/work/subdir/foo"
user@mypc:/work/bar$ readlink ../foo        # result: ""
user@mypc:/work/bar$ readlink -f ../foo     # result: "/work/subdir/foo"
user@mypc:/work/bar$ readlink -m ../foo     # result: "/work/subdir/foo"

What can I do to get absolute path /work/foo from relative path ../foo?

Edit1: Unfortunately it even becomes more tricky if I only have a variable MYPATH which I do not know if it is relative or absolute.

user@mypc:/work/bar$ MYVAR="../foo"
user@mypc:/work/bar$ realpath -L "$PWD/$MYVAR"  # result: "/work/foo" 
user@mypc:/work/bar$ MYVAR="/work/foo"
user@mypc:/work/bar$ realpath -L "$PWD/$MYVAR"  # result: "realpath: /work/bar//work/foo: No such file or directory"

What can I do to get absolute path /work/foo from variable MYPATH (which might be set to ../foo or /work/foo?

3

There are 3 best solutions below

0
user1934428 On

Based on your directory layout:

cd /work/bar
ls -d ../foo # Check that foo really exists
realpath ../foo # Prints /work/bar

If you get a not found error, it means that the directory really does not exist. That's why I propose to check this before.

However, your setting is a bit confusing in that you have two bar directories. Make sure that you apply the correct relative path when calling realpath.

2
Weijun Zhou On

When dealing with paths with symlinks, you need to be aware of the difference between "logical" and "physical" directory structures as explained in this answer.

In your example, after cd bar, the logical directory is /work/bar and the physical directory is /work/subdir/bar. An important thing to know is that the filesystem itself only contains information about physical directory structures, and the logical directory structure is only tracked by your shell.

If you pass ../foo as a parameter to an external process, the process can only ever resolve it to /work/subdir/foo because it cannot possibly know the logical directory structure which is only known by the shell. If you do cd .. (without set -o physical) instead, you are back in /work/ instead of /work/subdir/ because cd is part of the shell and can obtain the necessary info about logical directory structure.

Can we make realpath behave like cd? Yes, but we need to forward the info about logical path to the command line argument of realpath. The keypoint is that, realpath -L knows how to deal with logical paths, but above all it needs to know about the logical path, which can only be forwarded explicitly from the shell. One way to do so is via $PWD.

The solution to your problem is, therefore,

realpath -L "$PWD"/../foo
0
pmf On

The problem is that the symlink only extends to bar and everything below it. But .. moves upwards, leaving the symlink's logic. Therefore, you need to configure the process that deals with ../foo to not resolve the .. part logically.

For example, if you wanted to cd into ../foo, use its -L option, assuming your cd implements it, see some manual excerpts on cd -L:

  • The POSIX manual describes cd -L as

symbolic link components shall not be resolved before dot-dot components are processed

  • Bash's cd -L is defined as

the -L option forces symbolic links to be followed by resolving the link after processing instances of .. in dir.

Therefore:

$ pwd
/home/user/work

$ tree --noreport
.
├── bar -> subdir/bar
├── foo
└── subdir
    └── bar

$ cd bar

$ pwd; pwd -P
/home/user/work/bar
/home/user/work/subdir/bar

$ mypath='../foo'

$ cd -L "$mypath"

$ pwd; pwd -P
/home/user/work/foo
/home/user/work/foo

Note: If you have substituted cd to some other implementation or script, you may want to use builtin cd instead.