如何在一致的环境中循环使用NSTask run terminal命令?

时间:2012-11-04 09:25:00

标签: objective-c command nstask nspipe

我想使用NSTask模拟终端运行命令。代码如下。它可以循环输入并返回过程输出。

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);

    }
}

我的问题是:当循环结束时,运行环境会恢复到初始化状态。例如,默认运行路径为/Users/apple,我运行cd /将路径更改为/,然后运行pwd,它返回/Users/apple而不是/

那么如何使用NSTask完全模拟终端呢?

1 个答案:

答案 0 :(得分:5)

cdpwd是shell 内置命令。如果您执行任务

/bin/sh -c "cd /"

无法将更改的工作目录恢复到调用进程。如果要设置变量MYVAR=myvalue,则存在同样的问题。

您可以尝试单独解析这些行并更新环境。但是像

这样的多行命令呢?
for file in *.txt
do
    echo $file
done

您不能通过将每一行发送到单独的NSTask进程来模拟它。

您唯一能做的就是使用/bin/sh启动 NSTask进程,并将所有输入行提供给该进程的标准输入。但是你不能使用readDataToEndOfFile来读取输出,但你必须异步读取(使用[[pipe fileHandleForReading] waitForDataInBackgroundAndNotify])。

简而言之:您只能通过运行(单个)shell来模拟终端。

已添加:或许您可以将以下内容用作应用的起点。 (我省略了所有错误检查。)

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;
}