os.path.commonpath

Use commonpath. commonprefix is a plain string function living in os.path for historical reasons: for '/usr/lib' and '/usr/local' it answers '/usr/l'. commonpath splits into components first, ignores '' and '.' components, and refuses to mix absolute and relative paths.

os.path functionPython 3.5+ (commonprefix: all versions)Live demo
Common call
os.path.commonpath([a, b])
Returns
'/srv/app'
Replaces
commonprefix() for paths — and hand-written startswith() checks
Watch out
commonprefix('/usr/lib', '/usr/local') is '/usr/l'; commonpath raises ValueError for [] or mixed absolute/relative
os.path.commonpath(paths) / os.path.commonprefix(list)
→ str

Demo

Live evaluation
The same paths through both functions.
Try:
Inputs
pathslist[str]paths, comma-separated
Code
import posixpath
(posixpath.commonpath(['/srv/app/static', '/srv/app/templates']), posixpath.commonprefix(['/srv/app/static', '/srv/app/templates']))
Result
('/srv/app', '/srv/app/')

commonprefix stops at the first differing character, so '/srv/app/static' and '/srv/app/templates' give '/srv/app/' and '/usr/lib' with '/usr/local' gives the meaningless '/usr/l'. commonpath works on whole components and ignores './'. Mixing '/srv' and 'srv' raises ValueError: Can't mix absolute and relative paths. In the second tab only avatar.png stays inside: '..', an absolute name and the look-alike folder uploads-old all leave /srv/uploads, and a startswith(base) test would have let uploads-old through.

Parameters

NameTypeRequiredDescription
pathsiterable of str | bytes | PathLikeyescommonpath: two or more paths (one is fine). Any iterable since 3.13; before, a sequence.
listlist of stryescommonprefix: any strings (or lists) — compared character by character.

Return value

str — commonpath: a real sub-path shared by all (no trailing separator). commonprefix: the longest common string prefix ('' for an empty list).

Common patterns

Reject paths outside a base folder
Resolve first (realpath also defeats symlinks), then compare.
import os
def inside(base, path):
    base = os.path.realpath(base)
    path = os.path.realpath(path)
    return os.path.commonpath([base, path]) == base
Common root of many files
For display or for choosing an archive root.
import os
root = os.path.commonpath(file_list)
Longest common string prefix (not paths)
commonprefix is fine for plain strings.
import os
prefix = os.path.commonprefix(['interstellar', 'internet', 'interval'])

Examples

1. Shared folder
import posixpath posixpath.commonpath(['/srv/app/static', '/srv/app/templates'])
Returns
'/srv/app'
2. commonprefix keeps the slash
import posixpath posixpath.commonprefix(['/srv/app/static', '/srv/app/templates'])
Returns
'/srv/app/'
3. commonprefix splits names
import posixpath (posixpath.commonprefix(['/usr/lib', '/usr/local']), posixpath.commonpath(['/usr/lib', '/usr/local']))
Returns
('/usr/l', '/usr')
4. One path is the parent
import posixpath posixpath.commonpath(['a/b/c', 'a/b'])
Returns
'a/b'
5. Trailing slashes do not matter
import posixpath posixpath.commonpath(['/srv/app/', '/srv/app'])
Returns
'/srv/app'
6. Empty input
import posixpath posixpath.commonpath([])
Returns
ValueError: commonpath() arg is an empty sequence
7. Plain strings with commonprefix
import posixpath posixpath.commonprefix(['interstellar', 'internet', 'interval'])
Returns
'inter'

Pitfalls

1. Using commonprefix for paths
It works character by character and can cut a folder name in half.
commonprefix
import posixpath
posixpath.commonprefix(['/home/ada', '/home/adam'])
'/home/ada'
commonpath
import posixpath
posixpath.commonpath(['/home/ada', '/home/adam'])
'/home'
2. Checking containment with startswith
'/srv/uploads-old' starts with '/srv/uploads'. Compare whole components.
startswith
'/srv/uploads-old/x'.startswith('/srv/uploads')
True
commonpath
import posixpath
posixpath.commonpath(['/srv/uploads', '/srv/uploads-old/x']) == '/srv/uploads'
False
3. Comparing without normalizing first
commonpath does not resolve '..' — normalize (or realpath) the candidate before the check.
raw join
import posixpath
base = '/srv/uploads'
p = posixpath.join(base, '../etc/passwd')
posixpath.commonpath([base, p]) == base
True
normpath first
import posixpath
base = '/srv/uploads'
p = posixpath.normpath(posixpath.join(base, '../etc/passwd'))
posixpath.commonpath([base, p]) == base
False

When to use

Use it
  • Finding the shared folder of several paths (commonpath)
  • Path-traversal checks after normpath / realpath
Reach for something else
  • Paths → never commonprefix
  • pathlib code → PurePath.is_relative_to(base) after resolve()

Notes

CPython impl
posixpath.commonpath splits every path on '/', drops '' and '.' components, and compares the min and max component lists (any list between them shares their common prefix). commonprefix is genericpath.commonprefix: the same min/max trick on the raw strings
Windows
ntpath.commonpath also raises ValueError for paths on different drives, and compares case-insensitively
3.13
commonpath accepts any iterable, not only sequences

FAQ

commonpath compares whole path components and returns a valid path ('/usr' for /usr/lib and /usr/local). commonprefix compares characters and can return a half name ('/usr/l'). The docs warn that commonprefix is not secure for paths.