I learned a cool trick hack today in the process of trying to help somebody with their cocoa code. Dude was trying to fire an NSTask with some arguments for the current command, and then an additional argument that included the rest of a 3 or 4 stage pipeline. The command in question is /usr/bin/ldapsearch, and he was trying to set the arguments array something like:
arguments = [NSArray arrayWithObjects: @"-LLL", @"-x", @"-b", @"cn=users,dc=company,dc=com", @"-h", @"dirserver.company.com", @"cn", @"uid", @"| /usr/bin/othercommand -with -arguments | /bin/thirdcommand -and -even -more -args", nil];
This won’t work. Not even close, actually. The proper cocoa way to do this would be to create an NSTask for each command, setting each task’s arguments as necessary. You connect the tasks with NSPipe objects, as explained at cocoadevcentral. Obviously this would become somewhat verbose / tedious if you needed a long pipeline.
Of course, the whole idea of calling shell commands in the first place is usually considered ‘icky’ by most real programmers. The ‘better’ way would be to use the appropriate cocoa / carbon API, and there’s one for just about everything. The thing is that some folks (such as myself) are more comfortable in a unix shell than in Xcode; often it is faster to whip out an NSTask than to learn some Cocoa API to get the job done. Performance geeks will rightly assert that using NSTask is much slower, however the speed difference is not even perceptible to a human unless you’re using NSTask in a rapid-fire fashion (a simple “take input, fire NSTask, collect and display results” is quite fast). Now, I’m certain that I’ll read this again in a few years and laugh at myself for being a hack, but I don’t care :)
For right now, if I’m going to hack, I’m going to hack it good. The trick is this: set the NSTask command to /bin/sh, and the arguments as follows: The first argument is set to “-c”, the second argument is the entire shell pipeline, including as many commands and pipes as you need, and the third argument as the requisite ‘nil’. Basically, we’re now simply passing a little mini shell script directly to /bin/sh. This is roughly akin to creating a small shell script on the fly, writing it to the filesystem, and then executing it with NSTask, except this way there’s no need for any file i/o. Another advantage is that you should (note: untested!) be able to take advantage of other shell functions for which there is no direct equivalent in NSTask land; e.g. angle brackets for input / output redirection, etc.
3 Responses to NSTask trickery for long shell pipelines