尽管有单独的echo语句,脚本输出仍被缓冲到一条消息中?

时间:2019-05-08 01:17:15

标签: node.js linux shell unix child-process

我有一个包含三个echo语句的shell脚本:

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的任何内容)

编辑:我已经尝试过unbufferstdbuf,但都不能用,如简单的测试所示。这是stdbuf尝试的示例,我尝试使用各种不同的参数值(基本上是所有可能的选项)。

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

要清楚一点,当我也仅使用三个简单的print语句从node运行python脚本时,就会发生此问题。因此,它与语言无关,尤其与bash脚本无关。这是关于在基于unix的系统上成功检测任何语言的脚本的单个输出的信息。如果这是C / C ++可以做的事情,而我必须从node入手,那么我愿意去那里。欢迎任何可行的解决方案。


编辑:最初,我通过将脚本输出通过管道传递到sed并使用s/$/uniqueString在每个单独输出的末尾插入标识符来解决了自己的问题只是将接收到的数据拆分到该标识符上。

我悬赏的答案将适用于单行输出,但不适用于多行输出。测试中的错误使我认为不是这种情况,而是事实。公认的答案是更好的解决方案,并且适用于任何大小的输出。但是,如果您无法控制脚本并且必须处理用户创建的脚本,那么我发现sed解决方案是唯一可行的方法。而且确实有效,

7 个答案:

答案 0 :(得分:5)

您可以使用作为节点API一部分提供的readline接口。更多信息,请点击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.shchmod +x test.sh脚本上执行的特权。

答案 1 :(得分:1)

bash和python底层的C库是按行缓冲stdout的库。 stdbufunbuffer可以解决这个问题,但操作系统不做缓冲。

例如,Linux分配了4096个字节作为node.js进程和bash进程之间管道的缓冲区。

事实是,管道的一端(node.js)上的进程无法看到另一端的单独写入(echo调用),这是没有道理的。这不是正确的设计(您可以通过单个文件而不是stdout进行通信)。

如果您坚持认为,可以尝试愚弄OS调度程序:如果几乎没有什么东西接近写入管道,那么它将在读取器进程(node.js)中进行调度,该进程将读取OS当前的内容缓冲。

我在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"

答案 2 :(得分:1)

我在上一个项目中遇到了同样的问题。我在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);
});

答案 3 :(得分:1)

如果您希望test.sh的输出始终按行发送,那么恕我直言,您最好的选择是使用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}`);
});

答案 4 :(得分:0)

请勿使用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")

重击:

#!/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

答案 5 :(得分:0)

有一个非常简单的解决方案。只需在您的bash脚本中添加sleep 1.on('data')处理程序就不会合并输出。

这样的脚本:

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

您的确切脚本(已修复缺少的require('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);
});

答案 6 :(得分:0)

如果您尝试拆分解释每条消息,这可能会有所帮助: (我对节点没有太多经验,对不起,如果我做错了事)

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')