Question

I need to read the last added line to a log file, in realtime, and capture that line being added.

Something similar to Tail -f.

So my first attempt was to use Tail -f using NSTask.

I can't see any output using the code below:

    NSTask *server = [[NSTask alloc] init];
    [server setLaunchPath:@"/usr/bin/tail"];
    [server setArguments:[NSArray arrayWithObjects:@"-f", @"/path/to/my/LogFile.txt",nil]];

    NSPipe *outputPipe = [NSPipe pipe];
    [server setStandardInput:[NSPipe pipe]];
    [server setStandardOutput:outputPipe];

    [server launch];
    [server waitUntilExit];
    [server release];

    NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
    NSString *outputString = [[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease];
    NSLog (@"Output \n%@", outputString);

I can see the output as expected when using:

[server setLaunchPath:@"/bin/ls"];
  1. How can i capture the output of that tail NSTask?

  2. Is there any alternative to this method, where I can open a stream to file and each time a line is added, output it on screen? (basic logging functionality)

Was it helpful?

Solution

This is a little tricky to do your way, as readDataToEndOfFile will wait until tail closes the output stream before returning, but tail -f never closes the output stream (stdout). However, this is actually pretty simple to do with basic C I/O code, so I whipped up a simple FileTailer class that you can check out. It's not anything fancy, but it should show you how it's done. Here're the sources for FileTailer.h, FileTailer.m, and a test driver.

The meat of the class is pretty simple. You pass it a block, and it reads a character from the stream (if possible) and passes it to the block; if EOF has been reached, it waits a number of seconds (determined by refresh) and then tries to read the stream again.

- (void)readIndefinitely:(void (^)(int ch))action
{
    long pos = 0L;
    int ch = 0;

    while (1) {
        fseek(in, pos, SEEK_SET);
        int ch = fgetc(in);
        pos = ftell(in);
        if (ch != EOF) {
            action(ch);
        } else {
            [NSThread sleepForTimeInterval:refresh];
        }
    }
}

You can call it pretty simply, like this:

FileTailer *tail = [[[FileTailer alloc] initWithStream:stdin refreshPeriod:3.0] autorelease];
[tail readIndefinitely:^ void (int ch) { printf("%c", ch); }];

(Caveat: I wrote the FileTailer class pretty fast, so it's kind of ugly right now and should be cleaned up a bit, but it should serve as a decent example on how to read a file indefinitely, à la tail -f.)

OTHER TIPS

Here's a way to use "tail -f logfile" via NSTask in Objective-C:

asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask

...

Being a GUI-less application (i.e. a Foundation-based command line tool), asynctask.m runs an NSRunLoop manually to enable the use of asynchronous "waitForDataInBackgroundAndNotify" notifications. In addition, asynctask.m uses pthread_create(3) and pthread_detach(3) for writing more than 64 KB to the stdin of an NSTask.

Source code of asynctask.m available at: http://www.cocoadev.com/index.pl?NSPipe

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top