这是我基于上述答案的实现,从 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);
}
});
}
附:之所以有效,是因为它捕获了 shell 所做的环境更改。例如。 RVM 在安装时将PATH=$PATH:$HOME/.rvm/bin 添加到 .bashrc。 Cocoa 应用程序是从 launchd 启动的,因此它们的 PATH 中没有这些更改。
我对这段代码不是 100% 满意,因为它不能捕获所有内容。我的初衷是专门处理 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% 的用例,并且是开箱即用您可以为用户做的最好的事情。