如何检查给定路径是否可能是另一条路径的子路径?

时间:2011-01-20 11:42:56

标签: java file-io

我试图找到给定路径是否可能是使用java的另一个路径的子路径。这两条路径可能都不存在。

c:\Program Files\My Company\test\My App可能是c:\Program Files的孩子。

目前我正在使用

执行此操作
boolean myCheck(File maybeChild, File possibleParent)
{
    return maybeChild.getAbsolutePath().startsWith( possibleParent.getAbsolutePath());
}

10 个答案:

答案 0 :(得分:50)

您还可以使用 java.nio.file.Path 来更轻松地完成此操作。 java.nio.file.Path.startsWith 方法似乎可以处理所有可能的情况。

示例:

private static void isChild(Path child, String parentText) {
    Path parent = Paths.get(parentText).toAbsolutePath();
    System.out.println(parentText + " = " + child.startsWith(parent));
}

public static void main(String[] args) {
    Path child = Paths.get("/FolderA/FolderB/File").toAbsolutePath();
    isChild(child, "/FolderA/FolderB/File");
    isChild(child, "/FolderA/FolderB/F");
    isChild(child, "/FolderA/FolderB");
    isChild(child, "/FolderA/Folder");
    isChild(child, "/FolderA");
    isChild(child, "/Folder");
    isChild(child, "/");
    isChild(child, "");
}

输出

/FolderA/FolderB/File = true
/FolderA/FolderB/F = false
/FolderA/FolderB = true
/FolderA/Folder = false
/FolderA = true
/Folder = false
/ = true
 = false

如果您需要更高的可靠性,可以使用“toRealPath”而不是“toAbsolutePath”。

答案 1 :(得分:12)

File parent = maybeChild.getParentFile();
while ( parent != null ) {
  if ( parent.equals( possibleParent ) )
    return true;
  parent = parent.getParentFile();
}
return false;

答案 2 :(得分:11)

除了路径可能不存在(并且规范化可能不成功)之外,这看起来像一个合理的方法,应该在简单的情况下起作用。

您可能希望在循环中查看“可能的孩子”上的getParentFile(),测试它是否与每一步中的父项匹配。如果父级不是(真实)目录,您也可以将比较短路。

可能类似以下内容:

boolean myCheck(File maybeChild, File possibleParent) throws IOException
{
    final File parent = possibleParent.getCanonicalFile();
    if (!parent.exists() || !parent.isDirectory()) {
        // this cannot possibly be the parent
        return false;
    }

    File child = maybeChild.getCanonicalFile();
    while (child != null) {
        if (child.equals(parent)) {
            return true;
        }
        child = child.getParentFile();
    }
    // No match found, and we've hit the root directory
    return false;
}

请注意,如果您希望子关系 strict (即目录不是其自身的子目录),您可以将第9行的初始child分配更改为{{ 1}}所以第一次检查发生在孩子的包含目录上。

答案 3 :(得分:7)

这适用于您的示例。如果孩子是相对路径,它也将返回true (这通常是可取的。)

boolean myCheck(File maybeChild, File possibleParent)
{
    URI parentURI = possibleParent.toURI();
    URI childURI = maybeChild.toURI();
    return !parentURI.relativize(childURI).isAbsolute();
}

答案 4 :(得分:4)

虽然我会使用getCanonicalPath()而不是getAbsolutePath(),但它可能会正常工作。这应该规范化任何奇怪的路径,如x/../y/z,否则会搞砸匹配。

答案 5 :(得分:2)

maybeChild.getCanonicalPath().startsWith( possibleParent.getCanonicalPath() );

答案 6 :(得分:1)

注意相对路径!我认为最简单的解决方案是这样的:

public boolean myCheck(File maybeChild, File possibleParent) {
  if (requestedFile.isAbsolute) {
    return possibleParent.resolve(maybeChild).normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
  } else {
    return maybeChild.normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
  }
}

在scala中,您可以采用类似的方法:

val baseDir = Paths.get("/home/luvar/tmp")
val baseDirF = baseDir.toFile
//val requestedFile = Paths.get("file1")
val requestedFile = Paths.get("../.viminfo")
val fileToBeRead = if (requestedFile.isAbsolute) {
  requestedFile
} else {
  baseDir.resolve(requestedFile)
}
fileToBeRead.toAbsolutePath
baseDir.toAbsolutePath
fileToBeRead.normalize()
baseDir.normalize()
val isSubpath = fileToBeRead.normalize().toAbsolutePath.startsWith(baseDir.normalize().toAbsolutePath)

答案 7 :(得分:0)

旧问题,但1.7之前的解决方案:

public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
        String[] possiblePath = new File(possibleRoot).getAbsolutePath().replace('\\', '/').split("/");
        String[] possibleChildOrSamePath = new File(possibleChildOrSame).getAbsolutePath().replace('\\', '/').split("/");

        if (possibleChildOrSamePath.length < possiblePath.length) {
            return false;
        }

        // not ignoring case
        for (int i = 0; i < possiblePath.length; i++) {
            if (!possiblePath[i].equals(possibleChildOrSamePath[i])) {
                return false;
            }
        }
        return true;
}

