简体   繁体   English

如何使用确定的NSProgressIndicator来检查NSTask的进度? - 可可

[英]How to use a determinate NSProgressIndicator to check on the progress of NSTask? - Cocoa

What I have is NSTask running a long premade shell script and I want the NSProgressIndicator to check on how much is done. 我所拥有的是NSTask运行一个长预制的shell脚本,我希望NSProgressIndicator检查完成了多少。 I've tried many things but just can't seem to get it to work. 我尝试过很多东西,但似乎无法让它发挥作用。 I know how to use it if the progress bar is indeterminate but i want it to load as the task goes on. 如果进度条不确定但我希望它随着任务的进行而加载,我知道如何使用它。

Here is how I am running the script: 以下是我运行脚本的方法:

- (IBAction)pressButton:(id)sender {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];
    [task setArguments:[NSArray arrayWithObjects:[[NSBundle mainBundle] pathForResource:@"script" ofType:@"sh"], nil]];
    [task launch];
}

I need to put a progress bar in that checks the progress of that task while it happens and update accordingly. 我需要设置一个进度条来检查该任务发生的进度并相应地更新。

Here is an example of an async NSTask running a unix script. 以下是运行unix脚本的异步NSTask示例。 Within the Unix script there are echo commands that send back the current status to standard error like this: 在Unix脚本中有echo命令将当前状态发送回标准错误,如下所示:

echo "working" >&2

This is processed by notification center and sent to the display. 这由通知中心处理并发送到显示器。

To update a determinate progress bar just send status updates like "25.0" "26.0" and convert to float and send to the progress bar. 要更新确定的进度条,只需发送状态更新,如“25.0”“26.0”并转换为浮动并发送到进度条。

note: I got this working after alot of experimenting and by using many tips from this site and other references. 注意:经过大量的实验和使用本网站的许多提示和其他参考文献后,我得到了这个工作。 so I hope it is helpful to you. 所以我希望它对你有所帮助。

Here are the declarations: 以下是声明:

NSTask *unixTask;
NSPipe *unixStandardOutputPipe;
NSPipe *unixStandardErrorPipe;
NSPipe *unixStandardInputPipe;
NSFileHandle *fhOutput;
NSFileHandle *fhError;
NSData *standardOutputData;
NSData *standardErrorData;

Here are the main program modules: 以下是主要的程序模块:

    - (IBAction)buttonLaunchProgram:(id)sender {

    [_unixTaskStdOutput setString:@"" ];
    [_unixProgressUpdate setStringValue:@""];
    [_unixProgressBar startAnimation:sender];

    [self runCommand];
}
- (void)runCommand {

    //setup system pipes and filehandles to process output data
    unixStandardOutputPipe = [[NSPipe alloc] init];
    unixStandardErrorPipe =  [[NSPipe alloc] init];

    fhOutput = [unixStandardOutputPipe fileHandleForReading];
    fhError =  [unixStandardErrorPipe fileHandleForReading];

    //setup notification alerts
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc addObserver:self selector:@selector(notifiedForStdOutput:) name:NSFileHandleReadCompletionNotification object:fhOutput];
    [nc addObserver:self selector:@selector(notifiedForStdError:)  name:NSFileHandleReadCompletionNotification object:fhError];
    [nc addObserver:self selector:@selector(notifiedForComplete:)  name:NSTaskDidTerminateNotification object:unixTask];

    NSMutableArray *commandLine = [NSMutableArray new];
    [commandLine addObject:@"-c"];
    [commandLine addObject:@"/usr/bin/kpu -ca"]; //put your script here

    unixTask = [[NSTask alloc] init];
    [unixTask setLaunchPath:@"/bin/bash"];
    [unixTask setArguments:commandLine];
    [unixTask setStandardOutput:unixStandardOutputPipe];
    [unixTask setStandardError:unixStandardErrorPipe];
    [unixTask setStandardInput:[NSPipe pipe]];
    [unixTask launch];

    //note we are calling the file handle not the pipe
    [fhOutput readInBackgroundAndNotify];
    [fhError readInBackgroundAndNotify];
}
-(void) notifiedForStdOutput: (NSNotification *)notified
{

    NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
    NSLog(@"standard data ready %ld bytes",data.length);

    if ([data length]){

        NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
        NSTextStorage *ts = [_unixTaskStdOutput textStorage];
        [ts replaceCharactersInRange:NSMakeRange([ts length], 0)
                          withString:outputString];
    }

    if (unixTask != nil) {

        [fhOutput readInBackgroundAndNotify];
    }

}
-(void) notifiedForStdError: (NSNotification *)notified
{

    NSData * data = [[notified userInfo] valueForKey:NSFileHandleNotificationDataItem];
    NSLog(@"standard error ready %ld bytes",data.length);

    if ([data length]) {

        NSString * outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];  
        [_unixProgressUpdate setStringValue:outputString];
    }

    if (unixTask != nil) {

        [fhError readInBackgroundAndNotify];
    }

}
-(void) notifiedForComplete:(NSNotification *)anotification {

    NSLog(@"task completed or was stopped with exit code %d",[unixTask terminationStatus]);
    unixTask = nil;

    [_unixProgressBar stopAnimation:self];
    [_unixProgressBar viewDidHide];

    if ([unixTask terminationStatus] == 0) {
        [_unixProgressUpdate setStringValue:@"Success"]; 
    }
    else {
        [_unixProgressUpdate setStringValue:@"Terminated with non-zero exit code"];
    }
}
@end

