Python 从进程读取时,NSTask需要刷新';标准输出,终端不工作

Python 从进程读取时,NSTask需要刷新';标准输出,终端不工作,python,objective-c,stdout,pipe,nstask,Python,Objective C,Stdout,Pipe,Nstask,我有一个简单的Python脚本,它询问您的姓名,然后将其吐出来: def main(): print('Enter your name: ') for line in sys.stdin: print 'You entered: ' + line 很简单的东西!在OS X终端上运行此功能时,效果非常好: $ python nameTest.py Enter your name: Craig^D You entered: Craig 但是,当尝试通过NSTa

我有一个简单的Python脚本,它询问您的姓名,然后将其吐出来:

def main():
    print('Enter your name: ')
    for line in sys.stdin:
        print 'You entered: ' + line
很简单的东西!在OS X终端上运行此功能时,效果非常好:

$ python nameTest.py 
Enter your name: 
Craig^D
You entered: Craig
但是,当尝试通过
NSTask
运行此进程时,只有在Python脚本中添加了额外的flush()调用时,才会显示stdout。

这是如何配置
NSTask
和管道的:

NSTask *_currentTask = [[NSTask alloc] init];
_currentTask.launchPath = @"/usr/bin/python";
_currentTask.arguments = [NSArray arrayWithObject:@"nameTest.py"];

NSPipe *pipe = [[NSPipe alloc] init];
_currentTask.standardOutput = pipe;
_currentTask.standardError = pipe;

dispatch_queue_t stdout_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

__block dispatch_block_t checkBlock;

checkBlock = ^{
    NSData *readData = [[pipe fileHandleForReading] availableData];
    NSString *consoleOutput = [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding];
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self.consoleView appendString:consoleOutput];
    });
    if ([_currentTask isRunning]) {
        [NSThread sleepForTimeInterval:0.1];
        checkBlock();
    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSData *readData = [[pipe fileHandleForReading] readDataToEndOfFile];
            NSString *consoleOutput = [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding];
            [self.consoleView appendString:consoleOutput];
        });
    }
};

dispatch_async(stdout_queue, checkBlock);

[_currentTask launch];
但是当运行
NSTask
时,它是这样显示的(最初为空,但在输入我的名字并按CTRL+D后,它会立即完成):


因此,我的问题是:如何从我的
NSTask
中读取
stdout
,而不需要Python脚本中的额外flush()语句?为什么作为
NSTask运行时不会立即出现“输入您的姓名:提示”

当Python看到其标准输出是一个终端时,它会安排在脚本从
sys.stdin
读取时自动刷新
sys.stdout
。使用
NSTask
运行脚本时,脚本的标准输出是管道,而不是终端

更新 对此,有一个特定于Python的解决方案。您可以将
-u
标志传递给Python解释器(例如
\u currentTask.arguments=@[@“-u”,@“nameTest.py”;
),它告诉Python根本不要缓冲标准输入、标准输出或标准错误。您还可以在进程的环境中设置
pythonunbuffer=1
,以实现相同的效果

起初的 适用于任何程序的更通用的解决方案使用所谓的“伪终端”(或者,历史上称为“伪电传打字机”),我们将其缩短为“pty”。(事实上,这就是终端应用程序本身的功能。这是一种罕见的Mac电脑,它的物理终端或电传打字机连接到串行端口!)

每个pty实际上是一对虚拟设备:一个从设备和一个主设备。写入主设备的字节可以从从设备读取,反之亦然。因此,这些设备更像插座(双向),而不像管道(单向)。此外,pty还允许您设置终端I/O标志(或“termios”),以控制从机是否回显其输入,是否一次传递一行或一次传递一个字符,等等

无论如何,您可以使用
openpty
功能轻松打开主/从对。下面是一个小类别,您可以使用它使
NSTask
对象使用从属端作为任务的标准输入和输出

NSTask+PTY.h NSTask+PTY.m
然后,您可以使用
masterHandle

阅读任务并写入任务。您有什么问题吗?@kindall如果OP不清楚,很抱歉-在底部添加了特定的问题。我希望每个正在为为什么Python脚本不输出中间结果而苦苦挣扎的人都能找到这个问题。你又给我省了5个小时的麻烦!谢谢谢谢rob,这是一个非常好的回答,效果几乎完美。一个问题是,我似乎无法关闭category方法返回的
NSFileHandle
,它只是挂起。(调用
closeFile
是我模拟
CTRL+D
的方式)此外,由于输入/输出发生在同一个句柄上,我写入stdin的每个字符都会回显到stdout。对于回显问题:尝试使用
tcgetattr
获取从设备的
termios
。清除
c\u lflag
字段中的
ECHO
位,并使用
tcsetattr
将修改后的
termios
放回从机。您可能需要阅读
tcgetattr
termios
手册页。对于挂起问题:我不知道发生了什么。您可能需要找到一些其他的
termios
位进行调整。
Craig^DEnter your name: 
You entered: Craig
@interface NSTask (PTY)

- (NSFileHandle *)masterSideOfPTYOrError:(NSError **)error;

@end
#import "NSTask+PTY.h"
#import <util.h>

@implementation NSTask (PTY)

- (NSFileHandle *)masterSideOfPTYOrError:(NSError *__autoreleasing *)error {
    int fdMaster, fdSlave;
    int rc = openpty(&fdMaster, &fdSlave, NULL, NULL, NULL);
    if (rc != 0) {
        if (error) {
            *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
        }
        return NULL;
    }
    fcntl(fdMaster, F_SETFD, FD_CLOEXEC);
    fcntl(fdSlave, F_SETFD, FD_CLOEXEC);
    NSFileHandle *masterHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdMaster closeOnDealloc:YES];
    NSFileHandle *slaveHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdSlave closeOnDealloc:YES];
    self.standardInput = slaveHandle;
    self.standardOutput = slaveHandle;
    return masterHandle;
}

@end
NSTask *_currentTask = [[NSTask alloc] init];
_currentTask.launchPath = @"/usr/bin/python";
_currentTask.arguments = @[[[NSBundle mainBundle] pathForResource:@"nameTest" ofType:@"py"]];

NSError *error;
NSFileHandle *masterHandle = [_currentTask masterSideOfPTYOrError:&error];
if (!masterHandle) {
    NSLog(@"error: could not set up PTY for task: %@", error);
    return;
}