1

I would like to run a shell script, from a file or from an objective-c string (within the code). I would also like the shell script's result to be stored to a variable. I would not like the shell script to be split into arguments (such as setLaunchPath when I run it). For example: running this shell script "mount_webdav idisk.mac.com/mac_username /Volumes/mac_username" instead of "/bin/mount_webdav" then the arguments. Is there anyway to do this? I am using NSTask right now, but it has caused me some errors when I try to put the arguments with it. Here is the posed code:

(some of the .m file)

 NSString *doshellscript(NSString *cmd_launch_path, NSString *first_cmd_pt) {

 NSTask *task = [[NSTask alloc] init]; // Make a new task

 [task setLaunchPath: cmd_launch_path]; // Tell which command we are running

 [task setArguments: [NSArray arrayWithObjects: first_cmd_pt, nil]];

 [task setArguments: first_cmd_pt];

 NSPipe *pipe = [NSPipe pipe];

 [task setStandardOutput: pipe];

 [task launch];

  NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];

  NSString *string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];

  [task release]; //Release the task into the world, thus destroying it.

  return string;
}


NSString *mount_idisk(NSString *mac_username) {

 doshellscript(@"/bin/mkdir", [@"/Volumes/" stringByAppendingString:mac_username]);

 NSString *path_tmp = [mac_username stringByAppendingString: @"/ /Volumes/"];

 NSString *idisk_path = [path_tmp stringByAppendingString:mac_username];

 //NSLog(@"%@", [@" http://idisk.mac.com/" stringByAppendingString: idisk_path]);

 NSString *finished_path = [@"http://idisk.mac.com/" stringByAppendingString: idisk_path];

 doshellscript(@"/sbin/mount_webdav", finished_path);
}

... Here is the line I am using to run it: mount_idisk("username");

Chuck
  • 234,037
  • 30
  • 302
  • 389
alexyorke
  • 4,224
  • 3
  • 34
  • 55

1 Answers1

5

There is no way to pass a whole command line to NSTask.

For good reason; doing so is rife with security holes if you have any kind of string composition going on. Your string composition code would have to be fully cognizant of all of the rules of parsing a shell command line and would have to escape every possible combination of characters that might lead to arbitrary command execution.

The system() C API lets you execute arbitrary commands, but has no mechanism for capturing output directly. It would be easy to add something to your command line that spews the output into a temporary file that you later read, but doing so just adds more security holes above and beyond passing down a whole command line as a single string.

Wait... Looks like you have a straightforward bug:

[task setArguments: [NSArray arrayWithObjects: first_cmd_pt, nil]];
[task setArguments: first_cmd_pt];

Why are you setting and then re-setting the task's arguments?

Given that your mount_idisk() function is effectively composing the individual arguments and concatenating them together into a single string, why don't you simply stuff all the args into an NSArray and modify doshellscript() to take an array as the second parameter; the array of arguments?


You aren't creating the array of arguments correctly.

Namely:

NSArray *finished_path = [NSArray arrayWithObjects:@"http://idisk.mac.com/", mac_username, @"/ /Volumes/", mac_username, nil];

That line is creating an array contain 4 objects which are then treated as 4 separate arguments in the doshellscript() function and not the two arguments that you need.

Maybe something like:

NSString *mobileMeUserURL = [@"http://idisk.mac.com/" stringByAppendingString: mac_username];
NSString *localMountPath = [ @"/ /Volumes/" stringByAppendingString:  mac_username];
NSArray *arguments = [NSArray arrayWithObjects: mobileMeUserURL, localMountPath, nil];
bbum
  • 162,346
  • 23
  • 271
  • 359
  • @ bbum: Are you saying that you can only run one command at the time with NSTask? – Sentry.co Dec 15 '16 at 11:40
  • @GitSyncApp Correct. NSTask allows the external execution of a single command. Create multiple instances, one for each command. If you need to do any kind of piping between processes, write a shell script to a temp file and execute that. – bbum Dec 15 '16 at 16:52
  • @ bbum: What about calling a shellscript that in turn calls multiple commands? – Sentry.co Dec 15 '16 at 17:11
  • @ bbum: I figured out a way to do it: by passing each command as an argument from NSTask to a .sh file. The .sh file loop over and call each command and voila: Multiple command calls from NSTask. – Sentry.co Dec 15 '16 at 19:12
  • @GitSyncApp Right; one command per NSTask.... what that command does is arbitrary. :) – bbum Dec 16 '16 at 13:17
  • @ bbum Agreed. But your statement is false: "There is no way to pass a whole command line to NSTask" There is. The drawback is that you need a "generic" external .sh file to do it, the command-line code remains 'within the code'. But it can be use full to be able to quickly test ideas. Personally I went with Launching multiple NSTasks and then Listening for the final to complete as described in this answer: http://stackoverflow.com/questions/9400287/how-to-run-nstask-with-multiple-commands – Sentry.co Dec 16 '16 at 16:24
  • @GitSyncApp That's still a single command + separated arguments to `NSTask`. That you are "working around it" by spewing commands, etc, to an external shell script is well understood, but that doesn't change the behavior of `NSTask`. In fact, `NSTask` was designed the way it was because passing command lines is both a gigantic security hole and is rife with fragility the moment you consider that command line parsing is oft very different based on shell being used or, even, the configuration of the individual shell used. I sure hope you are controlling your script's environment, for example. – bbum Dec 17 '16 at 17:47
  • @ bburn Your security concerns should be duly noted for future onlookers. Hence my "But it can be use full to be able to quickly test ideas" comment followed by my " I went with Launching multiple NSTasks and then Listening for the final..." comment. That being said it would be interesting to test which is faster. Spawning many NSTask instances or using the .sh loop technique. In the end using a proper Library for the task will be faster than both. I'll do a speed test in the near future and post the result here. – Sentry.co Dec 17 '16 at 20:36