Подтвердить что ты не робот

Node.js порождает дочерний процесс в интерактивном режиме с отдельными потоками stdout и stderr

Рассмотрим следующую программу C (test.c):

#include <stdio.h>

int main() {
  printf("string out 1\n");
  fprintf(stderr, "string err 1\n");
  getchar();
  printf("string out 2\n");
  fprintf(stderr, "string err 2\n");
  fclose(stdout);
}

Для этого нужно напечатать строку в stdout, строку в stderr, затем дождаться ввода пользователя, затем другую строку в stdout и другую строку в stderr. Очень просто! При компиляции и запуске в командной строке вывод программы по завершении (ввод пользователя принимается для getchar()):

$ ./test 
string out 1
string err 1

string out 2
string err 2

При попытке запустить эту программу в качестве дочернего процесса с помощью nodejs со следующим кодом:

var TEST_EXEC = './test';

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

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

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

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.stdin.write('\n');
}, 1000);

Выход выглядит следующим образом:

$ nodejs test.js 
stderr: string err 1

stdout: string out 1
string out 2

stderr: string err 2

Очень отличается от вывода, как видно при запуске. /test в терминале. Это связано с тем, что программа. /test не запускается в интерактивной оболочке, когда она порождена nodejs. Поток stdout test.c буферизуется, и при запуске в терминале, как только будет достигнут \n, буфер очищается, но когда порождается таким образом с помощью node, буфер не очищается. Это может быть разрешено путем сброса stdout после каждой печати или изменения потока stdout для буферизации, чтобы он немедленно сбрасывал все. Предполагая, что источник test.c недоступен или не поддается изменению, ни один из указанных двух способов очистки не может быть реализован.

Затем я начал искать эмуляцию интерактивной оболочки, там pty.js(псевдотерминал), который выполняет хорошую работу, например:

var spawn = require('pty.js').spawn;
var test = spawn(TEST_EXEC);

test.on('data', function (data) {
  console.log('data: ' + data);
});

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.write('\n');
}, 1000);

Какие выходы:

$ nodejs test.js
data: string out 1
string err 1

data: 

data: string out 2
string err 2

Однако и stdout, и stderr объединяются (как вы могли бы видеть при запуске программы в терминале), и я не могу придумать способ разделения данных от потоков.

Итак, вопрос..

Можно ли использовать nodejs для достижения результата, как видно при запуске. /test без изменения кода test.c? Или путем эмуляции терминала или нереста процесса или любого другого метода?

Ура!

4b9b3361

Ответ 1

Я просто пересматривал это, так как теперь имеется опция "shell" для команды spawn в node начиная с версии 5.7.0. К сожалению, похоже, что нет возможности создавать интерактивную оболочку (я также пробовал с shell: '/bin/sh -i', но без радости). Однако я нашел этот, который предлагает использовать 'stdbuf', позволяющий вам изменить параметры буферизации программы, которую вы хотите запустить. Установка их на 0 во всех случаях создает небуферизованный вывод для всех потоков, и они все еще сохраняются отдельно.

Здесь обновленный javascript:

var TEST_EXEC = './test';

var spawn = require('child_process').spawn;
var test = spawn('stdbuf', ['-i0', '-o0', '-e0', TEST_EXEC]);

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

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

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.stdin.write('\n');
}, 1000);

Похоже, что это не предустановлено на OSX и, конечно, недоступно для Windows, могут быть похожими альтернативами.

Ответ 2

Я попробовал ответить user568109, но это не работает, что имеет смысл, поскольку канал копирует только данные между потоками. Следовательно, он получает только process.stdout, когда буфер очищается... Появится следующее:

var TEST_EXEC = './test';

var spawn = require('child_process').spawn;
var test = spawn(TEST_EXEC, [], { stdio: 'inherit' });

//the following is unfortunately not working 
//test.stdout.on('data', function (data) {
//  console.log('stdout: ' + data);
//});

Обратите внимание, что это эффективно разделяет stdio с процессом node. Не уверен, можете ли вы с этим жить.

Ответ 3

Вы можете сделать это:

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

test.stdin.pipe(process.stdin);
test.stdout.pipe(process.stdout);
test.stderr.pipe(process.stderr);

Когда вы используете события stdout и stderr для печати вывода на console.log, вы получите беспорядочный вывод из-за асинхронного выполнения функций. Выход будет упорядочен для потока независимо, но вывод может все же чередоваться между stdin, stdout и stderr.