无法从NSTask的stdout获得中间输出?

时间:2014-12-02 09:30:20

标签: objective-c macos cocoa nstask

我为一个简单的python脚本编写了一个NSTask async exec方法。

当python脚本只打印到stdout时,一切都很好。 如果有raw_input(期望来自用户的输入),它肯定会使输入正常,但它不会在raw_input之前打印数据。

发生了什么事?

- (NSString*)exec:(NSArray *)args environment:(NSDictionary*)env action:(void (^)(NSString*))action completed:(void (^)(NSString*))completed
{
    _task                           = [NSTask new];
    _output                         = [NSPipe new];
    _error                          = [NSPipe new];
    _input                          = [NSPipe new];
    NSFileHandle* outputF           = [_output fileHandleForReading];
    NSFileHandle* errorF            = [_error fileHandleForReading];
    NSFileHandle* inputF            = [_input fileHandleForWriting];

    __block NSString* fullOutput    = @"";

    NSMutableDictionary* envs = [NSMutableDictionary dictionary];

    NSArray* newArgs = @[@"bash",@"-c"];

    [_task setLaunchPath:@"/usr/bin/env"];
    if (env)
        for (NSString* key in env)
            envs[key] = env[key];

    if ([envs count]) [_task setEnvironment:envs];

    NSString* cmd = @"";

    cmd = [cmd stringByAppendingString:[[[self sanitizedArgs:args] componentsJoinedByString:@" "] stringByAppendingString:@" && echo \":::::$PWD:::::\""]];

    [_task setArguments:[newArgs arrayByAddingObject:cmd]];
    [_task setStandardOutput:_output];
    [_task setStandardError:_error];
    [_task setStandardInput:_input];

    void (^outputter)(NSFileHandle*) = ^(NSFileHandle *file){
        NSData *data = [file availableData];
        NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        NSLog(@"Output: %@",str);

        action(str);
        fullOutput = [fullOutput stringByAppendingString:str];
    };

    void (^inputter)(NSFileHandle*) = ^(NSFileHandle *file) {
        NSLog(@"In inputter");
        NSData *data = [[_task.standardOutput fileHandleForReading] availableData];
        NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        NSLog(@"Output: %@",str);
    };

    [outputF setReadabilityHandler:outputter];
    [errorF setReadabilityHandler:outputter];
    //[inputF setWriteabilityHandler:inputter];

    [_task setTerminationHandler:^(NSTask* task){
        NSLog(@"Terminated: %@",fullOutput);
        completed(fullOutput);

        //[task.standardOutput fileHandleForReading].readabilityHandler = nil;
        //[task.standardError fileHandleForReading].readabilityHandler = nil;
        //[task.standardInput fileHandleForWriting].writeabilityHandler = nil;
        //[task terminate];
        //task = nil;
    }];

    [_task launch];

    //[[_input fileHandleForWriting] waitForDataInBackgroundAndNotify];

    return @"";
}

P.S。我到处寻找解决方案,但似乎没有发现任何问题。看起来有大量的NSTask演练和教程,但是 - 有趣的巧合 - 它们通常避免处理任何stdin含义

1 个答案:

答案 0 :(得分:2)

这与父进程(具有NSTask对象的进程)无关。这完全是关于子进程的行为。子进程实际上并没有将字节写入管道(还)。

来自stdio man page

  

最初,标准错误流是无缓冲的;标准输入和输出   当且仅当流不引用时,才完全缓冲流   交互式或“终端”设备,由isatty(3)函数确定。   事实上,所有新开的流都引用了终端设备   默认为行缓冲,并写入对这些流的挂起输出   每当读取这样的输入流时自动进行。请注意这一点   仅适用于``true reads'&#39 ;;如果读取请求可以满足   现有的缓冲数据,不会发生自动刷新。在这些情况下,   或者在打印部分文件后进行大量计算   在输出端子上的线路,必须fflush(3)标准   在关闭和计算之前输出,以便输出出现。   或者,可以通过setvbuf(3)函数修改这些默认值。

在您的情况下,标准输入和输出实际上并不是指交互式/终端设备。因此,标准输出是完全缓冲的(即缓冲块,而不是行缓冲或无缓冲)。只有当stdio库内部的缓冲区已满,当流关闭或者在流上调用fflush()时才会刷新它。

您在阅读输入时自动刷新标准输出的行为只会在流连接到交互式/终端设备的情况下发生。

除了使用伪终端设备而不是管道进行输入和输出之外,父进程无法影响子进程的缓冲行为。然而,这是一项复杂且容易出错的事业。除此之外,必须对子进程进行编码以设置标准输出的缓冲模式或定期刷新它。