Question

I want to use NSTask to simulate the Terminal to run commands. The codes as follows. It can get input in loop and return the process output.

int main(int argc, const char * argv[])
{
  @autoreleasepool {      
    while (1) {
        char str[80] = {0};
        scanf("%s", str);
        NSString *cmdstr = [NSString stringWithUTF8String:str];

        NSTask *task = [NSTask new];
        [task setLaunchPath:@"/bin/sh"];
        [task setArguments:[NSArray arrayWithObjects:@"-c", cmdstr, nil]];

        NSPipe *pipe = [NSPipe pipe];
        [task setStandardOutput:pipe];

        [task launch];

        NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];

        [task waitUntilExit];

        NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", string);

    }
}

My question is: when a loop is end, the running environment restore to the initialization state. For example, the default running path is /Users/apple, and I run cd / to change the path to /, and then run pwd, it return the /Users/apple rather than the /.

So how can I use NSTask to simulate the Terminal completely ?

Was it helpful?

Solution

cd and pwd are shell built-in commands. If you execute the task

/bin/sh -c "cd /"

there is no way of getting the changed working directory back to the calling process. The same problem exists if you want to set variables MYVAR=myvalue.

You could try to parse these lines separately and update the environment. But what about multi-line commands like

for file in *.txt
do
    echo $file
done

You cannot emulate that by sending each line to separate NSTask processes.

The only thing you could do is to start a single /bin/sh process with NSTask, and feed all the input lines to the standard input of that process. But then you can not use readDataToEndOfFile to read the output, but you have to read asynchronously (using [[pipe fileHandleForReading] waitForDataInBackgroundAndNotify]).

So in short: you can simulate the Terminal only by running a (single) shell.

ADDED: Perhaps you can use the following as a starting point for your app. (I have omitted all error checking.)

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        // Commands are read from standard input:
        NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];

        NSPipe *inPipe = [NSPipe new]; // pipe for shell input
        NSPipe *outPipe = [NSPipe new]; // pipe for shell output

        NSTask *task = [NSTask new];
        [task setLaunchPath:@"/bin/sh"];
        [task setStandardInput:inPipe];
        [task setStandardOutput:outPipe];
        [task launch];

        // Wait for standard input ...
        [input waitForDataInBackgroundAndNotify];
        // ... and wait for shell output.
        [[outPipe fileHandleForReading] waitForDataInBackgroundAndNotify];

        // Wait asynchronously for standard input.
        // The block is executed as soon as some data is available on standard input.
        [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                          object:input queue:nil
                                                      usingBlock:^(NSNotification *note)
         {
             NSData *inData = [input availableData];
             if ([inData length] == 0) {
                 // EOF on standard input.
                 [[inPipe fileHandleForWriting] closeFile];
             } else {
                 // Read from standard input and write to shell input pipe.
                 [[inPipe fileHandleForWriting] writeData:inData];

                 // Continue waiting for standard input.
                 [input waitForDataInBackgroundAndNotify];
             }
         }];

        // Wait asynchronously for shell output.
        // The block is executed as soon as some data is available on the shell output pipe. 
        [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                          object:[outPipe fileHandleForReading] queue:nil
                                                      usingBlock:^(NSNotification *note)
         {
             // Read from shell output
             NSData *outData = [[outPipe fileHandleForReading] availableData];
             NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
             NSLog(@"output: %@", outStr);

             // Continue waiting for shell output.
             [[outPipe fileHandleForReading] waitForDataInBackgroundAndNotify];
         }];

        [task waitUntilExit];

    }
    return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top