NSTask 执行带参数的查找命令

NSTask to execute a find command with arguments

我正在尝试从 NSTask 执行终端命令,但似乎我做错了什么。

我试过这个:

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

参数有很多变化,但我无法让它发挥作用。 终端命令工作正常:

find /a/path/ -type f -name '*.m' -exec sed -i '' s/NSLog/\/\/NSLog/ {} +

如果你打算使用 /bin/bash 作为任务的启动路径,那么你必须使用一个参数列表,如果你调用 /bin/bash 作为命令,而不是像你这样的命令将作为 input 输入到 /bin/bash 命令提示符。它们是两个不同的东西。

也就是说,您的代码大致相当于 shell 处的以下命令:

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

注意该命令开头的 /bin/bash。另请注意,这是行不通的。最后,我特意将路径写为 "some path here" 并带有空格,以强调您的命令没有规定将其中包含空格的路径视为 find.

的单个参数

如果你想 运行 /bin/bash 并传递一个字符串来解释为命令,你需要使用 -c 选项并将命令字符串作为单个参数传递对于那个 -c 选项。所以:

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

这解决了其中一个问题。它将对应于:

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

这仍然没有解决路径问题。您可能会天真地尝试通过更改格式字符串以在 %@ 格式说明符周围加上引号来解决这个问题,如下所示:

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

但是,如果路径中包含引号,那将无济于事。更糟糕的是,如果路径中包含 $ 或 ` 等特殊字符,它也无济于事。该路径最终可能会被 shell 解释并执行子命令。例如,包含 $(rm -rf ~) 的路径将是灾难性的。

您可以尝试用单引号引用它:

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

但是路径本身可以包含一个单引号,它将结束引号,然后是特殊字符。恶意路径只是更改为 '$(rm -rf ~).

可以正确引用路径,但一般来说,在不需要时使用 shell 是愚蠢的做法。

更好的方法是直接调用您想要 运行 (/usr/bin/find) 的可执行文件并将参数传递给它。不涉及 shell,因此没有 shell 解释任何参数的风险。

所以:

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

甚至比这更好的是在代码中进行目录枚举 — 您在这里使用 find 的东西 — 在代码中,因为它很简单。

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
    }
}

最后,您传递给 sed 的命令似乎不起作用。也许它需要另一个级别的转义(针对 C 编译器和 sed 本身),但更容易避免将斜杠视为特殊字符。如果你想在替换中使用斜杠,你可以而且应该在模式周围使用不同的分隔符。例如:s!NSLog!//NSLog!.