2

Can someone help me convert the following code into code that instead has two NSTasks for "cat" and "grep", showing how the two can be connected together with pipes? I suppose I would prefer the latter approach, since then I no longer have to worry about quoting and stuff.

NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: @"/bin/sh"];

NSArray *arguments;
arguments = [NSArray arrayWithObjects: @"-c",
             @"cat /usr/share/dict/words | grep -i ham", nil];
[task setArguments: arguments];
[task launch];

Update: Note that cat and grep are here just meant as (lousy) example. I still want to do this for commands that make more sense.

Enchilada
  • 3,859
  • 1
  • 36
  • 69
  • As a matter of shell command style: Avoid `cat` whenever possible. `grep` accepts multiple file arguments after the pattern, so you should be using `grep -i ham /usr/share/dict/words`. And, look: Your problem goes away, as you can run `grep` directly and no longer need a pipe. – Jeremy W. Sherman Jun 05 '11 at 16:29

1 Answers1

3

Use a instance of NSTask for each program and connect their standard inputs/outputs with NSPipe:

NSPipe *pipe = [[NSPipe alloc] init];
NSPipe *resultPipe = [[NSPipe alloc] init];

NSTask *task1 = [[NSTask alloc] init];
[task1 setLaunchPath: @"/bin/cat"];
[task1 setStandardOutput: pipe];
[task1 launch];

NSTask *task2 = [[NSTask alloc] init];
[task2 setLaunchPath: @"/bin/grep"];
[task2 setStandardInput: pipe];
[task2 setStandardOutput: resultPipe];
[task2 launch];

NSData *result = [[resultPipe fileHandleForReading] readDataToEndOfFile];
Sven
  • 22,475
  • 4
  • 52
  • 71
  • Are you sure I don't have to do [task1 waitUntilExit] to make sure that task1 has outputted all that it wishes to output first? – Enchilada Jun 05 '11 at 21:21
  • Because I wonder whether task2 doesn't otherwise risk at being handed a premature input. – Enchilada Jun 05 '11 at 21:21
  • 1
    No, the pipe will take care of that. You actually can’t wait for the first task to finish before you start the second since the buffer space in a pipe is limited. – Sven Jun 06 '11 at 09:45
  • But if I want to run this code synchronously (i.e. wait until it's done before doing anything else), it's now the task2 that's the important one, right? Should I type [task2 waitUntilExit] after your code, and then fetch the data from task2's output (via a new pipe I will set using [task2 setStandardOutput:newPipe])? – Enchilada Jun 06 '11 at 09:52
  • And also, if I only use task2 at the end, after [task2 waitUntilExit] is done, it's also guaranteed that task1 is done, right? So then I could check the return status from both tasks, right? – Enchilada Jun 06 '11 at 09:58
  • 1
    You shouldn’t use the `NSTask` method `waitUntilExit` in this situation. I updated my sample code to read the output of the second task. – Sven Jun 06 '11 at 10:00
  • But if I try doing NSLog(@"task1 running: %d",[task1 isRunning]); NSLog(@"task2 running: %d",[task2 isRunning]); after your new code, I see that the result is unpredictable. Task 1 is always not running (i.e. it is finished). However, task 2 is sometimes still running and sometimes not. Are you sure I don't want a [task2 waitUntilExit]; after your new code? – Enchilada Jun 06 '11 at 11:32
  • 1
    After `readDataToEndOfFile` the second task closed the pipe. The process might still be running, but it cannot output any more data, so there is no point in waiting for the process to terminate. – Sven Jun 06 '11 at 12:01
  • Unless you want to actually make sure the thing returned 0 (success), right? – Enchilada Jun 06 '11 at 12:04
  • 1
    True, if you need the return value of the child process you have to wait for it to finnish, I should have mentioned that before. – Sven Jun 06 '11 at 12:08