I have a directory bar
inside a directory foo
, with file foo_file.txt
in directory foo
and file bar_file.txt
in directory bar
; i.e.
computer$ ls
foo/
computer$ ls foo/
bar/ foo_file.txt
computer$ ls foo/bar/
bar_file.txt
Using the python os.path.relpath function, I expect:
os.path.relpath('foo/bar/bar_file.txt', 'foo/foo_file.txt')
to give me:
'bar/bar_file.txt'
However, it actually gives me:
'../bar/bar_file.txt'
Why is this? Is there an easy way to get the behavior I want?
EDIT: This is on Linux with Python 2.7.3
path. relpath() method in Python is used to get a relative filepath to the given path either from the current working directory or from the given directory. Note: This method only computes the relative path.
isdir() os. path. isdir() method in Python is used to check whether the specified path is an existing directory or not.
path. dirname() method in Python is used to get the directory name from the specified path.
First, you have to import the os module in Python so you can run operating system functionalities in your code. Then you create the variable absolute_path which fetches the current directory relative to the root folder. This is the full path to your working directory, in this case, ~/home/projects/example-project/ .
os.path.relpath()
assumes that its arguments are directories.
>>> os.path.join(os.path.relpath(os.path.dirname('foo/bar/bar_file.txt'),
os.path.dirname('foo/foo_file.txt')),
os.path.basename('foo/bar/bar_file.txt'))
'bar/bar_file.txt'
relpath
has unexpected behavior. It treats all elements of a path as though it is a directory. So, in the path:
/path/to/a/file.txt
file.txt
is treated like a directory as well.
This means that when you run relpath
on two paths, say,
>>> from os.path import relpath
>>> relpath('/path/to/dest/file.txt', '/path/to/origin/file.txt')
'../../dest/file.txt'
This is incorrect. The true relative path from directory origin to dest is '../dest/file.txt'
This gets especially frustrating if you're trying to create symlinks and they end up being malformed.
To fix the problem, we must first find out if the path points to a file, if not we can do the comparison as usual, otherwise we need to remove the filename from the end, do the comparison with only directories, and then add the file back to the end.
Note that this only works if you actually have these files created on your system, python must access the filesystem to find the node types.
import os
def realrelpath(origin, dest):
'''Get the relative path between two paths, accounting for filepaths'''
# get the absolute paths so that strings can be compared
origin = os.path.abspath(origin)
dest = os.path.abspath(dest)
# find out if the origin and destination are filepaths
origin_isfile = os.path.isfile(origin)
dest_isfile = os.path.isfile(dest)
# if dealing with filepaths,
if origin_isfile or dest_isfile:
# get the base filename
filename = os.path.basename(origin) if origin_isfile else os.path.basename(dest)
# in cases where we're dealing with a file, use only the directory name
origin = os.path.dirname(origin) if origin_isfile else origin
dest = os.path.dirname(dest) if dest_isfile else dest
# get the relative path between directories, then re-add the filename
return os.path.join(os.path.relpath(dest, origin), filename)
else:
# if not dealing with any filepaths, just run relpath as usual
return os.path.relpath(dest, origin)
To get the real relative path from directory origin to dest, run:
>>> relrealpath('/path/to/origin/file.txt', '/path/to/dest/file.txt')
'../dest/file.txt'
I flipped the argument order because in my brain it makes more sense to say, "I want to know the relative path to take from arg1 to get to arg2", the standard relpath
implementation has it backwards (probably because that's how UNIX does it).
This need to access the filesystem is the real reason that relpath
has such strange behavior. Filesystem calls are expensive, so python leaves it up to you to know whether you're dealing with a file or with a directory and only performs string operations on the path you provide.
Note: There is probably a way to make the realrelpath
function a bit more efficient. For example, I'm not sure if the abspath
calls are necessary, or if they could be bundled with the os.path.isfile
checks with a syscall that returns more information. I welcome improvements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With