我正在尝试从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
作为命令,而不是作为/bin/bash
命令提示符输入的命令,该列表将起作用。它们是两回事。
也就是说,您的代码在 shell 上执行的命令大致等效
:/bin/bash find some path here -type f -name "*.m" -exec sed -i '' "s/NSLog///NSLog/" {} +
请注意该命令开头的/bin/bash
。另请注意,这不起作用。最后,我故意将路径写成带有空格的"此处的路径",以强调您的命令没有规定将包含空格的路径视为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
)并将参数传递给该可执行文件。不涉及外壳,因此没有对任何参数进行外壳解释的风险。
所以:
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!
.