You have to have some way to call back or interrupt the progress of a task in oder to tell how much progress you have made. 您必须有一些方法来回叫或中断任务中的任务进度,以告知您取得了多少进展。 If you are talking about a shell script you could break 1 script up into multiple scripts and upon the completion of a section of the script update the progress indicator. 如果您正在讨论shell脚本,则可以将1个脚本分解为多个脚本,并在完成脚本的一部分后更新进度指示器。 Other apps have done things like this, iirc Sparkle did some custom logic in its decompression code to uncompress in chunks so it could update a progress indicator. 其他应用程序已经完成了这样的事情,iirc Sparkle在其解压缩代码中做了一些自定义逻辑,以便在块中解压缩,因此它可以更新进度指示器。 If you want to achieve the same effect you are going to have to do something similar. 如果你想达到同样的效果,你将不得不做类似的事情。

获取您运行的命令的PID(进程ID),将其与PS(进程状态)命令结合使用,您可以获取进程的状态,使用代码中的该值在进度条中显示它

a simple way to have your script (nstask) communicate with your obj c controller is by using standard error as a communication channel. 让脚本(nstask)与对象控制器通信的一种简单方法是使用标准错误作为通信通道。 Not saying this is perfect but works quite well. 不是说这是完美的,但效果很好。 You have to setup your task as an asynchronous nstask and use notifications to read from standard input and standard error separately. 您必须将任务设置为异步nstask,并使用通知分别从标准输入和标准错误中读取。 I can post it later if you need it. 如果你需要,我可以稍后发布。

Wrap your script like this: 像这样包装你的脚本:

echo "now starting" >&2
for i in $(ls /tmp)
do 
echo $i 2>&1
done
echo "now ending" >&2

Process Your standard error through a pipe and filehandle and wire it to an outlet for textual status updates or convert it to a float for progress displays. 通过管道和文件句柄处理标准错误,并将其连接到插座以进行文本状态更新,或将其转换为浮动以显示进度。 Ie

echo "25.0" >&2. Can be captured and converted.

If you need to capture real std error then trap it and merge it to std out like my example. 如果你需要捕获真正的std错误然后捕获它并将其合并到std out就像我的例子。 This is not a perfect approach but I use it frequently and it works well. 这不是一个完美的方法,但我经常使用它,它运作良好。

On my iPad and don't have code. 在我的iPad上,没有代码。 Let me know if you need a better example. 如果你需要一个更好的例子,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM