我该如何在Objective-C中解析相对路径?

5
我正在尝试下面的代码,以便扩展这些位置的绝对路径,以便我可以将它们用于使用NSFileManager进行的操作,但是在使用波浪线和相对路径时失败。
我正在使用Objective-C在Xcode中开发命令行应用程序。我可以从命令行运行程序并为我扩展路径,但是从Xcode中的目标中,我正在使用$PROJECT_DIR和$HOME传递命令行参数的值来获取部分内容。问题是我需要到达$PROJECT_DIR/..,但它在NSFilemanager中无法解决。
似乎URLByResolvingSymlinksInPath或URLByStandardizingPath不起作用。我应该做些什么?
BOOL isDir = TRUE;
for (NSString *path in @[@"~/", @".", @".."]) {
    NSURL *url = [[[NSURL URLWithString:path] URLByResolvingSymlinksInPath] URLByStandardizingPath];
    DebugLog(@"path: %@", url.absoluteString);
    DebugLog(@"Exists: %@", [[NSFileManager defaultManager] fileExistsAtPath:url.path isDirectory:&isDir] ? @"YES" : @"NO");
}

更新:我正在使用stdlib中的realpath来解析路径,并创建了以下方法,尽管我不理解这个C函数。具体来说,我不知道解析出的值是什么,以及我如何使用它。但我确实看到了期望的返回值。
- (NSString *)resolvePath:(NSString *)path {
    NSString *expandedPath = [[path stringByExpandingTildeInPath] stringByStandardizingPath];
    const char *cpath = [expandedPath cStringUsingEncoding:NSUTF8StringEncoding];
    char *resolved = NULL;
    char *returnValue = realpath(cpath, resolved);

//    DebugLog(@"resolved: %s", resolved);
//    DebugLog(@"returnValue: %s", returnValue);

    return [NSString stringWithCString:returnValue encoding:NSUTF8StringEncoding];
}

1
你不能只是使用NSBundle吗? - Ramy Al Zuhouri
这是针对在Mac文件系统上处理文件的命令行程序。在这种情况下,我不会触及NSBundle。 - Brennan
在看到一些初始回复后,我应该注意到,在这个Xcode项目中,我已经设置了运行命令行程序的参数,并将工作目录设置为项目所在位置。这样就设置了当前工作目录,我可以可靠地在具有源代码控制的文件中运行这个程序,以进行测试。目前看来,我似乎已经得到了我需要的东西,尽管我不理解realpath函数的解析值。 - Brennan
当您在真实的 shell 中运行代码而不是使用 Xcode 的调试器时,即使用户键入了相对路径,在 argv 中始终会给出绝对路径。我建议您更新调试参数以使用完整路径(例如 /Users/brennan/file.txt)。 - Abhi Beckert
请注意,调用realpath会创建一个泄漏,因为传递NULL将分配一个大小为PATH_MAX字节的数组来存储结果。 - OzB
3个回答

10
在您的示例路径中,只有~/是绝对路径,因此只有~/可能被转换为绝对URL。但遗憾的是,NSURL根本不解析波浪号(~)。您必须使用-[NSString stringByStandardizingPath]-[NSString stringByExpandingTildeInPath]来展开波浪号。无法将.转换为绝对URL,而不指定其相对的URL。 NSURL不会假设您要使用进程的当前目录,您必须明确表达。由于同样的原因,也不可能解析..。尽管如此,somename/..中不可能解析..,因为somename可能最终成为符号链接,跟随符号链接后的..可能会将您带到与包含somename的目录不同的目录中。不幸的是,NSURL文档没有提到这些限制,NSString文档则有。您可以使用-[NSFileManager currentDirectoryPath]获取当前目录,将其传递给+[NSURL fileURLWithPath:],并将结果(当前目录的绝对file URL)传递给+[NSURL URLWithString:relativeToURL:]以解析...和符号链接。

请看我上面的评论。我正在使用stdlib中的realpath函数,并且CWD是从Xcode中的方案设置的,该方案用于测试目的运行程序。我能够以这种方式获得预期的路径,但是在Objective-C级别上使用NSURL或NSString找到的任何方法都无法实现。也许NSFileManager有一些有用的东西。 - Brennan

2
以下是我的解决方案,它使用了一个底层的C函数,虽然我一开始试图避免使用它,但它对我的目的很有效。在下面的Objective-C方法中创建了这个方法。完整的项目可以在GitHub上获得。 https://github.com/brennanMKE/Xcode4CodeSnippets/tree/master/SnippetImporter
- (NSString *)resolvePath:(NSString *)path {
    NSString *expandedPath = [[path stringByExpandingTildeInPath] stringByStandardizingPath];
    const char *cpath = [expandedPath cStringUsingEncoding:NSUTF8StringEncoding];
    char *resolved = NULL;
    char *returnValue = realpath(cpath, resolved);

    if (returnValue == NULL && resolved != NULL) {
        printf("Error with path: %s\n", resolved);
        // if there is an error then resolved is set with the path which caused the issue
        // returning nil will prevent further action on this path
        return nil;
    }

    return [NSString stringWithCString:returnValue encoding:NSUTF8StringEncoding];
}

2
调用stringByExpandingTildeInPath是不必要的。这正是stringByStandardizingPath所做的第一件事情。 - Bob Whiteman
2
你的代码存在内存泄漏问题。realpath文档中指出,如果第二个参数传入NULL,则会动态分配所需的内存并将指针作为返回值返回。你需要在构建最终的NSString后调用free释放内存,或者传入一个大小为MAX_PATH的缓冲区作为第二个参数。char resolved[PATH_MAX]; if (realpath([[path stringByStandardizingPath] UTF8String], resolved) == nullptr) return path; return [NSString stringWithCString:resolved encoding:NSUTF8StringEncoding]; - Max Rahm

2

你的示例路径分为两组:波浪线路径和“点”路径。

对于波浪线路径,你必须在代码中展开它们,文件系统不识别波浪线,但是它是命令行解释器(也称为“shell”,CLI)引入的一种简写。要进行扩展,可以使用stringByExpandingTildeInPath等方法。

“点”路径不同,“.”和“..”目录条目作为文件系统的一部分存在,并且包含它们的路径有效。

然而,以“.”或“..”开头的路径将被视为相对于当前工作目录(CWD)。虽然对于 CLI 来说 CWD 很明显,但对 GUI 应用程序而言可能不太明显 - 这意味着尽管这些路径有效,但在启动 GUI 应用程序后它们可能不会引用您预期的内容。但是,你可以设置 CWD,请参见 changeCurrentDirectoryPath:,在此之后,以“.”或“..”开头的路径应该引用您期望的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接