child_process.spawn () зависает, когда дочерний процесс запрашивает ввод - PullRequest
3 голосов
/ 17 апреля 2019

Я написал сценарий узла для управления развертыванием репозитория git в группе автоматического масштабирования AWS.

Скрипт использует child_process.spawn () для автоматизации git, клонирования репозиториев, тегов извлечения и т. Д.

Работает нормально, если git может найти подходящие учетные данные.Однако, если учетные данные не найдены автоматически, то порожденный процесс попытается запросить учетные данные и в этот момент зависнет.Даже Ctrl-C не может выйти.Весь сеанс оболочки должен быть закончен.

Вызов spawn () обернут в функцию для возврата Promise.Моя функция выглядит так ...

const cp = require('child_process');

let spawn_promise = (command, args, options, stream_output) => {
    return new Promise((resolve, reject) => {
        console.log(chalk.cyan(`${command} [${args}]`));

        let childProcess = cp.spawn(command, args, options);
        let std_out = '';
        let std_err = '';

        childProcess.stdout.on('data', function (data) {
            std_out += data.toString();
            if (stream_output)
                console.log(chalk.green(data.toString()));
        });
        childProcess.stderr.on('data', function (data) {
            std_err += data.toString();
            if (stream_output)
                console.log(chalk.red(data.toString()));
        });

        childProcess.on('close', (code) => {
            if (code === 0) {
                console.log(chalk.blue(`exit_code = ${code}`));
                return resolve(std_out);
            }
            else {
                console.log(chalk.yellow(`exit_code = ${code}`));
                return reject(std_err);
            }
        });

        childProcess.on('error', (error) => {
            std_err += error.toString();
            if (stream_output)
                console.log(chalk.red(error.toString()));
        });
    });
}

Я называю это так ...

return spawn_promise('git', ['fetch', '--all'], {env: process.env})
   .then(() => {
      ...

В основном это работает очень хорошо, и позволяет легко манипулировать выходными данными, ошибками и т. Д.

У меня проблемы с поиском хорошего способа обработки ввода, хотя, если это требуется для порожденного процесса.

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

Я могу решить проблему с вводом, выполнив это ...

let childProcess = cp.spawn(command, args, { stdio: [process.stdin, process.stdout, process.stderr] });

Это позволяет git правильно запрашивать учетные данные.Однако затем я теряю способность перехватывать выходные данные команды.

Как правильно справиться с этим?

Я должен также упомянуть, что эта функция также автоматизирует некоторые относительно длительные работы.процессы, для создания AMI и т. д. Это то, для чего предназначен параметр «stream_output».Я хочу иметь возможность просматривать вывод команды в режиме реального времени, а не ждать, пока процесс завершится.

1 Ответ

3 голосов
/ 21 апреля 2019

child_process имеет stdin для обработки ввода, и то же самое можно использовать для ввода ввода, когда child_process работает.

См. Ниже пример:

test.sh

#!/bin/sh

echo "Please enter something:"
read ch
echo "Thanks"

Когда я запускаю на этом терминале:

shell-input $ ./test.sh 
Please enter something:
something
Thanks
shell-input $ 

Когда я использую ваш код для запуска этого: test.js

const cp = require('child_process');
const chalk = require('chalk');

let spawn_promise = (command, args, options, stream_output) => {
    return new Promise((resolve, reject) => {
        console.log(chalk.cyan(`${command} [${args}]`));

        let childProcess = cp.spawn(command, args, options);
        let std_out = '';
        let std_err = '';

        childProcess.stdout.on('data', function (data) {
            std_out += data.toString();
            if (stream_output)
                console.log(chalk.green(data.toString()));
        });
        childProcess.stderr.on('data', function (data) {
            std_err += data.toString();
            if (stream_output)
                console.log(chalk.red(data.toString()));
        });

        childProcess.on('close', (code) => {
            if (code === 0) {
                console.log(chalk.blue(`exit_code = ${code}`));
                return resolve(std_out);
            }
            else {
                console.log(chalk.yellow(`exit_code = ${code}`));
                return reject(std_err);
            }
        });

        childProcess.on('error', (error) => {
            std_err += error.toString();
            if (stream_output)
                console.log(chalk.red(error.toString()));
        });
    });
}

spawn_promise('./test.sh',  { env: process.env})
   .then(() => {
   });

Output:

 $ node test.js 
./test.sh [[object Object]]

<stuck here>

Я изменяю ваш код для включения следующего:

...
        childProcess.stdout.on('data', function (data) {


            if (data == "Please enter something:\n")
            {
                childProcess.stdin.write("something\n");
                //childProcess.stdin.end(); // Call this to end the session
            }

            std_out += data.toString();
            if (stream_output)
                console.log(chalk.green(data.toString()));
        });
  ...

Затем я снова бегу:

$ node test.js 
./test.sh [[object Object]]
exit_code = 0

Это работает. В основном вам нужно выяснить, когда стандартный ввод ожидает ввода. Для этого вы можете использовать data событие stdout, а затем написать stdin. Если у вас нет учетных данных для записи, вы можете завершить сеанс, позвонив по номеру childProcess.stdin.end();

...