NSTask not picking up $PATH from the user's environment
Asked Answered
O

5

13

I don't know why this method returns a blank string:

- (NSString *)installedGitLocation {
    NSString *launchPath = @"/usr/bin/which";

    // Set up the task
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:launchPath];
    NSArray *args = [NSArray arrayWithObject:@"git"];
    [task setArguments:args];

    // Set the output pipe.
    NSPipe *outPipe = [[NSPipe alloc] init];
    [task setStandardOutput:outPipe];

    [task launch];

    NSData *data = [[outPipe fileHandleForReading] readDataToEndOfFile];
    NSString *path = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

    return path;
}

If instead of passing @"git" as the argument, I pass @"which" I get /usr/bin/which returned as expected. So at least the principle works.

from the terminal

$ which which
$ /usr/bin/which
$
$ which git
$ /usr/local/git/bin/git

So it works there.

The only thing I can think of is that which isn't searching through all the paths in my environment.

This is driving me crazy! Does anyone have any ideas?

EDIT: It looks like this is about setting up either NSTask or the user's shell (e.g., ~/.bashrc) so that the correct environment ($PATH) is seen by NSTask.

Oribella answered 22/12, 2008 at 17:18 Comment(1)
I'm writing a CLI app, and I've found NSTask sees the $PATH when you run your program from the CLI. Unfortunately, you must compile and run on the command line as well, because testing the program in Xcode brings us to this situation.Compony
L
13

Running a task via NSTask uses fork() and exec() to actually run the task. The user's interactive shell isn't involved at all. Since $PATH is (by and large) a shell concept, it doesn't apply when you're talking about running processes in some other fashion.

Load answered 23/12, 2008 at 2:17 Comment(3)
More to the point, if the user sets his PATH using one of his shell's run-commands files, the app does not receive that variable because it wasn't run from the shell; as such, the subprocess doesn't inherit it because it's the process's direct child. (cont'd because SO limits length for some reason)Maieutic
But if the user sets PATH in ~/.MacOSX/environment.plist, the application does receive the PATH variable, and the subprocess will inherit it.Maieutic
In other words, the environment (where environments such as PATH are stored) used for executing applications is not the same as the environment used for executing shells (bash, etc.). Mac OS X maintains two separate environments - one for shells, one for applications.Roxie
T
22

Try,

    [task setLaunchPath:@"/bin/bash"];
    NSArray *args = [NSArray arrayWithObjects:@"-l",
                     @"-c",
                     @"which git",
                     nil];
    [task setArguments: args];

This worked for me on Snow Leopard; I haven't tested on any other system. The -l (lowercase L) tells bash to "act as if it had been invoked as a login shell", and in the process it picked up my normal $PATH. This did not work for me if the launch path was set to /bit/sh, even with -l.

Tanney answered 30/11, 2009 at 4:39 Comment(2)
That assumes that the user uses bash, or that they use another Bourne-like shell and they set PATH in their .profile. Anyone who uses tcsh and anyone who uses ksh, zsh, or fish and didn't set PATH in .profile (e.g., they set it in .zshenv instead) is screwed.Maieutic
See this answer on another question, and my comment on it, for the full version of this solution: #209397 It's certainly not simple.Maieutic
L
13

Running a task via NSTask uses fork() and exec() to actually run the task. The user's interactive shell isn't involved at all. Since $PATH is (by and large) a shell concept, it doesn't apply when you're talking about running processes in some other fashion.

Load answered 23/12, 2008 at 2:17 Comment(3)
More to the point, if the user sets his PATH using one of his shell's run-commands files, the app does not receive that variable because it wasn't run from the shell; as such, the subprocess doesn't inherit it because it's the process's direct child. (cont'd because SO limits length for some reason)Maieutic
But if the user sets PATH in ~/.MacOSX/environment.plist, the application does receive the PATH variable, and the subprocess will inherit it.Maieutic
In other words, the environment (where environments such as PATH are stored) used for executing applications is not the same as the environment used for executing shells (bash, etc.). Mac OS X maintains two separate environments - one for shells, one for applications.Roxie
T
2

Is /usr/local/git/bin in your $PATH when you run the program? I think which only looks in the user's $PATH.

Torry answered 22/12, 2008 at 17:29 Comment(3)
from logging NSProcessInfo in the method, my Path comes up as PATH = "/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"; which isn't my full shell path. How would I get the user's path so that I could set it in the environment?Oribella
I edited the question to reflect this issue. +1 for pointing toward the problem.Flannel
Since $PATH can be set in multiple places including ~/.profile ~/.bashrc etc, the easiest solution would probably be the technique outlined in Brian Webseter's link. Something like "/bin/bash -c env | grep ^PATH"Torry
G
1

Take a look at the question Find out location of an executable file in Cocoa. It looks like the basic problem is the same. The answer unfortunately isn't nice and neat, but there's some useful info there.

Gregale answered 22/12, 2008 at 18:48 Comment(0)
P
0

In Swift NSTask is replaced by Process but this is what it works for me:

let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-l", "-c", "which git"]
Pagepageant answered 12/11, 2019 at 23:48 Comment(1)
It’s probably a better idea to try and obtain the shell from ProcessInfo.processInfo.environment[“SHELL”].Ruching

© 2022 - 2024 — McMap. All rights reserved.