Вывод скрипта буферизуется в одно сообщение, несмотря на отдельные операторы echo? - PullRequest
9 голосов
/ 08 мая 2019

У меня есть сценарий оболочки с тремя echo инструкциями:

echo 'first message'

echo 'second message'

echo 'third message'

Затем я запускаю этот сценарий в узле и собираю выходные данные с помощью этого кода:

var child = process.spawn('./test.sh');
child.stdout.on('data', data => {
   data = JSON.stringify(data.toString('utf8'));
   console.log(data);
});

Ноединственное число - "first message\nsecond message\nthird message\n", что является проблемой.Я ожидал трех выходов, а не одного, взятого вместе из-за некоторой формы буферизации.И я не могу просто разбить на новые строки, потому что отдельные выходы могут содержать новые строки.

Есть ли способ отличить сообщения отдельных операторов echo?(или другие выходные команды, например, printf, или что-либо, что приводит к записи данных в stdout или stderror)

Редактировать: я пробовал unbuffer и stdbuf, ни одна из них не работает, так как простоТестирование может продемонстрировать.Вот пример попытки stdbuf, которую я пытался использовать с различными значениями аргументов, по существу со всеми возможными вариантами.

 var child = process.spawn('stdbuf', ['-i0', '-o0', '-e0', './test.sh']);

Чтобы было ясно, эта проблема возникает, когда я запускаю скрипт на python изузел тоже всего с тремя простыми print операторами.Так что это не зависит от языка, а не от bash-скриптинга в частности.Речь идет об успешном обнаружении отдельных выходных данных скрипта на любом языке в системе на основе Unix.Если это то, что может сделать C / C ++, и мне нужно подключиться к этому с узла, я готов пойти туда.Любое работающее решение приветствуется.


Редактировать: Первоначально я решил проблему для себя, отправив вывод скрипта в sed и используя s/$/uniqueString для вставки идентификатора вконец каждого отдельного вывода, а затем просто разделение полученных данных по этому идентификатору.

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

Ответы [ 7 ]

5 голосов
/ 10 мая 2019

Вы можете использовать интерфейс readline, предоставляемый как часть API узлов. Больше информации здесь https://nodejs.org/api/readline.html#readline_event_line. Вы будете использовать spawn, поскольку, однако, передайте stdout в readline, чтобы он мог проанализировать строки. Не уверен, что это то, что вы собираетесь делать. Вот пример кода:

var process = require('child_process');
const readline = require('readline');

var child = process.spawn('./test.sh');

// Use readline interface
const readlinebyline = readline.createInterface({ input: child.stdout });

// Called when a line is received
readlinebyline.on('line', (line) => {
    line = JSON.stringify(line.toString('utf8'));
    console.log(line);
});

Выход:

"first message"
"second message"
"third message"

Если вы получаете сообщение об ошибке типа TypeError: input.on is not a function, убедитесь, что у вас есть права доступа к сценарию test.sh с помощью chmod +x test.sh.

1 голос
/ 17 мая 2019

Если вы ожидаете, что вывод из test.sh будет всегда посылаться по линии, тогда IMHO, ваш лучший выбор - использовать readline

const readline = require('readline');
const {spawn} = require('child_process');

const child = spawn('./test.sh');
const rl = readline.createInterface({
    input: child.stdout
});

rl.on('line', (input) => {
    console.log(`Received: ${input}`);
});
1 голос
/ 16 мая 2019

Я столкнулся с той же проблемой в предыдущем проекте.Я использовал переключатель интерпретации в операторе echo, а затем разделил строку на непечатаемый символ.

Пример:

echo -e 'one\u0016'

echo -e "two\u0016"

echo -e 'three\u0016'

Результат:

"one\u0016\ntwo\u0016\nthree\u0016\n"

И соответствующий Javascript:

var child = process.spawn('./test.sh');
child.stdout.on('data', data => {
   var value = data.toString('utf8');
   var values = value.split("\u0016\n").filter(item => item);
   console.log(values);
});
1 голос
/ 12 мая 2019

Библиотека C, которая лежит в основе bash и python, выполняет буферизацию stdout для каждой строки. stdbuf и unbuffer будут иметь дело с этим, но не с буферизацией, выполняемой операционной системой.

