简体   繁体   English

Xcode Cocoa目标C使用NSTask执行“查找” Shell命令返回语法

[英]Xcode Cocoa Objective C Using NSTask to Execute “Find” Shell Command Returns Syntax

I am using Xcode 6.4 trying to execute a find command. 我正在使用Xcode 6.4尝试执行find命令。 The application is a wrapper around the find command. 该应用程序是find命令的包装器。 We have a SAN that finder will not search however the find command will search the SAN, I don't wish to discuss the issues with the SAN. 我们有一个SAN,Finder不会搜索,但是find命令会搜索SAN,我不想讨论SAN的问题。

I wrote the application originally in Swift in a few short minutes. 我最初是在几分钟内用Swift编写应用程序的。 Unfortunately it will not deploy to OSX 10.8 so I proceeded to rewrite in Objective C. 不幸的是,它将无法部署到OSX 10.8,因此我着手在Objective C中进行重写。

I suspect the problem I'm having is related to the way I am constructing the arguments array. 我怀疑我遇到的问题与构造arguments数组的方式有关。 The only examples I came across after Googling for 3 days all had hard coded literals for the arguments, in the real world we use variables. 在谷歌搜索3天后,我遇到的唯一示例都具有用于参数的硬编码字面量,在现实世界中,我们使用变量。

Here is the basic code associated when I click the "Search" button. 这是单击“搜索”按钮时关联的基本代码。

NSString* newShell = @"-c";
NSString* commandFind = @"find";
NSString* optionName = @"-iname";
NSString* searchFor = @"\"";
NSString* searchPath = @"\"";
searchPath = [searchPath stringByAppendingString:_searchPathOutlet.stringValue];
searchPath = [searchPath stringByAppendingString:@"\""];
NSString* searchWildCard = @"*";
NSString* searchWord = _searchWordsOutlet.stringValue;
searchFor = [searchFor stringByAppendingString:searchWildCard];
searchFor = [searchFor stringByAppendingString:searchWord];
searchFor = [searchFor stringByAppendingString:searchWildCard];
searchFor = [searchFor stringByAppendingString:@"\""];
NSLog(@"%@",searchPath); //debug
NSLog(@"%@",searchFor); //debug
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects: newShell, commandFind, searchPath, optionName, searchFor, nil];
NSString *stringRep = [NSString stringWithFormat:@"%@",arguments]; //debug
NSLog(@"%@",stringRep); //debug
[task setArguments:arguments];
NSPipe* pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task launch];
[task waitUntilExit]; // Alternatively, make it asynchronous.
NSData *outputData = [[pipe fileHandleForReading] readDataToEndOfFile];
NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
_searchResultsOutlet.string = outputString;



The output is as follows:
2015-07-16 12:09:39.141 APSAETVSANSearch[2716:68456] "/Users/test/Downloads"
2015-07-16 12:09:39.141 APSAETVSANSearch[2716:68456] "*adobe*"
2015-07-16 12:09:39.141 APSAETVSANSearch[2716:68456] (
"-c",
find,
"\"/Users/test/Downloads\"",
"-iname",
"\"*adobe*\""
)
usage: find [-H | -L | -P] [-EXdsx] [-f path] path ... [expression]
find [-H | -L | -P] [-EXdsx] -f path [path ...] [expression]

I agree with both you and trojanfoe. 我同意您和trojanfoe的意见。 I am new to objective-c and I find the syntax a little strange compared to other languages but I have some working code listed below. 我是Objective-c的新手,与其他语言相比,我觉得语法有些奇怪,但是下面列出了一些工作代码。 I would like to run the function doSearch as a callback in an asynchronous thread. 我想将功能doSearch作为异步线程中的回调运行。 I am familiar with Microsoft's threading related to passing "thread safe" parameters using delegates to the thread's callback function (eg searchPathURL, searchWords, textView) but can't seem to find an example that is appropriate via google. 我熟悉Microsoft的线程,该线程与使用委托给线程的回调函数(例如searchPathURL,searchWords,textView)传递“线程安全”参数有关,但似乎找不到适合Google的示例。 Here is my function can you get me started? 这是我的职能,您可以让我开始吗?

- (IBAction)buttonSearch_Click:(id)sender {
doSearch(directoryURL, _searchWordOutlet.stringValue, _textViewOutlet);
}

void doSearch(NSURL *searchPathURL, NSString *searchWords, NSTextView *textView){
NSArray *keys = [NSArray arrayWithObjects:
NSURLIsDirectoryKey, NSURLIsPackageKey, NSURLLocalizedNameKey, nil];
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager]
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:(NSDirectoryEnumerationSkipsHiddenFiles)
errorHandler:^(NSURL *url, NSError *error) {
 // Handle the error.
 // Return YES if the enumeration should continue after the error.
 return YES;
}
];
for (NSURL *url in enumerator) {
// Error-checking is omitted for clarity.

NSNumber *isDirectory = nil;
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];

