Convert NSTask "/bin/sh -c" command into proper pipeline code - objective-c

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.

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

Related

NSTask - execute echo command

I'm trying to run a simple task which has to execute an echo "Hello World"
Well here is my code:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects:#"echo","hello world" nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
[task setStandardError: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
//...
//Code to get task response
Keep giving me no such file or directory error.. What am I doing wrong ?
The right way to execute a command is
bash -c "echo 'hello world'"
which means the arguments you should pass are
arguments = [NSArray arrayWithObjects:#"-c", #"echo 'hello world'", nil];

How to run NSTask with multiple commands

I'm trying to make a NSTask running a command like this:
ps -clx | grep 'Finder' | awk '{print $2}'
Here is my method
- (void) processByName:(NSString*)name {
NSTask *task1 = [[NSTask alloc] init];
NSPipe *pipe1 = [NSPipe pipe];
[task1 waitUntilExit];
[task1 setLaunchPath: #"/bin/ps"];
[task1 setArguments: [NSArray arrayWithObjects: #"-clx", nil]];
[task1 setStandardOutput: pipe1];
NSTask *task2 = [[NSTask alloc] init];
NSPipe *pipe2 = [NSPipe pipe];
[task2 setLaunchPath: #"/usr/bin/grep"];
[task2 setArguments: [NSArray arrayWithObjects: #"'Finder'", nil]];
[task2 setStandardInput:pipe1];
[task2 setStandardOutput: pipe2];
NSTask *task3 = [[NSTask alloc] init];
NSPipe *pipe3 = [NSPipe pipe];
[task3 setLaunchPath: #"/usr/bin/grep"];
[task3 setArguments: [NSArray arrayWithObjects: #"'{print $2}'", nil]];
[task3 setStandardInput:pipe2];
[task3 setStandardOutput: pipe3];
NSFileHandle *file = [pipe3 fileHandleForReading];
[task1 launch];
[task2 launch];
[task3 launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Result: %#", string);
}
But the result is just
Result:
What am I doing wrong?
EDIT
- (void) processByName:(NSString*)name {
NSTask *task1 = [[NSTask alloc] init];
NSPipe *pipe1 = [NSPipe pipe];
[task1 waitUntilExit];
[task1 setLaunchPath: #"/bin/ps"];
[task1 setArguments: [NSArray arrayWithObjects: #"-clx", nil]];
[task1 setStandardOutput: pipe1];
NSTask *task2 = [[NSTask alloc] init];
NSPipe *pipe2 = [NSPipe pipe];
[task2 setLaunchPath: #"/usr/bin/grep"];
[task2 setArguments: [NSArray arrayWithObjects: #"'Finder'", nil]];
[task2 setStandardInput:pipe1];
[task2 setStandardOutput: pipe2];
NSTask *task3 = [[NSTask alloc] init];
NSPipe *pipe3 = [NSPipe pipe];
[task3 setLaunchPath: #"/usr/bin/grep"];
[task3 setArguments: [NSArray arrayWithObjects: #"'{print $2}'", nil]];
[task3 setStandardInput:pipe2];
[task3 setStandardOutput: pipe3];
NSFileHandle *file = [pipe3 fileHandleForReading];
[task1 launch];
[task2 launch];
[task3 launch];
[[NSNotificationCenter defaultCenter] addObserverForName:NSTaskDidTerminateNotification
object:task3
queue:nil
usingBlock:^(NSNotification* notification){
NSData * data = [file readDataToEndOfFile];
NSString * string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Result: %#", string);
}];
}
The tasks run in a separate process from your code, i.e., asychronously. They probably haven't finished (they may not have even launched!) by the time you get to the readDataToEndOfFile two lines later.
If you're already on a background thread here, you can poll their status: while( ![task isRunning]){, or if you're on the main thread, I'd suggest using GCD to put this onto a queue and doing the polling there.
Actually, better than that would be to use notifications:
[task3 launch];
[[NSNotificationCenter defaultCenter] addObserverForName:NSTaskDidTerminateNotification
object:task3
queue:nil
usingBlock:^{
NSData * data = [file readDataToEndOfFile];
NSString * string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Result: %#", string);
}];
See TN2050: Observing Process Lifetime Without Polling. Each NSTask will send NSTaskDidTerminateNotification when it terminates (you should, ideally, check its return code rather than assuming it ran successfully). You can create a block to be run when task3 sends that notification.
The following code works for me.
NSTask *task1 = [[NSTask alloc] init];
NSPipe *pipe1 = [NSPipe pipe];
[task1 waitUntilExit];
[task1 setLaunchPath: #"/bin/sh"];
[task1 setArguments: [NSArray arrayWithObjects: #"-c",#"ps -A |grep -m1 Finder | awk '{print $1}'", nil]];
[task1 setStandardOutput: pipe1];
[task1 launch];
NSFileHandle *file = [pipe1 fileHandleForReading];
NSData * data = [file readDataToEndOfFile];
NSString * string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Result: %#", string);
Almost 8 years later, I think grep binary is'nt in /usr/bin, for me it's in /bin. Also, for your awk command, you set the launch path to grep.

executing shell command with | (pipe) using NSTask

I'm trying to execute this comamnd ps -ef | grep test using NSTask but I can't get the | grep test to be included in the NSTask:
This is what I'm using currently to get the output of ps -ef into a string then I need to somehow get the pid of the process test
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/ps"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-ef", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
NSLog (#"got\n%#", string);
Piping is a feature provided by shells, such as /bin/sh. You may try launching your command via such a shell:
/* ... */
[task setLaunchPath: #"/bin/sh"];
/* ... */
arguments = [NSArray arrayWithObjects: #"-c", #"ps -ef | grep test", nil];
However, if you let the user supply a value (instead of hard-coding e.g. test), you are making the program susceptible to shell injection attacks, which are kind of like SQL injection. An alternative, which doesn't suffer from this problem, is to use a pipe object to connect the standard output of ps with the standard input of grep:
NSTask *psTask = [[NSTask alloc] init];
NSTask *grepTask = [[NSTask alloc] init];
[psTask setLaunchPath: #"/bin/ps"];
[grepTask setLaunchPath: #"/bin/grep"];
[psTask setArguments: [NSArray arrayWithObjects: #"-ef", nil]];
[grepTask setArguments: [NSArray arrayWithObjects: #"test", nil]];
/* ps ==> grep */
NSPipe *pipeBetween = [NSPipe pipe];
[psTask setStandardOutput: pipeBetween];
[grepTask setStandardInput: pipeBetween];
/* grep ==> me */
NSPipe *pipeToMe = [NSPipe pipe];
[grepTask setStandardOutput: pipeToMe];
NSFileHandle *grepOutput = [pipeToMe fileHandleForReading];
[psTask launch];
[grepTask launch];
NSData *data = [grepOutput readDataToEndOfFile];
/* etc. */
This uses built-in Foundation functionality to perform the same steps as the shell does when it encounters the | character.
Finally as others have pointed out, the usage of grep is overkill. Just add this to your code:
NSArray *lines = [string componentsSeparatedByString:#"\n"];
NSArray *filteredLines = [lines filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: #"SELF contains[c] 'test'"]];
You may need to call [task waitUntilExit] before you launch the task, so that the process can finish running before you read the output.

How to use NSTask with pbcopy?

I am a beginner and I have a problem. I would like to use NSTask with the command "pbcopy". I tried this but it seems that it doesn't work :
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/echo"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"my-text-to-copy", #"| pbcopy", nil];
[task setArguments: arguments];
[task launch];
Any ideas ? Thanks.
It works fine :
NSTask *task = [[NSTask alloc] init];
NSPipe *pipe;
pipe = [NSPipe pipe];
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/echo"];
[task setStandardOutput:pipe]; // write to pipe
[task setArguments: [NSArray arrayWithObjects: #"tmp", nil]];
[task launch];
[task waitUntilExit];
task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/pbcopy"];
[task setStandardInput:pipe]; // read from pipe
[task launch];
[task waitUntilExit];
The pipe ("|") is a feature of the shell, not an argument to the command you're using. You have to use two NSTasks, one for echo and one for pbcopy and set up an NSPipe between them.
Btw, I'm assuming that you're just using this as an example. Otherwise it would be much simpler to use NSPasteboard for this.

NSTask to call command top. Getting Error

intelligent people!
Thanks so much for checking out my post.
Right now I'm running this:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/top"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-stats", #"pid,cpu", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
and it is giving me the error:
Error opening terminal: unknown.
Any clues? Thanks again!
It seems as if it was my arguments:
arguments = [NSArray arrayWithObjects: #"-s", #"1",#"-l",#"3600",#"-stats",#"pid,cpu,time,command", nil];
Thanks!