NSTask с несколькими входными каналами

Я пытаюсь использовать каналы для обработки команды, требующей нескольких входных данных, но не совсем уверен, как это сделать. Вот фрагмент того, что я пытаюсь сделать. Я знаю, как обращаться с первым входом, но не понимаю, что делать со вторым входом -newstdinpass

NSTask *task = [[NSTask alloc] init];
NSPipe *pipe = [NSPipe pipe];

[task setLaunchPath: @"/bin/sh"];
[task setArguments: [NSArray arrayWithObjects: @"-c", @"/usr/bin/hdiutil chpass -oldstdinpass -newstdinpass /path/to/dmg", nil]];
[task setStandardInput:pipe];
[task launch];

[[pipe fileHandleForWriting] writeData:[@"thepassword" dataUsingEncoding:NSUTF8StringEncoding]];
[[pipe fileHandleForWriting] closeFile];

[task waitUntilExit];
[task release];

Итак, я знаю, что использование hdiutil таким образом немного хакерское, но с точки зрения каналов, правильно ли я это делаю?

Спасибо.

ОБНОВЛЕНИЕ: если другие задаются вопросом об этом, быстрое решение моей проблемы - передать строку с нулевым завершением, как указал Кен Томасес ниже. Используйте [[NSString stringWithFormat:@"oldpass\0newpass\0"] dataUsingEncoding:NSUTF8StringEncoding] в трубу. Теперь еще нужно научиться соединять несколько NSTasks трубами...


person Daniel    schedule 19.04.2012    source источник


Ответы (2)


Вы можете создать несколько NSTask и кучу NSPipe и соединить их вместе, или вы можете использовать трюк sh -c, чтобы передать оболочке команду, позволить ей проанализировать ее и настроить все IPC.

Пример:

NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: @"/bin/sh"];

NSArray *arguments;
arguments = [NSArray arrayWithObjects: @"-c",
                     @"cat /usr/share/dict/words | grep -i ham | rev | tail -5", nil];
[task setArguments: arguments];
// and then do all the other jazz for running an NSTask.

Ссылка: http://borkware.com/quickies/one?topic=nstask


Теперь, что касается "правильной" функции выполнения команды, вот та, которую я обычно использую...

Код:

/*******************************************************
 *
 * MAIN ROUTINE
 *
 *******************************************************/

- (void)runCommand:(NSString *)cmd withArgs:(NSArray *)argsArray
{
    //-------------------------------
    // Set up Task
    //-------------------------------

    if (task) { [self terminate]; }

    task = [[NSTask alloc] init];
    [task setLaunchPath:cmd];
    [task setArguments:argsArray];

    [task setStandardOutput:[NSPipe pipe]];
    [task setStandardError:[task standardOutput]];

    //-------------------------------
    // Set up Observers
    //-------------------------------

    [PP_NOTIFIER removeObserver:self];
    [PP_NOTIFIER addObserver:self 
                    selector:@selector(commandSentData:) 
                        name: NSFileHandleReadCompletionNotification 
                      object: [[task standardOutput] fileHandleForReading]];

    [PP_NOTIFIER addObserver:self 
                    selector:@selector(taskTerminated:) 
                        name:NSTaskDidTerminateNotification 
                      object:nil];

    //-------------------------------
    // Launch
    //-------------------------------
    [[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];

    [task launch];
}

/*******************************************************
 *
 * OBSERVERS
 *
 *******************************************************/

- (void)commandSentData:(NSNotification*)n
{
    NSData* d;
    d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];

    if ([d length])
    {
        NSString* s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];

        NSLog(@"Received : %@",s);
    }

    [[n object] readInBackgroundAndNotify]; 
}

- (void)taskTerminated:(NSNotification*)n
{
    [task release];
    task = nil;
}
person Dr.Kameleon    schedule 19.04.2012
comment
Спасибо за ваш код. Я все еще немного запутался в том, как объединить несколько задач для одной и той же команды, но я возьму на себя труд. - person Daniel; 19.04.2012
comment
Используйте один канал в качестве стандартного вывода одной задачи и стандартного ввода следующей. Затем еще один канал соединяет выход второй задачи со входом третьей. И т. д. Если вы хотите передать ввод всей цепочке, создайте канал и настройте его как ввод для первой задачи и напишите в него, как вы уже сделали. Если вам нужен вывод из всей цепочки, создайте конвейер и установите его в качестве стандартного вывода последней задачи в цепочке и читайте из него. - person Ken Thomases; 20.04.2012
comment
вы спасли день - person Charlton Provatas; 15.12.2017

Ваше использование трубы кажется мне правильным.

Я не уверен, почему вы используете /bin/sh. Просто настройте NSTask так, чтобы его путь запуска был @"/usr/bin/hdiutil", а его аргументы были массивом @"chpass", @"-oldstdinpass", @"-newstdinpass" и @"/path/to/dmg". Это намного безопаснее. Например, что, если путь к dmg содержит символ, интерпретируемый оболочкой, например $?

Если вы специально не хотите воспользоваться преимуществами оболочки, не используйте оболочку.

person Ken Thomases    schedule 19.04.2012
comment
Это хороший совет, но он по-прежнему не касается того, как вы можете отправить значение -oldstdinpass и значение -newstdinpass через один канал stdin. - person Rob Keniger; 19.04.2012
comment
@Кен, это хороший совет. На самом деле у меня были странные проблемы с NSTask, который не работал правильно с путем запуска, установленным на @"/usr/bin/hdiutil", и с передачей глагола create в качестве аргумента. Я попробую для этого способ без оболочки. - person Daniel; 19.04.2012
comment
@RobKeniger, на самом деле я не смотрел на детали команды hdiutil и на то, как она берет пароль. Он утверждает, что принимает их по порядку, завершая нулем (не заканчивая новой строкой). Таким образом, можно написать оба на канале, прежде чем закрыть его. - person Ken Thomases; 19.04.2012
comment
Кажется, работает строка с завершением NULL! @Rob, ты можешь передать [[NSString stringWithFormat:@"oldpass\0newpass\0"] dataUsingEncoding:NSUTF8StringEncoding] в канал. - person Daniel; 20.04.2012