Python 如何检查一个目录是否是另一个目录的子目录
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3812849/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to check whether a directory is a sub directory of another directory
提问by Simon
I like to write a template system in Python, which allows to include files.
我喜欢用 Python 编写一个模板系统,它允许包含文件。
e.g.
例如
This is a template
You can safely include files with safe_include`othertemplate.rst`
As you know, including files might be dangerous. For example, if I use the template system in a web application which allows users to create their own templates, they might do something like
如您所知,包含文件可能很危险。例如,如果我在允许用户创建自己的模板的 Web 应用程序中使用模板系统,他们可能会执行类似的操作
I want your passwords: safe_include`/etc/password`
So therefore, I have to restrict the inclusion of files to files which are for example in a certain subdirectory (e.g. /home/user/templates)
因此,我必须将文件的包含限制为例如位于某个子目录中的文件(例如/home/user/templates)
The question is now: How can I check, whether /home/user/templates/includes/inc1.rstis in a subdirectory of /home/user/templates?
现在的问题是:如何检查,是否/home/user/templates/includes/inc1.rst在 的子目录中/home/user/templates?
Would the following code work and be secure?
以下代码是否有效并且安全?
import os.path
def in_directory(file, directory, allow_symlink = False):
#make both absolute
directory = os.path.abspath(directory)
file = os.path.abspath(file)
#check whether file is a symbolic link, if yes, return false if they are not allowed
if not allow_symlink and os.path.islink(file):
return False
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
As long, as allow_symlinkis False, it should be secure, I think. Allowing symlinks of course would make it insecure if the user is able to create such links.
allow_symlink我认为,只要是 False,它就应该是安全的。如果用户能够创建这样的链接,当然允许符号链接会使其不安全。
UPDATE - SolutionThe code above does not work, if intermediate directories are symbolic links.
To prevent this, you have to use realpathinstead of abspath.
更新 - 解决方案如果中间目录是符号链接,则上面的代码不起作用。为了防止这种情况,您必须使用realpath而不是abspath.
UPDATE:adding a trailing / to directory to solve the problem with commonprefix() Reorx pointed out.
更新:添加一个尾随 / 到目录来解决 commonprefix() Reorx 指出的问题。
This also makes allow_symlinkunnecessary as symlinks are expanded to their real destination
这也变得allow_symlink不必要,因为符号链接被扩展到它们的真实目的地
import os.path
def in_directory(file, directory):
#make both absolute
directory = os.path.join(os.path.realpath(directory), '')
file = os.path.realpath(file)
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
采纳答案by blaze
os.path.realpath(path): Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path (if they are supported by the operating system).
os.path.realpath(path):返回指定文件名的规范路径,消除路径中遇到的任何符号链接(如果操作系统支持它们)。
Use it on directory and subdirectory name, then check latter starts with former.
在目录和子目录名称上使用它,然后检查后者是否以前者开头。
回答by Andrew_1510
I would test the result from commonprefix against the filename to get a better answer, something like this:
我会针对文件名测试 commonprefix 的结果以获得更好的答案,如下所示:
def is_in_folder(filename, folder='/tmp/'):
# normalize both parameters
fn = os.path.normpath(filename)
fd = os.path.normpath(folder)
# get common prefix
commonprefix = os.path.commonprefix([fn, fd])
if commonprefix == fd:
# in case they have common prefix, check more:
sufix_part = fn.replace(fd, '')
sufix_part = sufix_part.lstrip('/')
new_file_name = os.path.join(fd, sufix_part)
if new_file_name == fn:
return True
pass
# for all other, it's False
return False
回答by Rob Dennis
so, I needed this, and due to the criticisms about commonprefx, I went a different way:
所以,我需要这个,并且由于对 commonprefx 的批评,我采取了不同的方式:
def os_path_split_asunder(path, debug=False):
"""
http://stackoverflow.com/a/4580931/171094
"""
parts = []
while True:
newpath, tail = os.path.split(path)
if debug: print repr(path), (newpath, tail)
if newpath == path:
assert not tail
if path: parts.append(path)
break
parts.append(tail)
path = newpath
parts.reverse()
return parts
def is_subdirectory(potential_subdirectory, expected_parent_directory):
"""
Is the first argument a sub-directory of the second argument?
:param potential_subdirectory:
:param expected_parent_directory:
:return: True if the potential_subdirectory is a child of the expected parent directory
>>> is_subdirectory('/var/test2', '/var/test')
False
>>> is_subdirectory('/var/test', '/var/test2')
False
>>> is_subdirectory('var/test2', 'var/test')
False
>>> is_subdirectory('var/test', 'var/test2')
False
>>> is_subdirectory('/var/test/sub', '/var/test')
True
>>> is_subdirectory('/var/test', '/var/test/sub')
False
>>> is_subdirectory('var/test/sub', 'var/test')
True
>>> is_subdirectory('var/test', 'var/test')
True
>>> is_subdirectory('var/test', 'var/test/fake_sub/..')
True
>>> is_subdirectory('var/test/sub/sub2/sub3/../..', 'var/test')
True
>>> is_subdirectory('var/test/sub', 'var/test/fake_sub/..')
True
>>> is_subdirectory('var/test', 'var/test/sub')
False
"""
def _get_normalized_parts(path):
return os_path_split_asunder(os.path.realpath(os.path.abspath(os.path.normpath(path))))
# make absolute and handle symbolic links, split into components
sub_parts = _get_normalized_parts(potential_subdirectory)
parent_parts = _get_normalized_parts(expected_parent_directory)
if len(parent_parts) > len(sub_parts):
# a parent directory never has more path segments than its child
return False
# we expect the zip to end with the short path, which we know to be the parent
return all(part1==part2 for part1, part2 in zip(sub_parts, parent_parts))
回答by jgoeders
def is_subdir(path, directory):
path = os.path.realpath(path)
directory = os.path.realpath(directory)
relative = os.path.relpath(path, directory)
return not relative.startswith(os.pardir + os.sep)
回答by Evgeni Sergeev
Based on another answer here, with correction, and with a user-friendlier name:
基于此处的另一个答案,并进行更正,并使用用户友好的名称:
def isA_subdirOfB_orAisB(A, B):
"""It is assumed that A is a directory."""
relative = os.path.relpath(os.path.realpath(A),
os.path.realpath(B))
return not (relative == os.pardir
or relative.startswith(os.pardir + os.sep))
回答by jme
Python 3's pathlibmodule makes this straightforward with its Path.parentsattribute. For example:
Python 3 的pathlib模块通过其Path.parents属性使这变得简单。例如:
from pathlib import Path
root = Path('/path/to/root')
child = root / 'some' / 'child' / 'dir'
other = Path('/some/other/path')
Then:
然后:
>>> root in child.parents
True
>>> other in child.parents
False
回答by Tom Bull
Problems with many of the suggested methods
许多建议的方法存在问题
If you're going to test for directory parentage with string comparison or os.path.commonprefixmethods, these are prone to errors with similarly-named paths or relative paths. For example:
如果您要使用字符串比较或os.path.commonprefix方法来测试目录父系,则这些名称很容易出现名称相似的路径或相对路径的错误。例如:
/path/to/files/myfilewould be shown as a child path of/path/to/fileusing many of the methods./path/to/files/../../myfileswould not be shown as a parent of/path/myfiles/myfileby many of the methods. In fact, it is.
/path/to/files/myfile将显示为/path/to/file使用许多方法的子路径。/path/to/files/../../myfiles不会被/path/myfiles/myfile许多方法显示为的父级。事实上,确实如此。
The previous answerby Rob Dennis provides a good way to compare path parentage without encountering these problems. Python 3.4 added the pathlibmodule which can perform these kind of path operations in a more sophisticated way, optionally without referencing the underlying OS. jme has described in another previous answerhow to use pathlibfor the purpose of accurately determining if one path is a child of another. If you prefer not to use pathlib(not sure why, it's pretty great) then Python 3.5 introduced a new OS-based method in os.paththat allows you to do perform path parent-child checks in a similarly accurate and error-free manner with a lot less code.
Rob Dennis的先前答案提供了一种比较路径血统而不会遇到这些问题的好方法。Python 3.4 添加了pathlib可以以更复杂的方式执行此类路径操作的模块,可选择不引用底层操作系统。jme 在之前的另一个答案中描述了如何使用pathlib以准确确定一条路径是否是另一条路径的子路径。如果您不想使用pathlib(不知道为什么,它非常棒),那么 Python 3.5 引入了一种新的基于操作系统的方法,os.path它允许您以同样准确和无错误的方式执行路径父子检查,而更少代码。
New for Python 3.5
Python 3.5 的新功能
Python 3.5 introduced the function os.path.commonpath. This is a method that is specific to the OS that the code is running on. You can use commonpathin the following way to accurately determine path parentage:
Python 3.5 引入了该功能os.path.commonpath。这是一种特定于运行代码的操作系统的方法。您可以通过commonpath以下方式使用来准确确定路径出身:
def path_is_parent(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
Accurate one-liner
精准单线
You can combine the whole lot into a one-line if statement in Python 3.5. It's ugly, it includes unnecessary duplicate calls to os.path.abspathand it definitely won't fit in the PEP 8 79-character line-length guidelines, but if you like that kind of thing, here goes:
您可以在 Python 3.5 中将整批合并为一行 if 语句。它很丑陋,它包含不必要的重复调用os.path.abspath,它绝对不符合 PEP 8 79 字符行长指南,但如果你喜欢这种东西,这里是:
if os.path.commonpath([os.path.abspath(parent_path_to_test)]) == os.path.commonpath([os.path.abspath(parent_path_to_test), os.path.abspath(child_path_to_test)]):
# Yes, the child path is under the parent path
回答by Oliver
I like the "path in other_path.parents" approached mentioned in another answer because I'm a big fan of pathlib, BUT I feel that approach is a bit heavy (it creates one Path instance for each parent to root of path). Also the case where path == other_path will fail with that approach, whereas os.commonpath would succeed on that case.
我喜欢在另一个答案中提到的“other_path.parents 中的路径”,因为我是 pathlib 的忠实粉丝,但我觉得这种方法有点沉重(它为每个父级到路径的根创建了一个 Path 实例)。还有 path == other_path 会失败的情况,而 os.commonpath 会在这种情况下成功。
The following is a different approach, with its own set of pros and cons compared to other methods identified in the various answers:
以下是一种不同的方法,与各种答案中确定的其他方法相比,它有自己的优缺点:
try:
other_path.relative_to(path)
except ValueError:
...no common path...
else:
...common path...
which is a little more verbose but can easily be added as a function in your application's common utilities module or even add the method to Path at startup time.
这有点冗长,但可以轻松地作为函数添加到应用程序的通用实用程序模块中,甚至可以在启动时将方法添加到 Path。
回答by Juan A. Navarro
def is_in_directory(filepath, directory):
return os.path.realpath(filepath).startswith(
os.path.realpath(directory) + os.sep)
回答by Jacek B?ocki
I used below function for similar problem:
我使用以下功能解决类似问题:
def is_subdir(p1, p2):
"""returns true if p1 is p2 or its subdirectory"""
p1, p2 = os.path.realpath(p1), os.path.realpath(p2)
return p1 == p2 or p1.startswith(p2+os.sep)
After running into problems with symbolic link I've modified the function. Now it checks if both paths are directories.
在遇到符号链接问题后,我修改了该函数。现在它检查两个路径是否都是目录。
def is_subdir(p1, p2):
"""check if p1 is p2 or its subdirectory
:param str p1: subdirectory candidate
:param str p2: parent directory
:returns True if p1,p2 are directories and p1 is p2 or its subdirectory"""
if os.path.isdir(p1) and os.path.isdir(p2):
p1, p2 = os.path.realpath(p1), os.path.realpath(p2)
return p1 == p2 or p1.startswith(p2+os.sep)
else:
return False

