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);
}

Что должно вывести строку на стандартный вывод, строку на стандартный вывод, затем дождаться ввода пользователя, затем еще одну строку на стандартный вывод и еще одну строку на стандартный вывод. Очень просто! При компиляции и запуске в командной строке вывод программы по завершении (пользовательский ввод получен для 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, буфер сбрасывается, но при создании таким образом с помощью узла буфер не сбрасывается. Эту проблему можно решить, либо сбрасывая стандартный вывод после каждой печати, либо изменяя поток стандартного вывода на небуферизованный, чтобы он немедленно сбрасывал все данные. Предполагая, что источник 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? Либо эмуляцией терминала, либо созданием процесса, либо любым другим методом?

Ваше здоровье!


person gratz    schedule 11.03.2013    source источник
comment
Спасибо, что приложили много усилий для описания проблемы! У меня была такая же проблема при запуске чужого скрипта python из узла, но я ничего не знал о лежащей в основе механике буферизации. По-видимому (мои навыки python +/- отсутствуют), простое использование python -u включает небуферизованный ввод-вывод и решает мою проблему!   -  person Matthias    schedule 29.04.2013
comment
@gratz Вы справились с этой проблемой? Я столкнулся с аналогичной проблемой. Вы можете это сделать из stackoverflow.com/questions/42130677/   -  person Joey Yi Zhao    schedule 15.02.2017


Ответы (3)


Я попробовал ответить 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 с процессом узла. Не уверен, что ты сможешь с этим жить.

person Matthias    schedule 29.04.2013
comment
Использование spawn() с опцией { stdio: 'inherit' } действительно позволяет чередовать вывод в родительские потоки, но - по крайней мере, с 0.10.10 - таким образом вы теряете возможность прослушивать потоки через события: из документа Свойства .stdin, .stdout, .stderr на объектах дочернего процесса: если дочерние потоки stdio используются совместно с родительским, то это не будет установлено. nodejs.org/api/child_process.html#child_process_child_stdout. Другими словами: с 0.10.10 ваш код не работает при доступе к свойству .stdout. Я буду рад поставить +1 к вашему ответу, если вы это исправите. - person mklement0; 06.06.2013
comment
Ты прав! Это отстой; Я не думаю, что есть что-то вроде события data в доступном для записи потоке, таком как process.stdout, поэтому я не знаю, сможете ли вы каким-то образом добраться до этого вывода :-( - person Matthias; 14.06.2013
comment
+1 за использование техники spawn() + { stdio: 'inherit' } (даже если она не решает проблему OP). Что касается прослушивания записей: кто-то нашел обходной путь для самого процесса путем "подкласса" метода process.stdout.write(), но, к сожалению, он не работает для того, что пишут дочерние процессы в общие потоки: gist.github.com/bsatrom/1349384 - person mklement0; 14.06.2013

Я только что возвращался к этому, так как теперь для команды spawn в узле, начиная с версии 5.7.0, доступна опция «оболочки». К сожалению, похоже, что нет возможности создать интерактивную оболочку (я тоже пробовал с shell: '/bin/sh -i', но без радости). Однако я только что нашел this, который предлагает использовать '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, хотя могут быть аналогичные альтернативы.

person gratz    schedule 03.03.2016
comment
Этот трюк с 'stdbuf' решил проблему, с которой я столкнулся с буферизацией stdout команды, запущенной внутри порожденной оболочки (с использованием Node v0.10.29 на Raspbian Jessie). Большое спасибо, gratz! - person mvanallen; 21.02.2017
comment
Рад, что это помогло @mvanallen - person gratz; 21.02.2017

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

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, вы получите беспорядочный вывод из-за асинхронного выполнения функций. Вывод будет упорядочен для потока независимо, но вывод все равно может чередоваться между _5 _, _ 6_ и stderr.

person user568109    schedule 11.03.2013
comment
Если я что-то не упустил, как я могу программно использовать выходные данные таким образом? - person gratz; 12.03.2013
comment
Вы можете передать стандартные потоки «test» другому процессу, скажем, процессу «a» (здесь это сам узел), результат которого будет точно таким же, как у «test», при этом ваш код будет по-прежнему использоваться для прослушивания результатов теста. - person user568109; 12.03.2013
comment
Но чтобы получить стандартный вывод теста при достижении '\ n', процесс должен быть терминалом или псевдотерминалом (pty), чтобы гарантировать, что тестовые каналы будут сбрасываться, как если бы они выполнялись в интерактивной оболочке. Любой из них будет означать теряете различие между stdout и stderr, как в приведенных выше примерах? - person gratz; 12.03.2013
comment
Это было бы очень полезно: github.com/joyent/node/issues/2754 И вот цель - заставить приложение думать, что оно работает в интерактивном терминале, только без объединенных stdout и stderr: stackoverflow.com/questions/1401002/ - person gratz; 13.03.2013
comment
разве вы не имеете в виду process.stdin.pipe(test.stdin)? - person Rainb; 07.01.2021