Exec: отображать стандартный вывод "в реальном времени"

198

У меня есть такой простой сценарий:

var exec = require('child_process').exec;

exec('coffee -cw my_file.coffee', function(error, stdout, stderr) {
    console.log(stdout);
});

где я просто выполняю команду для компиляции файла сценария кофе. Но stdout никогда не отображается в консоли, потому что команда никогда не заканчивается (из-за опции -w кофе). Если я выполняю команду прямо с консоли, я получаю такое сообщение:

18:05:59 - compiled my_file.coffee

Мой вопрос: можно ли отображать эти сообщения с помощью node.js exec? Если да, то как? !

Благодарность

Мравей
источник
1
Я пришел сюда в поисках вывода stdout из исполняемого файла Python. Обратите внимание, что все нижеприведенное будет работать, но вам нужно запустить python с параметром «-u», чтобы сделать вывод небуферизованным и, таким образом, получать обновления в реальном времени.
Энди

Ответы:

274

Не используйте exec. Использование, spawnкоторое является EventEmmiterобъектом. Затем вы можете слушать stdout/ stderrevents ( spawn.stdout.on('data',callback..)) по мере их появления .

Из документации NodeJS:

var spawn = require('child_process').spawn,
    ls    = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', function (data) {
  console.log('stdout: ' + data.toString());
});

ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data.toString());
});

ls.on('exit', function (code) {
  console.log('child process exited with code ' + code.toString());
});

exec буферизует вывод и обычно возвращает его, когда команда завершила выполнение.

Пориа Азими
источник
25
Очень хорошо. К вашему сведению: аргумент обратного вызова stdout / stderr events 'data' является буфером, поэтому вызывайте его с помощью .toString ()
SergeL
4
Для тех из вас, кто не может заставить spawn работать в Windows, взгляните на этот отличный ответ .
tomekwi
22
exec также является EventEmitter по крайней мере в последней версии.
Николай Ценков
4
Также имейте в виду, что обратный вызов не будет вызываться всякий раз, когда программа выводит новую строку. Если вы хотите получать «события» от дочернего процесса, этот процесс должен очистить буфер ( flush(stdout);в C), чтобы запускать события в Node.js.
Julian F. Weinert
7
+1 на exec также является EventEmitter ... потратил 2 часа на рефакторинг моей строки в массив args (очень длинная и сложная командная строка ffmpeg) ... только чтобы узнать, что мне действительно не нужно.
deadconversations
185

exec также вернет объект ChildProcess, который является EventEmitter.

var exec = require('child_process').exec;
var coffeeProcess = exec('coffee -cw my_file.coffee');

coffeeProcess.stdout.on('data', function(data) {
    console.log(data); 
});

ИЛИ pipeстандартный вывод дочернего процесса к основному.

coffeeProcess.stdout.pipe(process.stdout);

ИЛИ наследовать stdio с помощью spawn

spawn('coffee -cw my_file.coffee', { stdio: 'inherit' });
Натанаэль Смит
источник
35
Похоже, это можно упростить, просто используя pipe:coffeeProcess.stdout.pipe(process.stdout);
Эрик Фриз,
3
Комментарий @ EricFreese - это то, что я искал, потому что я хотел использовать функцию замены символов stdout (использование транспортира в скрипте узла)
LoremIpsum
19
Simpler: spawn(cmd, argv, { stdio: 'inherit' }). См. Различные примеры на nodejs.org/api/child_process.html#child_process_options_stdio .
Morgan Touverey Quilling
3
+1 за предложение @ MorganTouvereyQuilling использовать spawnс stdio: 'inherit'. Он производит более точный вывод, чем execи piping stdout/ stderr, например, при отображении информации о ходе выполнения из файла git clone.
Ливвен
62

Уже есть несколько ответов, однако ни один из них не упоминает лучший (и самый простой) способ сделать это, а именно использование spawnи { stdio: 'inherit' }опцию . Кажется, что он дает наиболее точный результат, например, при отображении информации о ходе выполнения из файла git clone.

Просто сделайте это:

var spawn = require('child_process').spawn;

spawn('coffee', ['-cw', 'my_file.coffee'], { stdio: 'inherit' });

Благодарим @MorganTouvereyQuilling за указание на это в этом комментарии .

Ливвен
источник
1
Я обнаружил, что когда подпроцесс использует форматированный вывод, такой как цветной текст, он stdio: "inherit"сохраняет это форматирование, а child.stdout.pipe(process.stdout)не сохраняет .
Рикки Гибсон
Это отлично сохраняет вывод даже для процессов со сложным выводом, таких как индикаторы выполнения при установке npm. Потрясающие!
Dave Koo
1
почему это не принятый ответ? это было единственное, что сработало для меня, и это всего лишь 2 чертовых строчки !!!
Lincoln
Этот совет был полезен при запуске некоторых приложений командной строки Symfony, которые используют индикаторы выполнения. Ура.
Halfstop
Это должен быть принятый ответ - единственное, что сохраняет идеальное выходное представление, и это самое простое? да, пожалуйста
evnp
23