Linux, например, выделяет 4096 байт в качестве буфера для канала между вашим процессом node.js и процессом bash.

Правда в том, что у процесса на одном конце канала (node.js) нет честного способа увидеть отдельные записи (echo вызовы) на другом конце. Это неправильный дизайн (вы можете общаться через отдельные файлы вместо stdout).

Если вы настаиваете, вы можете попытаться обмануть планировщик ОС: если даже ничего не удастся даже близко записать в канал, он запланирует процесс чтения (node.js), который будет читать то, что в данный момент находится в ОС. буфер.

Я проверял это на Linux:

$ cat test.sh 
echo 'first message'
sleep 0.1
echo 'second message'
sleep 0.1
echo 'third message'
$ cat test.js 
const  child_process  = require('child_process');
var child = child_process.spawn(`./test.sh`);
child.stdout.on('data', data => {
   data = JSON.stringify(data.toString('utf8'));
   global.process.stdout.write(data); // notice global object
});
$ node test.js
"first message\n""second message\n""third message\n"
0 голосов
/ 17 мая 2019

Если вы пытаетесь разбить на части каждое сообщение, это может помочь: (У меня нет большого опыта работы с узлом, извините, если я что-то не так)

test.sh :

#!/bin/bash
echo -n 'first message'
echo -ne '\0'
echo -n 'second message'
echo -ne '\0'
echo -n 'third message'
echo -ne '\0'

узел :

var child = process.spawn('./test.sh');
var data_buffer  = Buffer.from([]);
var data_array   = [];
child.stdout.on('data', data => {
  data_buffer   += data;
  while (data_buffer.includes("\0")) {
    let i        = data_buffer.indexOf("\0");
    let s        = data_buffer.slice(0,i);
    data_array.push(s);
    data_buffer  = data_buffer.slice(i+1);
    let json     = JSON.stringify(s.toString('utf8'));
    console.log('--8<-------- split ------------');
    console.log('index: '+i);
    console.log('received: '+s);
    console.log('json: '+json);
    console.log(data_array);
  }
});

Это, по сути, будет использовать строки с разделителями NULL вместо символов с разделителями строк.Другой вариант - использовать IFS, но мне это не удалось.Этот метод избавит вас от необходимости использовать readline .

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

Чтобы это работало, конечно, вы должны убедиться, что у вас нет нулевых символовв ваших данных.Но вы можете изменить символ-разделитель, если вы это сделаете.

Этот подход, я думаю, более тщательный, ИМХО.

Если вам нужно python3 :

#!/usr/bin/python3
print("first message", end = '\x00')
print("second message", end = '\x00')
print("third message", end = '\x00')
0 голосов
/ 17 мая 2019

Существует очень простое решение для этого.Просто добавьте sleep 1 к вашему bash-скрипту, и обработчик .on('data') не объединит выходные данные.

Итак, скрипт такой:

#/bin/bash
echo 'first message'
sleep 1
echo 'second message'
sleep 1
echo 'third message'

И ваш точный скрипт (с исправлениемк отсутствующему требуется ('child_process');

var process = require('child_process');
var child = process.spawn('./test.sh');
child.stdout.on('data', data => {
   data = JSON.stringify(data.toString('utf8'));
   console.log(data);
});
0 голосов
/ 10 мая 2019

Не использовать console.log:

const  process_module  = require('child_process');

var child = process_module.spawn('./test.sh');
child.stdout.on('data', data => {
   process.stdout.write(data);
});

ОБНОВЛЕНИЕ (просто чтобы показать разницу между модулем process и глобальным объектом process):

const process = require('child_process');

var child = process.spawn(`./test.sh`);
child.stdout.on('data', data => {
   global.process.stdout.write(data); // notice global object
});

Файлы, которые я использовал для тестирования этого скрипта:

Python:

#!/usr/bin/env python

print("first message")
print("second message")
print("third message")

Bash:

#!/usr/bin/env bash

echo 'first message'
echo 'second message'
echo 'third message'

Выход:

first message
second message
third message

Убедитесь, что это исполняемые скрипты с:

chmod a+x test.sh
chmod a+x test.py
...