0

I am trying to execute a terminal command from NSTask but it's seems that I do something wrong.

I have tried this:

  NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/bin/bash";

NSString *arr = [NSString stringWithFormat:@"find %@ -type f -name \"*.m\" -exec sed -i '' \"s/NSLog/\\/\\/NSLog/\" {} +",path];
task.arguments = [arr componentsSeparatedByString:@" "];

[task launch];

with a lot of variations in arguments but I cant get it to work. The terminal command works fine:

find /a/path/ -type f -name '*.m' -exec sed -i '' s/NSLog/\\/\\/NSLog/ {} +
BlackM
  • 3,927
  • 8
  • 39
  • 69

1 Answers1

3

If you are going to use /bin/bash as the task's launch path, then you have to use an argument list that would work if you invoked /bin/bash as the command, not a command as you would enter as input to a /bin/bash command prompt. They are two different things.

That is, your code is doing roughly the equivalent of the following command at the shell:

/bin/bash find some path here -type f -name "*.m" -exec sed -i '' "s/NSLog/\/\/NSLog/" {} +

Note the /bin/bash at the beginning of that command. Also note that this would not work. Finally, I deliberately wrote the path as "some path here" with spaces to emphasize that your command makes no provision for a path which has spaces in it to be treated as a single argument to find.

If you want to run /bin/bash and pass it a string to interpret as a command, you need to use the -c option and pass the command string as a single argument for that -c option. So:

/bin/bash -c 'find some path here -type f -name "*.m" -exec sed -i "" "s/NSLog/\/\/NSLog/" {} +'

That solves one of the problems. It would correspond to:

task.arguments = @[ @"-c", arr ];

This still doesn't solve the problem with the path. You might naively attempt to solve that by changing your format string to put quotes around the %@ format specifier, like so:

NSString *arr = [NSString stringWithFormat:@"find \"%@\" -type f -name \"*.m\" -exec sed -i '' \"s/NSLog/\\/\\/NSLog/\" {} +",path];

However, that doesn't help if the path has quotes in it. Even worse, it doesn't help if the path has special characters like $ or ` in it. The path could end up being interpreted by the shell and executing subcommands. For example, a path which includes $(rm -rf ~) in it would be disastrous.

You could try quoting it with single quotes:

NSString *arr = [NSString stringWithFormat:@"find '%@' -type f -name \"*.m\" -exec sed -i '' \"s/NSLog/\\/\\/NSLog/\" {} +",path];

But the path could itself include a single quote, which would end the quoting, and then special characters. The malicious path just changes to '$(rm -rf ~).

It's possible to properly quote the path but, in general, it's folly to use the shell when it's not necessary.

Much better is to directly invoke the executable you want to run (/usr/bin/find) and pass arguments to that. There's no shell involved, so no risk of shell interpretation of any arguments.

So:

task.launchPath = @"/usr/bin/find";
task.arguments = @[ path, @"-type", @"f", @"-name", @"*.m", @"-exec", @"sed", @"-i", @"", @"s/NSLog/\\/\\/NSLog/\", @"{}", @"+" ];

Even better than that is to do the directory enumeration — what you're using find for here — in code, since it's easy.

NSURL* url = [NSURL fileURLWithPath:path];
NSFileManager* fm = [[NSFileMananger alloc] init];
// Consider passing NSDirectoryEnumerationSkipsPackageDescendants in the options
NSDirectoryEnumerator* e = [fm enumeratorAtURL:url includingPropertiesForKeys:nil options:0 errorHandler:nil];
for (NSURL* item in e)
{
    if ([item.pathExtension isEqualToString:@"m"])
    {
        // Use NSTask to run your /usr/bin/sed command on the item
    }
}

Finally, the command you're passing to sed doesn't seem like it could work. Maybe it needs another level of escaping (against the C compiler and sed itself), but it's easier to just avoid treating the slash as a special character. You can and should use a different delimiter around the pattern if you want to use slashes in the substitution. For example: s!NSLog!//NSLog!.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • EXCELLENT answer which help me to understand a lot about the find command in Unix. Thank you very much! – BlackM Feb 27 '16 at 07:53