if ([isDirectory boolValue]) {
NSString *localizedName = nil;
[url getResourceValue:&localizedName forKey:NSURLLocalizedNameKey error:NULL];

NSNumber *isPackage = nil;
[url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:NULL];

if ([isPackage boolValue]) {
//Its a package
//NSLog(@"Package at %@", localizedName);
}
else {
//Its a directory
//NSLog(@"Directory at %@", localizedName);
}
}
else {
//Its a file
NSString *searchPath = [url.path lowercaseString];
NSString *searchText = [searchWords lowercaseString];
if ([searchPath containsString:searchText]) {
NSLog(@"%@", url.path);
[textView insertText:url.path];
[textView insertText:@"\n"];
}
}
}
}

When you use /bin/sh -c <command> the command has to be one argument. 当使用/bin/sh -c <command> ,命令必须是一个参数。 That is, at a shell, you can't do: 也就是说,在外壳程序上,您不能执行以下操作:

/bin/sh -c find "/Users/test/Downloads" -iname "*adobe*"

You have to do: 你必须做:

/bin/sh -c 'find "/Users/test/Downloads" -iname "*adobe*"'

So, just make one single string whose content is equivalent to: 因此,只需制作一个字符串,其内容等效于:

NSString* command = @"find \"/Users/test/Downloads\" -iname \"*adobe*\"";

and use an argument array equivalent to @[ @"-c", command ] . 并使用等效于@[ @"-c", command ]的参数数组。

Alternatively, if you don't need the shell to process the string (and, in your example, you don't), you should just set the task's launch path to @"/usr/bin/find" and set the arguments to @[ @"/Users/test/Downloads", @"-iname", @"*adobe*" ] . 另外,如果不需要外壳程序来处理字符串(在您的示例中也不需要),则只需将任务的启动路径设置为@"/usr/bin/find"并将参数设置为@[ @"/Users/test/Downloads", @"-iname", @"*adobe*" ] Using a shell when you don't need it only adds danger and inefficiency. 不需要时使用外壳只会增加危险和效率低下。 For example, if your user enters "$(rm -rf ~)" in your text field, they will be very unhappy when you run the task. 例如,如果用户在文本字段中输入“ $(rm -rf〜)”,则当您运行任务时,他们将非常不满意。 Less destructive but more likely is if the directory path or search term contains a double quote ( " ) character. 破坏性较小,但如果目录路径或搜索词包含双引号( " )字符,则破坏可能性更大。

All of that said, I concur with trojanfoe that you should do this programmatically rather than launching a subprocess. 综上所述,我同意trojanfoe的观点,您应该以编程方式执行此操作,而不是启动子进程。 If NSDirectoryEnumerator doesn't work for some reason, you can use POSIX/BSD APIs. 如果由于某些原因NSDirectoryEnumerator不起作用,则可以使用POSIX / BSD API。


Update in response to your updated question: 更新以回应您更新的问题:

To run a task in the background, you can use Grand Central Dispatch (GCD). 要在后台运行任务,您可以使用Grand Central Dispatch(GCD)。 For example, your -buttonSearch_Click: method could be written like this: 例如,您的-buttonSearch_Click:方法可以这样编写:

- (IBAction)buttonSearch_Click:(id)sender {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        doSearch(directoryURL, _searchWordOutlet.stringValue, _textViewOutlet);
    });
}

However, you can't update the GUI from a background thread. 但是,您不能从后台线程更新GUI。 So, your doSearch() function needs to shunt any manipulation of the text view back to the main thread. 因此,您的doSearch()函数需要将对文本视图的任何操作分流回主线程。 It can do this using code like the following: 它可以使用如下代码执行此操作:

dispatch_async(dispatch_get_main_queue(), ^{
    [textView insertText:url.path];
    [textView insertText:@"\n"];
});

By the way, your check if the search term is in the path is not the same as what the find command you started with does. 顺便说一句,您对搜索词是否在路径中的检查与您最初使用的find命令所执行的检查不同。 You're checking the whole path, including parent directories, while the find command only checked each item's file name. 您正在检查整个路径,包括父目录,而find命令仅检查每个项目的文件名。 You can get the file name from an NSURL by requesting its lastPathComponent rather than its path . 您可以通过请求NSURLlastPathComponent而不是其path来获取文件名。

Also, to do a case-insensitive check if one string contains another, you should not lowercase boths strings and then call -containsString: . 另外,如果一个字符串包含另一个做一个不区分大小写的检查,你不应该小写boths字符串,然后调用-containsString: You should just use -localizedCaseInsensitiveContainsString: without lowercasing manually. 您应该只使用-localizedCaseInsensitiveContainsString:而不用手动-localizedCaseInsensitiveContainsString:大小写。 (Or, if you don't want locale-appropriate case-insensitivity, you can use -rangeOfString:options: with NSCaseInsensitiveSearch for the options.) (或者,如果不想让区域设置不区分大小写,则可以使用-rangeOfString:options:NSCaseInsensitiveSearch作为选项。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM