找出Cocoa中可执行文件的位置

时间:2008-10-16 14:55:28

标签: cocoa macos

简单的问题是:如何在Cocoa应用程序中找到可执行文件的位置。

请记住,在许多类Unix操作系统中,人们使用PATH环境为其可执行文件分配首选位置,尤其是当他们的系统中有多个版本的相同应用程序时。作为一种好的做法,我们的Cocoa应用程序应该找到它所需的可执行文件的PREFERRED位置。

例如,/ usr / bin中的Leopard默认配置中有一个SVN 1.4,你通过/ opt / local / bin上的MacPorts安装了一个更新的版本,比如SVN 1.5.3。你可以使用/etc/path.d或.bash_profile或.zshrc设置你的PATH:

export PATH = / opt / local / bin:$ PATH

因此,您可以使用新版本的svn而不是系统中的旧版本。它适用于任何终端环境。但不是在Cocoa应用程序中。据我所知,Cocoa应用程序只有一个默认的PATH环境:

export PATH =“/ usr / bin:/ bin:/ usr / sbin:/ sbin”

默认情况下,它不会使用/etc/path.d,.bash_profile,.profile,.zshrc等中的配置。

那我们究竟该怎么办?

P.S。我们有a semi-solution here,但它无法完全满足此问题的目标。

5 个答案:

答案 0 :(得分:9)

这是试图做这个棘手的部分是,用户可以有自己的shell设置任何东西:SH,庆典,CSH,tcsh的,等等,并且每个壳不同设置其终端环境。我不确定自己是否会为此而烦恼,但如果你想要,那么这就是我要采取的路线。

第一步是找出用户的shell。在OS X上,此信息存储在目录服务中,可以通过DirectoryService.framework中的API或使用dscl命令行工具来加入。 DirectoryService API是一个王室的痛苦,所以我可能会去CLI路线。在Cocoa中,您可以使用NSTask来执行带有参数的工具来获取用户的shell(我会在其他地方留下详细信息)。该命令看起来像:

dscl -plist localhost -read /Local/Default/Users/username UserShell

这将返回您可以解释为plist并转换为NSDictionary的XML文本,或者您可以省略-plist选项并自行解析文本输出。

一旦知道了用户shell的路径,下一步就是执行该shell并告诉它运行env命令打印出用户的环境。看起来大多数shell接受一个-c命令行选项,允许你传入一个字符串来执行 - 我想你只需要假设它是用户选择的任何shell的通用接口。

一旦拥有了用户的环境,您就可以从中获取其路径列表,并搜索您正在寻找的任何可执行文件。就像我说的那样,我真的不知道这是否值得这么麻烦,但如果我实施这个话,那就是我要去的方向。

答案 1 :(得分:8)

与Brian Webster的回答相关:

获取用户shell的更简单方法是使用NSProcessInfo类。 e.g

NSDictionary *environmentDict = [[NSProcessInfo processInfo] environment];
NSString *shellString = [environmentDict objectForKey:@"SHELL"];

这比使用dscl和解析XML输入更容易。

答案 2 :(得分:6)

这是基于上述答案的实现,可以从applicationDidFinishLaunching调用:

// from http://cocoawithlove.com/2009/05/invoking-other-processes-in-cocoa.html
#import "NSTask+OneLineTasksWithOutput.h"

void FixUnixPath() {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
        NSString *userShell = [[[NSProcessInfo processInfo] environment] objectForKey:@"SHELL"];
        NSLog(@"User's shell is %@", userShell);

        // avoid executing stuff like /sbin/nologin as a shell
        BOOL isValidShell = NO;
        for (NSString *validShell in [[NSString stringWithContentsOfFile:@"/etc/shells" encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) {
            if ([[validShell stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:userShell]) {
                isValidShell = YES;
                break;
            }
        }

        if (!isValidShell) {
            NSLog(@"Shell %@ is not in /etc/shells, won't continue.", userShell);
            return;
        }
        NSString *userPath = [[NSTask stringByLaunchingPath:userShell withArguments:[NSArray arrayWithObjects:@"-c", @"echo $PATH", nil] error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        if (userPath.length > 0 && [userPath rangeOfString:@":"].length > 0 && [userPath rangeOfString:@"/usr/bin"].length > 0) {
            // BINGO!
            NSLog(@"User's PATH as reported by %@ is %@", userShell, userPath);
            setenv("PATH", [userPath fileSystemRepresentation], 1);
        }
    });
}

P.S。这样做的原因是因为它捕获了shell所做的环境变化。例如。 RVM在安装时将PATH=$PATH:$HOME/.rvm/bin添加到.bashrc。 Cocoa应用程序是从launchd启动的,因此他们的PATH中没有这些更改。

我对这段代码并不是百分之百满意,因为它并没有抓住所有内容。我最初的意图是专门处理RVM,所以我不得不在这里使用非登录shell,但实际上,人们随机地将PATH修改放入.bashrc和.bash_profile中,所以最好同时运行它们。

我的一个用户甚至在他的shell配置文件中有一个交互式菜单(!!!),这自然会导致此代码挂起,我只为他导出一个shell env标志。 :-)添加超时可能是一个好主意。

这也假设shell是bourne兼容的,因此不适用于fish 2.0,它在黑客社区中越来越受欢迎。 (Fish认为$ PATH是一个数组,而不是冒号分隔的字符串。因此它默认使用空格作为分隔符打印它。一个人可以做一个简单的修复,比如运行for i in $PATH; echo "PATH=$i"; end然后只采取行从PATH=开始。在任何情况下过滤都是个好主意,因为配置文件脚本经常会自行打印。)

最后需要注意的是,这段代码已经成为一年多来发货应用程序的重要组成部分(全年大部分时间都是Mac App Store中排名前10位的付费开发者工具)。但是,我现在正在实施沙盒并将其取出;当然,你不能从沙盒应用程序中做到这一点。我将其替换为对RVM和朋友的明确支持,并手动再现他们各自的环境变化。

对于那些希望从沙盒应用程序中使用类似系统Git的人,请注意,虽然您无权访问读取文件和枚举目录,但您可以访问stat - [[NSFileManager defaultManager] fileExistsAtPath:path]。您可以使用它来探测寻找二进制文件的典型文件夹的硬编码列表,当您找到位置(例如/ usr / local或/ opt / local或其他)时,请让用户通过NSOpenPanel授予您访问权限。这不会抓住每个案例,但会处理90%的用例,这是您可以为开箱即用的用户做的最好的事情。

答案 3 :(得分:2)

您的用户有多大可能拥有您正在使用的工具的自定义版本(以及您的应用与该工具的任意版本兼容的可能性有多大)?如果答案“不是很”,那么请考虑默认使用系统提供的工具路径,并为高级用户提供一种指定自己路径作为首选项的方法。

答案 4 :(得分:1)

是不是从您的登录shell设置Finder的路径(因此,任何GUI启动的Cocoa应用程序)?如果你的登录shell和你在Terminal.app中使用的shell不一样,那可能会引起一些混乱。

此信息可能会有所帮助: http://lists.apple.com/archives/cocoa-dev/2005/Oct/msg00528.html

显然,为GUI进程设置环境变量的“正确”方法是隐藏的.plist文件。我确信我一度都知道这件事,然后很快忘记了。

相关问题