29

Is there a quick way to replace part of the path in python?

for example:

old_path='/abc/dfg/ghi/f.txt'

I don't know the beginning of the path (/abc/dfg/), so what I'd really like to tell python to keep everything that comes after /ghi/ (inclusive) and replace everything before /ghi/ with /jkl/mno/:

>>> new_path
    '/jkl/mno/ghi/f.txt/'
0

6 Answers 6

31

If you're using Python 3.4+, or willing to install the backport, consider using pathlib instead of os.path:

path = pathlib.Path(old_path)
index = path.parts.index('ghi')
new_path = pathlib.Path('/jkl/mno').joinpath(*path.parts[index:])

If you just want to stick with the 2.7 or 3.3 stdlib, there's no direct way to do this, but you can get the equivalent of parts by looping over os.path.split. For example, keeping each path component until you find the first ghi, and then tacking on the new prefix, will replace everything before the last ghi (if you want to replace everything before the first ghi, it's not hard to change things):

path = old_path
new_path = ''
while True:
    path, base = os.path.split(path)
    new_path = os.path.join(base, new_path)
    if base == 'ghi':
        break
new_path = os.path.join('/jkl/mno', new_path)

This is a bit clumsy, so you might want to consider writing a simple function that gives you a list or tuple of the path components, so you can just use find, then join it all back together, as with the pathlib version.

Sign up to request clarification or add additional context in comments.

3 Comments

pathlib looks great, suppose there isn't an equivalent in Python 2.7?
@HappyPy: Yes, there is. Not in the stdlib, but, like many of the new features of 3.x, there's a semi-official backport on PyPI that has all of the features that make sense in 2.x. (I believe this means everything but the stuff about converting to locale-based bytes names on Unix and Unicode str names on Windows; instead, you just get plain unspecified str on both platforms.)
For python >3.4: You have to do an import pathlib and the first line of the example should be: p = pathlib.Path(old_path) instead of path = pathlib.Path(old_path). Because it's referenced as p in the following lines. [Btw: the example helped a lot !]
17
>>> import os.path
>>> old_path='/abc/dfg/ghi/f.txt'

First grab the relative path from the starting directory of your choice using os.path.relpath

>>> rel = os.path.relpath(old_path, '/abc/dfg/')
>>> rel
'ghi\\f.txt'

Then add the new first part of the path to this relative path using os.path.join

>>> new_path = os.path.join('jkl\mno', rel)
>>> new_path
'jkl\\mno\\ghi\\f.txt'

4 Comments

Sorry, forgot to mention that I don't know what the beginning of the path is, so ideally I would like to be able to tell python to select only the part of the path starting from /ghi/ and replace anything that comes before with /jkl/mno/. Is this possible somehow?
That might be ambiguous though, what if you have foo/bar/foo/folder/f.txt and want to start at foo, which foo is it? Do you at least know how far up you want to go? like will it always be one folder up from the file location?
Yes, I'm sure there are no repeated folder names within the path. So, it would be a sort of split. I want python to select everything from '/ghi/' (in my example that is /ghi/f.txt) and replace whatever comes before with /jkl/mno/.
Bad idea to mixture os and pathlib. Just use one and stick with it.
10

The following is useful when you want to replace some known base directory in your path.

from pathlib import Path
old_path = Path('/abc/dfg/ghi/f.txt')
old_root = Path('/abc/dfg')
new_root = Path('/jkl/mno')
new_path = new_root / old_path.relative_to(old_root)
# Result: /jkl/mno/ghi/f.txt

I understand that the OP specifically mentioned that the path to the base directory is not known. However, since it is a common task to remove the path to the base directory, and the title of the question ("replace part of the path") is certainly bringing some folks with this subtype of problem here, I am posting it anyway.

1 Comment

This is the way. (I know, not for the Q as posted, but for most others who know old_root.)
9

You can use the index of ghi:

old_path.replace(old_path[:old_path.index("ghi")],"/jkl/mno/")
In [4]: old_path.replace(old_path[:old_path.index("ghi")],"/jkl/mno/" )
Out[4]: '/jkl/mno/ghi/f.txt'

2 Comments

I did not downvote, but this is not a great answer TBH. What if the sequence 'ghi' is repeated in the prefix? For instance, it won't work on /fooghi/bar/ghi/baz. Splitting into parts first could be better.
@TheDataScientician, the OP wanted to replace a full directory in the path, i.e /ghi/. Replacing a substring in the directory name is a very different thing.
2

A rather naive approach, but does the job:

Function:

def replace_path(path, frm, to):
    pre, match, post = path.rpartition(frm)
    return ''.join((to if match else pre, match, post))

Example:

>>> s = '/abc/dfg/ghi/f.txt'
>>> replace_path(s, '/ghi/', '/jkl/mno')
'/jkl/mno/ghi/f.txt'
>>> replace_path(s, '/whatever/', '/jkl/mno')
'/abc/dfg/ghi/f.txt'

Comments

0

I needed to replace an arbitrary number of an arbitrary strings in a path e.g. replace 'package' with foo in

VERSION_FILE = Path(f'{Path.home()}', 'projects', 'package', 'package', '_version.py')

So I use this call

_replace_path_text(VERSION_FILE, 'package', 'foo)

def _replace_path_text(path, text, replacement):
    parts = list(path.parts)
    new_parts = [part.replace(text, replacement) for part in parts]
    return Path(*new_parts)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.