Xcode Какао Цель C Использование NSTask для выполнения команды "Найти" Shell возвращает синтаксис
Я использую Xcode 6.4, пытаясь выполнить команду поиска. Приложение является оберткой вокруг команды find. У нас есть SAN, который средство поиска не будет искать, однако команда find будет искать SAN, я не хочу обсуждать проблемы с SAN.
Первоначально я написал заявление в Swift за несколько коротких минут. К сожалению, он не будет развернут в OSX 10.8, поэтому я переписал в Objective C.
Я подозреваю, что моя проблема связана с тем, как я строю массив arguments. Единственные примеры, с которыми я столкнулся после трехдневного поиска в Google, содержали жестко закодированные литералы для аргументов, в реальном мире мы используем переменные.
Вот основной код, связанный, когда я нажимаю кнопку "Поиск".
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]
Я согласен как с вами, так и с Trojanfoe. Я новичок в Objective-C, и я нахожу синтаксис немного странным по сравнению с другими языками, но у меня есть некоторый рабочий код, перечисленный ниже. Я хотел бы запустить функцию doSearch в качестве обратного вызова в асинхронном потоке. Я знаком с многопоточностью Microsoft, связанной с передачей "потокобезопасных" параметров с использованием делегатов в функцию обратного вызова потока (например, searchPathURL, searchWords, textView), но не могу найти подходящий пример через Google. Вот моя функция, вы можете начать?
- (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"];
}
}
}
}
1 ответ
Когда вы используете /bin/sh -c <command>
команда должна быть одним аргументом. То есть в оболочке вы не можете сделать:
/bin/sh -c find "/Users/test/Downloads" -iname "*adobe*"
Ты должен сделать:
/bin/sh -c 'find "/Users/test/Downloads" -iname "*adobe*"'
Итак, просто создайте одну строку, содержимое которой эквивалентно:
NSString* command = @"find \"/Users/test/Downloads\" -iname \"*adobe*\"";
и использовать массив аргументов, эквивалентный @[ @"-c", command ]
,
В качестве альтернативы, если вам не нужна оболочка для обработки строки (а в вашем примере это не так), вам просто нужно задать путь запуска задачи: @"/usr/bin/find"
и установить аргументы @[ @"/Users/test/Downloads", @"-iname", @"*adobe*" ]
, Использование оболочки, когда она вам не нужна, только добавляет опасности и неэффективности. Например, если ваш пользователь введет "$(rm -rf ~)" в текстовое поле, он будет очень недоволен, когда вы запустите задание. Менее разрушительно, но более вероятно, если путь к каталогу или поисковый запрос содержат двойные кавычки ("
) персонаж.
Все это говорит, я согласен с trojanfoe, что вы должны делать это программно, а не запускать подпроцесс. Если NSDirectoryEnumerator
по какой-то причине не работает, вы можете использовать API POSIX/BSD.
Обновление в ответ на ваш обновленный вопрос:
Для запуска задачи в фоновом режиме вы можете использовать Grand Central Dispatch (GCD). Например, ваш -buttonSearch_Click:
метод может быть написан так:
- (IBAction)buttonSearch_Click:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
doSearch(directoryURL, _searchWordOutlet.stringValue, _textViewOutlet);
});
}
Тем не менее, вы не можете обновить графический интерфейс из фонового потока. Так что ваши doSearch()
Функция должна шунтировать любые манипуляции с текстовым представлением обратно в основной поток. Это можно сделать с помощью кода, подобного следующему:
dispatch_async(dispatch_get_main_queue(), ^{
[textView insertText:url.path];
[textView insertText:@"\n"];
});
Кстати, ваша проверка, если поисковый термин находится в пути, не совпадает с тем, что find
команда, с которой вы начали, делает. Вы проверяете весь путь, включая родительские каталоги, а find
Команда проверила только имя файла каждого элемента. Вы можете получить имя файла из NSURL
запросив его lastPathComponent
а не его path
,
Кроме того, чтобы выполнить проверку без учета регистра, если одна строка содержит другую, вы не должны использовать обе строки в нижнем регистре, а затем вызвать -containsString:
, Вы должны просто использовать -localizedCaseInsensitiveContainsString:
без нижнего регистра вручную. (Или, если вы не хотите, чтобы регистр не учитывал регистр символов, вы можете использовать -rangeOfString:options:
с NSCaseInsensitiveSearch
для вариантов.)