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 для вариантов.)

Другие вопросы по тегам