Вдохновленный ответом Натанаэля Смита и комментарием Эрика Фриза, это может быть очень просто:

var exec = require('child_process').exec;
exec('coffee -cw my_file.coffee').stdout.pipe(process.stdout);
Тайлер Лонг
источник
Кажется, это нормально работает для простых команд, таких как, lsно не работает для более сложных команд, таких как npm install. Я даже пробовал связать как stdout, так и stderr с соответствующими объектами процесса.
linuxdan
@linuxdan это может быть потому, что npm пишет в stderr (я видел, как некоторые пишут там индикатор выполнения). вы можете также использовать stderr или расширить решение Tongfa для прослушивания на stderr.
Серджиу
@linuxdan Из того, что я видел, самый надежный способ - spawn(command, args, { stdio: 'inherit' })это предложенный здесь stackoverflow.com/questions/10232192/…
Ливвен
Лучший ответ, спасибо за это. Работал как шарм
Абхишек Шарма
21

Я просто хотел бы добавить, что одна небольшая проблема с выводом строк буфера из порожденного процесса console.log()заключается в том, что он добавляет новые строки, которые могут распределять вывод созданного вами процесса на дополнительные строки. Если вы выведете stdoutили stderrс process.stdout.write()вместо console.log(), то вы получите вывод консоли от порожденного процесса «как есть».

Я видел это решение здесь: Node.js: печать на консоль без конечной новой строки?

Надеюсь, что это поможет кому-то, использующему указанное выше решение (которое отлично подходит для вывода в реальном времени, даже если оно взято из документации).

Кевин Тельер
источник
1
Для еще более точного использования вывода spawn(command, args, { stdio: 'inherit' }), как предлагает @MorganTouvereyQuilling здесь stackoverflow.com/questions/10232192/…
Ливвен
12

Я счел полезным добавить в мои утилиты собственный сценарий exec.

utilities.js

const { exec } = require('child_process')

module.exports.exec = (command) => {
  const process = exec(command)

  process.stdout.on('data', (data) => {
    console.log('stdout: ' + data.toString())
  })

  process.stderr.on('data', (data) => {
    console.log('stderr: ' + data.toString())
  })

  process.on('exit', (code) => {
    console.log('child process exited with code ' + code.toString())
  })
}

app.js

const { exec } = require('./utilities.js')

exec('coffee -cw my_file.coffee')
IanLancaster
источник
5

Изучив все остальные ответы, я пришел к следующему:

function oldSchoolMakeBuild(cb) {
    var makeProcess = exec('make -C ./oldSchoolMakeBuild',
         function (error, stdout, stderr) {
             stderr && console.error(stderr);
             cb(error);
        });
    makeProcess.stdout.on('data', function(data) {
        process.stdout.write('oldSchoolMakeBuild: '+ data);
    });
}

Иногда dataбудет несколько строк, поэтому oldSchoolMakeBuildзаголовок будет отображаться один раз для нескольких строк. Но меня это не беспокоило настолько, чтобы это изменить.

Тонгфа
источник
3

child_process.spawn возвращает объект с потоками stdout и stderr. Вы можете нажать на поток stdout, чтобы прочитать данные, которые дочерний процесс отправляет обратно в Node. stdout, являющийся потоком, имеет «данные», «конец» и другие события, которые имеют потоки. spawn лучше всего использовать, когда вы хотите, чтобы дочерний процесс возвращал большой объем данных в Node - обработка изображений, чтение двоичных данных и т. д.

так что вы можете решить свою проблему с помощью child_process.spawn, как показано ниже.

var spawn = require('child_process').spawn,
ls = spawn('coffee -cw my_file.coffee');

ls.stdout.on('data', function (data) {
  console.log('stdout: ' + data.toString());
});

ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data.toString());
});

ls.on('exit', function (code) {
  console.log('code ' + code.toString());
});
Адеохо Эммануэль ИММ
источник
1

Вот вспомогательная функция async, написанная на машинописном тексте, которая, кажется, помогает мне. Я думаю, это не сработает для долгоживущих процессов, но все же может кому-то пригодиться?

import * as child_process from "child_process";

private async spawn(command: string, args: string[]): Promise<{code: number | null, result: string}> {
    return new Promise((resolve, reject) => {
        const spawn = child_process.spawn(command, args)
        let result: string
        spawn.stdout.on('data', (data: any) => {
            if (result) {
                reject(Error('Helper function does not work for long lived proccess'))
            }
            result = data.toString()
        })
        spawn.stderr.on('data', (error: any) => {
            reject(Error(error.toString()))
        })
        spawn.on('exit', code => {
            resolve({code, result})
        })
    })
}
Swaner
источник