如何确定两个文件路径(或文件URL)是否在macOS上标识相同的文件或目录? [英] How to I determine whether two file paths (or file URLs) identify the same file or directory on macOS?

查看:88
本文介绍了如何确定两个文件路径(或文件URL)是否在macOS上标识相同的文件或目录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

想象一下在macOS上两个路径的简单示例:

Imagine this simple example of two paths on macOS:

  • /etc/hosts
  • /private/etc/hosts

两个都指向同一个文件.但是您如何确定呢?

Both point to the same file. But how do you determine that?

另一个例子:

  • 〜/Desktop
  • /Users/您的姓名/桌面

或者在大小写不敏感的文件系统上混合使用大写/小写字母:

Or what about upper / lower case mixes on a case-insensitive file system:

  • /卷/外部/我的文件
  • /卷/外部/我的文件

甚至是这个:

  • /Applications/Über.app

此处:Ü"可以以两种 unicode组成格式(NFD,NFC)指定.有关使用(NS)URL API时可能发生这种情况的示例,请参见我的要旨.

Here: The "Ü" can be specified in two unicode composition formats (NFD, NFC). For an example where this can happen when you use the (NS)URL API see this gist of mine.

自macOS 10.15(Catalina)起,另外还有公司链接在一个卷组中从一个卷到另一个卷的链接.相同FS对象的路径可以写为:

Since macOS 10.15 (Catalina) there are additionally firmlinks that link from one volume to another in a volume group. Paths for the same FS object could be written as:

  • /Applications/查找任何File.app
  • /系统/卷/数据/应用程序/查找任何File.app

我想记录能可靠地处理所有这些复杂情况的方法,以求高效(即快速).

I like to document ways that reliably deal with all these intricacies, with the goal of being efficient (i.e. fast).

推荐答案

有两种方法来检查两个路径(或其文件URL)是否指向同一文件系统项:

There are two ways to check if two paths (or their file URLs) point to the same file system item:

  • 比较他们的路径.这需要先准备路径.
  • 比较其ID(节点).这样总体上更安全,因为它避免了Unicode复杂性和错误情况下的所有并发症.

在ObjC中,这相当容易(注意:根据一位博学的Apple开发人员,不应依赖 [NSURL fileReferenceURL] ,因此此代码使用了更简洁的方式):

In ObjC this is fairly easy (note: Accordingly to a knowledgeable Apple developer one should not rely on [NSURL fileReferenceURL], so this code uses a cleaner way):

NSString *p1 = @"/etc/hosts";
NSString *p2 = @"/private/etc/hosts";
NSURL *url1 = [NSURL fileURLWithPath:p1];
NSURL *url2 = [NSURL fileURLWithPath:p2];

id ref1 = nil, ref2 = nil;
[url1 getResourceValue:&ref1 forKey:NSURLFileResourceIdentifierKey error:nil];
[url2 getResourceValue:&ref2 forKey:NSURLFileResourceIdentifierKey error:nil];

BOOL equal = [ref1 isEqual:ref2];

Swift中的等效项(请注意:请勿使用 fileReferenceURL ,请参见此错误报告):

The equivalent in Swift (note: do not use fileReferenceURL, see this bug report):

let p1 = "/etc/hosts"
let p2 = "/private/etc/hosts"
let url1 = URL(fileURLWithPath: p1)
let url2 = URL(fileURLWithPath: p2)

let ref1 = try url1.resourceValues(forKeys[.fileResourceIdentifierKey])
                   .fileResourceIdentifier
let ref2 = try url2.resourceValues(forKeys[.fileResourceIdentifierKey])
                   .fileResourceIdentifier
let equal = ref1?.isEqual(ref2) ?? false

这两种解决方案都在后台使用BSD函数 lstat ,因此您也可以在纯C语言中编写该代码:

Both solution use the BSD function lstat under the hood, so you could also write this in plain C:

static bool paths_are_equal (const char *p1, const char *p2) {
  struct stat stat1, stat2;
  int res1 = lstat (p1, &stat1);
  int res2 = lstat (p2, &stat2);
  return (res1 == 0 && res2 == 0) &&
         (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
}

但是,请注意警告有关使用此类文件引用的信息:

However, heed the warning about using these kind of file references:

此标识符的值在系统重新启动后并不持久.

The value of this identifier is not persistent across system restarts.

这主要是用于卷ID,但也可能会影响不支持持久文件ID的文件系统上的文件ID.

This is mainly meant for the volume ID, but may also affect the file ID on file systems that do not support persistent file IDs.

要比较路径,必须先获取其规范路径.

如果不执行此操作,则不能确定大小写是正确的,这反过来会导致非常复杂的比较代码.(有关详细信息,请参见使用 NSURLCanonicalPathKey .)

If you do not do this, you can not be sure that the case is correct, which in turn will lead to very complex comparison code. (See using NSURLCanonicalPathKey for details.)

有多种方法可以弄乱案件:

There are different ways how the case can be messed up:

  • 用户可能以错误的大小写手动输入了姓名.
  • 您之前已经存储了路径,但是与此同时,用户已经重命名了文件的大小写.您的路径仍然会标识相同的文件,但是现在情况是错误的,并且根据获取比较的其他路径的方式,相等路径的比较可能会失败.

仅当从文件系统操作获得路径时,您不能错误地指定路径的任何部分(即大小写错误),您无需获取规范路径,而只需调用 standardizingPath,然后比较它们的路径是否相等(不需要区分大小写的选项).

Only if you got the path from a file system operation where you could not specify any part of the path incorrectly (i.e. with the wrong case), you do not need to get the canonical path but can just call standardizingPath and then compare their paths for equality (no case-insensitive option necessary).

否则,为了安全起见,请从如下所示的URL获取规范路径:

Otherwise, and to be on the safe side, get the canonical path from a URL like this:

import Foundation

let uncleanPath = "/applications"
let url = URL(fileURLWithPath: uncleanPath)

if let resourceValues = try? url.resourceValues(forKeys: [.canonicalPathKey]),
    let resolvedPath = resourceValues.canonicalPath {
        print(resolvedPath) // gives "/Applications"
    }

如果路径存储在字符串而不是URL对象中,则可以调用 stringByStandardizingPath ( Apple文档).但这既不能解决错误的情况,也不能分解字符,这可能会导致出现问题,如上述要点.

If your path is stored in an String instead of a URL object, you could call stringByStandardizingPath (Apple Docs). But that would neither resolve incorrect case nor would it decompose the characters, which may cause problems as shown in the aforementioned gist.

因此,从字符串创建文件URL,然后使用上述方法获取规范路径是更安全的方法,甚至更好的方法是,使用lstat()解决方案比较文件ID,如上所示.

Therefore, it's safer to create a file URL from the String and then use the above method to get the canonical path or, even better, use the lstat() solution to compare the file IDs as shown above.

还有一个BSD函数,可从C字符串获取规范路径: realpath().但是,这是不安全的,因为它无法将卷组中不同路径的情况(如问题所示)解析为相同的字符串.因此,为此目的应避免使用此功能.

There's also a BSD function to get the canonical path from a C string: realpath(). However, this is not safe because it does not resolve the case of different paths in a volume group (as shown in the question) to the same string. Therefore, this function should be avoided for this purpose.

这篇关于如何确定两个文件路径(或文件URL)是否在macOS上标识相同的文件或目录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