Check if a directory is a (file system) root
Question:
I have a script that searches for a directory containing a specific file, starting from the current directory and going up the tree (think of trying to find out where the .git
directory sits).
My method looks like this:
def getDir(self,cwd):
path = os.path.abspath(cwd)
if not os.path.isdir(path):
raise RuntimeError("getDir should not be run on files")
if FILE in os.listdir(path):
return path
parent = os.path.join(path, os.path.pardir)
if os.access(parent, os.R_OK) and os.access(parent, os.X_OK):
return self.getDir(parent)
else
return None
Now the problem with this method is that, if it cannot find the directory, it loops (and eventually stack-overflows) because apparently joining /
and ..
gives you /
again. I tried comparing path
with parent
or their repr
s, but that did not work (they were always distinct). My workaround for now is to include a depth counter in the recursive method and stop at some random maximum threshold.
My question is thus, is there a reliable cross-platform way to check whether I have reached a root in the file system?
Answers:
I don’t think you can find out if it’s a file system root portably, however I’d suggest doing a call to os.path.realpath()
on both the current dir and your calculated parent and compare if they’re the same — this means you are spinning your wheels and there’s no point in proceeding.
For example:
>>> os.path.realpath('/usr/bin/..')
'/usr'
>>> os.path.realpath('/usr/bin/../..')
'/'
>>> os.path.realpath('/usr/bin/../../..')
'/'
>>> os.path.realpath('/..')
'/'
if os.path.dirname(path) == path:
# you have yourself root.
# works on Windows and *nix paths.
# does NOT work on Windows shares (\servershare)
This works for us on Linux. Not sure about Windows, however:
def _find_root(start, stop, func=os.path.exists):
"""Search up the start hierarchy for stop; return None at the FS root.
Uses func() to determine if stop exists; default is os.path.exists().
"""
if func(os.path.join(start, stop)):
return start
else:
if os.path.samefile(start, os.path.dirname(start)):
return None
else:
return _find_root(os.path.dirname(start), stop)
We use os.path.normpath(start) in the call to this function.
With pathlib.Path
there’s a sharp edge if one wants to do it properly. For Path("~/../../")
to work, one has to use expanduser()
and resolve()
in correct order:
from pathlib import Path
path = Path("~/../..")
path = path.expanduser().resolve()
if path.parent == path:
print("This points to fs root!")
Otherwise, the condition will end up with a path that is ../
away from CWD.
pathlib
paths have an anchor
property to do just that. So you can just check if you’re at the absolute path’s root:
from pathlib import Path
absolute_path = Path(path_to_check).absolute()
if absolute_path == Path(absolute_path.anchor):
print("it's a root")
It’s your choice whether to use absolute
or resolve
, they handle symlinks differently.
I have a script that searches for a directory containing a specific file, starting from the current directory and going up the tree (think of trying to find out where the .git
directory sits).
My method looks like this:
def getDir(self,cwd):
path = os.path.abspath(cwd)
if not os.path.isdir(path):
raise RuntimeError("getDir should not be run on files")
if FILE in os.listdir(path):
return path
parent = os.path.join(path, os.path.pardir)
if os.access(parent, os.R_OK) and os.access(parent, os.X_OK):
return self.getDir(parent)
else
return None
Now the problem with this method is that, if it cannot find the directory, it loops (and eventually stack-overflows) because apparently joining /
and ..
gives you /
again. I tried comparing path
with parent
or their repr
s, but that did not work (they were always distinct). My workaround for now is to include a depth counter in the recursive method and stop at some random maximum threshold.
My question is thus, is there a reliable cross-platform way to check whether I have reached a root in the file system?
I don’t think you can find out if it’s a file system root portably, however I’d suggest doing a call to os.path.realpath()
on both the current dir and your calculated parent and compare if they’re the same — this means you are spinning your wheels and there’s no point in proceeding.
For example:
>>> os.path.realpath('/usr/bin/..')
'/usr'
>>> os.path.realpath('/usr/bin/../..')
'/'
>>> os.path.realpath('/usr/bin/../../..')
'/'
>>> os.path.realpath('/..')
'/'
if os.path.dirname(path) == path:
# you have yourself root.
# works on Windows and *nix paths.
# does NOT work on Windows shares (\servershare)
This works for us on Linux. Not sure about Windows, however:
def _find_root(start, stop, func=os.path.exists):
"""Search up the start hierarchy for stop; return None at the FS root.
Uses func() to determine if stop exists; default is os.path.exists().
"""
if func(os.path.join(start, stop)):
return start
else:
if os.path.samefile(start, os.path.dirname(start)):
return None
else:
return _find_root(os.path.dirname(start), stop)
We use os.path.normpath(start) in the call to this function.
With pathlib.Path
there’s a sharp edge if one wants to do it properly. For Path("~/../../")
to work, one has to use expanduser()
and resolve()
in correct order:
from pathlib import Path
path = Path("~/../..")
path = path.expanduser().resolve()
if path.parent == path:
print("This points to fs root!")
Otherwise, the condition will end up with a path that is ../
away from CWD.
pathlib
paths have an anchor
property to do just that. So you can just check if you’re at the absolute path’s root:
from pathlib import Path
absolute_path = Path(path_to_check).absolute()
if absolute_path == Path(absolute_path.anchor):
print("it's a root")
It’s your choice whether to use absolute
or resolve
, they handle symlinks differently.