Python 从进程读取时,NSTask需要刷新';标准输出,终端不工作
我有一个简单的Python脚本,它询问您的姓名,然后将其吐出来: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
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;
}