为了完整性,java 1.7+解决方案:

public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
        Path p1 = Paths.get(possibleChildOrSame).toAbsolutePath();
        Path p2 = Paths.get(possibleRoot).toAbsolutePath();
        return p1.startsWith(p2);
}

答案 8 :(得分:0)

令人惊讶的是,没有简单但实用的解决方案。

接受的答案确实认为与子目录相同,这是错误的。

以下是仅使用 java.nio.file.Path API的一种:

static boolean isChildPath(Path parent, Path child){
      Path pn = parent.normalize();
      Path cn = child.normalize();
      return cn.getNameCount() > pn.getNameCount() && cn.startsWith(pn);
}

测试用例:

 @Test
public void testChildPath() {
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A"))).isTrue();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A.txt"))).isTrue();

      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/../A"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/FA"))).isFalse();

      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA"))).isFalse();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderAB"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/Z/X/../A"))).isTrue();
}

答案 9 :(得分:0)

在测试路径是否相等时,应考虑以下注意事项:

  1. 文件系统区分大小写。唯一可以处理区分大小写的文件系统的 API 是 NIO.2 (1.7+),这就是 java.io.FileString 都不能使用的原因。
  2. 单独的路径条目处理:C:\abc 不是 C:\abcd 的祖先,甚至也不是 String.startsWith() 的直接父,因此无法使用 C:\Program Files API。
  3. Windows 上,C:\PROGRA~1Files.isSameFile() 是同一目录,Path.startsWith()(来自 NIO.2)是唯一可以处理此权限的 API。这是 Files.isSameFile() 方法不支持的。
  4. 符号链接友好性(我的回答未完全涵盖,因为实际要求可能有所不同)。对于目录符号链接,C:\Documents and Settings 在某种程度上支持这种,因此C:\Users\Public 确实是Path.startsWith() 的祖先。同样,这也是自定义代码比 boolean isAncestorOf(final Path parent, final Path child) { final Path absoluteParent = parent.toAbsolutePath().normalize(); final Path absoluteChild = child.toAbsolutePath().normalize(); if (absoluteParent.getNameCount() >= absoluteChild.getNameCount()) { return false; } final Path immediateParent = absoluteChild.getParent(); if (immediateParent == null) { return false; } return isSameFileAs(absoluteParent, immediateParent) || isAncestorOf(absoluteParent, immediateParent); } boolean isSameFileAs(final Path path, final Path path2) { try { return Files.isSameFile(path, path2); } catch (final IOException ioe) { return path.toAbsolutePath().normalize().equals(path2.toAbsolutePath().normalize()); } } API(请参阅 this most-voted answer)稍微好一点的地方。

综上所述,解决方案可能是这样的。爪哇:

fun Path.isAncestorOf(child: Path): Boolean {
  val absoluteParent = toAbsolutePath().normalize()
  val absoluteChild = child.toAbsolutePath().normalize()

  if (absoluteParent.nameCount >= absoluteChild.nameCount) {
    return false
  }

  val immediateParent = absoluteChild.parent
                        ?: return false

  return absoluteParent.isSameFileAs(immediateParent) || absoluteParent.isAncestorOf(immediateParent)
}

fun Path.isSameFileAs(that: Path): Boolean =
  try {
    Files.isSameFile(this, that)
  }
  catch (_: NoSuchFileException) {
    toAbsolutePath().normalize() == that.toAbsolutePath().normalize()
  }

科特林:

#!groovy

def label = "debug-${UUID.randomUUID().toString()}"

podTemplate(label: label, slaveConnectTimeout: '10', containers: [
        containerTemplate(
                name: 'docker-in-docker',
                image: 'cfzen/dind:java11',
                privileged: true,
                workingDir: '/home/jenkins/agent',
                ttyEnabled: true,
                command: 'cat',
          envVars: [
            envVar(key: 'TESTCONTAINERS_HOST_OVERRIDE', value: 'tcp://localhost:2375'),
            envVar(key: 'TESTCONTAINERS_RYUK_DISABLED', value: 'true'),
          ]
        ),
        containerTemplate(
                name: 'helm-kubectl',
                image: 'dtzar/helm-kubectl',
                workingDir: '/home/jenkins/agent/',
                ttyEnabled: true,
                command: 'cat'
        )
],
        volumes: [hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),],
        annotations: [
                podAnnotation(key: 'iam.amazonaws.com/role',
                        value: 'arn:aws:iam::xxxxxxxxxxx')
        ],
)
        {
            node(label) {

                deleteDir()

                stage('Checkout') {
                    checkout scm
                    def shortCommit = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
                    currentBuild.description = "${shortCommit}"
                }


              stage('Run Integration tests') {
                container('docker-in-docker') {
                  withCredentials([
                    usernamePassword(credentialsId: 'jenkins-artifactory-credentials',
                      passwordVariable: 'ARTIFACTORY_SERVER_PASSWORD',
                      usernameVariable: 'ARTIFACTORY_SERVER_USERNAME')])
                    {
                  echo 'Run Integration tests'
                  sh("mvn -B clean verify -q -s  mvn/local-settings.xml")
                }
            }
        }