OTZ
OTZ

Reputation: 3073

Expand a relative path but not follow any symlink in the path in Python

This is a subtle question, I know, but I hope you can bear with me for a moment.

Suppose /tmp/dir is a symlink to /home/user/some/dir. Suppose also that your current working directory is /tmp/dir.

Even expanding something like . does not seem to be possible, as os.getcwd() returns /home/user/some/dir instead of /tmp/dir, which is what pwd command returns. Relative dir can also be ../dir/../dir/subdir, .././././dir/foo, etc.

So my question: Is there any reliable function that does path expansion of a relative path but does not follow the symlink that may exist in the relative path. In case of ../dir/../dir/subdir, for example, I would like to get /tmp/dir/subdir and NOT /home/user/some/dir/subdir.

Just to avoid getting something I do not want, the answer is NOT os.path.abspath, os.path.realpath, os.path.expanduser, or os.path.relpath.

Upvotes: 0

Views: 1188

Answers (2)

Aya
Aya

Reputation: 42030

Seems as if you're not the first to notice this odd behavior of chdir(2).

There's nothing about it in the Linux manpage, but a similar page says...

int chdir(const char *path);

[...]

The chdir() function makes the directory named by path the new current directory. If the last component of path is a symbolic link, chdir() resolves the contents of the symbolic link. If the chdir() function fails, the current directory is unchanged.

...although with no explanation as to why it resolves the symbolic link.

So, you can't technically have a current working directory of /tmp/dir, even if your shell claims otherwise.

However, you can exploit the fact that the shell's built-in cd command sets the environment variable PWD to the value you entered, so you can do this...

$ cd /tmp/dir
$ python
>>> import os
>>> os.getcwd()
'/home/user/some/dir'
>>> os.environ['PWD']
'/tmp/dir'
>>> os.path.normpath(os.path.join(os.environ['PWD'], '../dir/../dir/subdir'))
'/tmp/dir/subdir'

...although it may fail in cases when the process wasn't started from a shell.

Upvotes: 1

Guillaume
Guillaume

Reputation: 10971

Not sure that's what you're looking for, but... you can write a function that generate the "logical" full path from the relative path and current working directory then check that the generated path really exists on the system. That function could look like:

import os

def absolute_path(path):
    wd = os.getcwd()
    full = os.path.join(wd, path)

    parts = full.split(os.sep)
    kept = []
    skip = 0
    for part in reversed(parts):
        if part == '..':
            skip += 1
        elif skip == 0:
            kept.insert(0, part)
        else:
            skip -= 1
    return os.sep + os.path.join(*kept)

Upvotes: 0

Related Questions