6

我所拥有的是 NSTask 运行一个很长的预制 shell 脚本,我希望 NSProgressIndicator 检查完成了多少。我尝试了很多东西,但似乎无法让它发挥作用。如果进度条不确定,我知道如何使用它,但我希望它在任务进行时加载。

这是我运行脚本的方式:

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

我需要在其中放置一个进度条来检查该任务发生时的进度并相应地更新。

4

4 回答 4

7

这是一个运行 unix 脚本的异步 NSTask 示例。在 Unix 脚本中有一些echo命令可以将当前状态发送回标准错误,如下所示:

echo "working" >&2

这由通知中心处理并发送到显示器。

要更新确定的进度条,只需发送“25.0”“26.0”等状态更新并转换为浮动并发送到进度条。

注意:经过大量试验并使用本网站和其他参考资料中的许多技巧后,我得到了这个工作。所以我希望它对你有帮助。

以下是声明:

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

以下是主要程序模块:

    - (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
于 2012-02-04T21:49:00.353 回答
1

您必须有某种方式来回调或中断任务的进度,以便告诉您已经取得了多少进展。如果您谈论的是 shell 脚本,您可以将 1 个脚本分解为多个脚本,并在脚本的一部分完成后更新进度指示器。其他应用程序也做过类似的事情,iirc Sparkle 在其解压缩代码中做了一些自定义逻辑以分块解压缩,因此它可以更新进度指示器。如果你想达到同样的效果,你将不得不做类似的事情。

于 2012-01-17T02:43:11.177 回答
0

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

于 2012-01-17T07:42:39.847 回答
0

让您的脚本(nstask)与您的 obj c 控制器通信的一种简单方法是使用标准错误作为通信渠道。并不是说这是完美的,但效果很好。您必须将您的任务设置为异步 nstask 并使用通知分别从标准输入和标准错误中读取。如果您需要,我可以稍后发布。

像这样包装你的脚本:

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

通过管道和文件句柄处理您的标准错误,并将其连接到出口以进行文本状态更新,或将其转换为浮点数以显示进度。IE

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

如果您需要捕获真正的 std 错误,请捕获它并将其合并到 std 输出,就像我的示例一样。这不是一个完美的方法,但我经常使用它并且效果很好。

在我的 iPad 上,没有代码。如果您需要更好的示例,请告诉我。

于 2012-02-04T05:28:27.500 回答