简单的问题是:如何在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等中的配置.
那我们究竟怎么办?
ps我们在这里有一个半解决方案,但它不能完全满足这个问题的目标.
尝试执行此操作的棘手部分是,用户可以将其shell设置为任何内容:sh,bash,csh,tcsh等,并且每个shell以不同方式设置其终端环境.我不确定自己是否会为此自行解决问题,但如果你真的想这样做,这就是我要采取的路线.
第一步是找出用户的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的通用接口.
一旦你拥有了用户的环境,你就可以从中获取它们的路径列表,然后搜索你正在寻找的任何可执行文件.就像我说的那样,我真的不知道这是否值得这么麻烦,但如果我实施这个,那就是我要去的方向.
与Brian Webster的回答有关:
获取User的shell的一种更简单的方法是使用NSProcessInfo类.例如
NSDictionary *environmentDict = [[NSProcessInfo processInfo] environment]; NSString *shellString = [environmentDict objectForKey:@"SHELL"];
这比使用dscl和解析XML输入更容易.
这是我基于上述答案的实现,可以从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); } }); }
PS这个工作的原因是因为它捕获了shell所做的环境变化.例如,RVM PATH=$PATH:$HOME/.rvm/bin
在安装时添加到.bashrc.Cocoa应用程序是从launchd启动的,因此它们的PATH中没有这些更改.
我对这段代码并不是百分之百满意,因为它并没有抓住所有东西.我最初的意图是专门处理RVM,所以我不得不在这里使用非登录shell,但实际上,人们随机地将PATH修改放入.bashrc和.bash_profile中,因此最好同时运行它们.
我的一个用户甚至在他的shell配置文件中有一个交互式菜单(!!!),这自然导致这个代码挂起,我只为他导出一个shell env标志.:-)添加超时可能是一个好主意.
这也假设shell是bourne兼容的,因此不适用于鱼2.0,它在黑客社区中越来越受欢迎.(Fish认为$ PATH是一个数组,而不是冒号分隔的字符串.因此它默认使用空格作为分隔符打印它.可以做一个简单的修复,比如运行for i in $PATH; echo "PATH=$i"; end
然后只采用开头的行PATH=
.过滤是在任何情况下都是个好主意,因为配置文件脚本经常会自行打印.)
最后需要注意的是,这段代码已经成为一年多来发货应用程序的重要组成部分(一年中大部分时间都在Mac App Store上排名前10位的付费开发者工具).但是,我现在正在实施沙盒并将其取出; 当然,你不能从沙盒应用程序中做到这一点.我正在替换它,明确支持RVM和朋友,并手动重现他们各自的env更改.
对于那些希望从沙盒应用程序中使用类似系统Git的人,请注意,虽然您无权访问读取文件和枚举目录,但您可以访问stat - [[NSFileManager defaultManager] fileExistsAtPath:path]
.您可以使用它来探测寻找二进制文件的典型文件夹的硬编码列表,当您找到位置(例如/ usr/local或/ opt/local或其他)时,请让用户通过NSOpenPanel授予您访问权限.这不会抓住每个案例,但会处理90%的用例,这是您可以为开箱即用的用户做的最好的事情.