4

Suppose I have a path like this:

/folderA/folderB/folderC/item1
/folderA/folderB/folderX/item2
/folderA/folderB/folderF/item1
/folderA/folderB/folderQ/item5

How do I get the last two elements of the file path?

folderC/item1
folderX/item2
folderF/item1
folderQ/item5

I know I can git either the directory or the file by

>>> os.path.basename('/folderA/folderB/folderC/item1')
>>> item1


>>> os.path.dirname('/folderA/folderB/folderC/item1')
>>> /folderA/folderB/folderC/
sanjayr
  • 1,679
  • 2
  • 20
  • 41

3 Answers3

10
from pathlib import Path
path = r"/folderA/folderB/folderC/item1"
Path(*Path(path).parts[-2:])

output:

PosixPath('folderC/item1')

NB: if you prefer you can cast PosixPath object into a str

itamar kanter
  • 1,170
  • 3
  • 10
  • 25
8
import os
path = r"/folderA/folderB/folderC/item1"

For Windows:

os.sep.join(path.rsplit(r"/")[-2:])

Operating system independent:

os.sep.join(os.path.normpath(path).split(os.sep)[-2:])
Andreas
  • 8,694
  • 3
  • 14
  • 38
1

You might want to think about doing this in an OS independent way. In that case you should use the os.path routines to do it. However the os.path.split() does not behave the same as the normal split() so you cannot just use that directly for what you want.

You could use the splitall(path) method here. I will not include the code since I did not write it, so I will just link to it instead. splitall() uses os.path.split() repeatedly to break the path up.

It returns a list with the path components in it. You can then pick off the last three using a slice operation on the returned list. If you want the last three back together as a path again you would use os.path.join().

Edit:

I found a version of this on stackoverflow here that I like better. I have linked to the answer, but since it is on this site (and so is under CC BY-SA) I will also flesh it out more fully and include code here. Warning, I have only tested this on Windows but it should be fine for other OSes. Let me know if not.

import os
def split_path(path):
    folders = []
    drive, path = os.path.splitdrive(path)
    while True:
        path, folder = os.path.split(path)

        if folder:
            folders.append(folder)
        else:
            if path:
                folders.append(path)
            break
        
    if drive:
        folders.append(drive)
    return folders[::-1]

I was testing @Andreas answer but using just os.path.normpath(path).split(os.sep)[-2:] without the os.sep.join(), since I was using to generate the folder list in the place of the above splitall()/split_path() to get a list of all the path components. That is a generally more useful tool from my perspective though I acknowledge not needed for the OPs question.

In that case the split(os.sep) does the wrong thing. It uses the leading separator as a separator (obviously) so it returns an empty path element instead of the leading root directory (which is the separator). That is wrong when you want a list of directory components. It is not apparent when you include the because join hides that since when you join the leading empty entry to the rest, it joins it with the separator adding it to the front. SO I condisider this problematic in the general non join case.

For completeness, the reconstruct_path() below will reconstruct the path from a list of folders. It will handle the drives on Windows including relative paths that include a drive, which can be a thing.

import os
import sys

def reconstruct_path(folders):
    folders = folders[:]
    path = ""

    # On windows, pop off the drive if there is one. Affects handling of relative vs rooted paths 
    if sys.platform == 'win32' and ':' == folders[0][-1]:
        path = folders[0]
        del folders[0]
    if folders and folders[0] == os.sep:
        path += folders[0]
        del folders[0]
    path += os.sep.join(folders)
    return path
Glenn Mackintosh
  • 2,765
  • 1
  • 10
  • 18
  • I mean if you want os independet you also can do this: os.sep.join(os.path.normpath(path).split(os.sep)[-2:]) but the readability is extremely bad in my opinion. – Andreas Aug 07 '20 at 22:17
  • @Andreas That one liner does not work if the path is an absolute path (with a leading '/' or '\\' depending on your OS). It gives a "." in the list where the root should be. I know in the OP's case that is not an issue since OP is ignoring the stuff at the front and only looking at the last two path elements. However, in case someone hits this looking for a general solution, I want to caution them that this one liner can be problematic. – Glenn Mackintosh Aug 09 '20 at 05:26
  • Thank you for the comment, are you sure this is the case? I posted a second os independent line. I tested it with leading "/" and both "/" and "\\" : path = r"/folderA\\folderB/folderC\\item1" and it works just fine for me, because the normpath gets rid of those. – Andreas Aug 09 '20 at 11:23
  • @Andreas Interesting. I was testing it without the join, since I was using instead of the above `splitall()` which returns a list of all the path components. In that case it does the wrong thing, It uses the leading separator as a separator (obviously) so it returns an empty path element instead of the leading root directory (which is the separator). That is wrong when you want a list of directory components. The `join()` hides that since when you join the leading empty entry to the rest, it joins it with the separator adding it to the front. Still problematic in the general non `join()` case. – Glenn Mackintosh Aug 09 '20 at 17:49