You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:通过Node.js在浏览器实时展示Bash输出失败

解决Bash脚本输出实时显示到浏览器的问题

你遇到的核心问题是:Node.js的exec会等待子进程完全结束才返回所有输出,且HTTP请求默认是一次性响应,无法分段推送数据;同时Bash管道存在输出缓冲,导致内容不能即时流出。下面是分步解决方案:

第一步:修改Node.js服务器,用Spawn+SSE实现实时推送

替换exec为spawn(可实时捕获子进程输出),并通过Server-Sent Events(SSE) 向浏览器单向推送数据,保持HTTP连接打开以实时传输内容。

修改后的server.js代码:

const http = require('http');
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    const indexPath = path.join(__dirname, 'public', 'index.html');
    fs.readFile(indexPath, 'utf8', (err, content) => {
      if (err) {
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end('Error reading index.html');
        return;
      }
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(content);
    });
  } else if (req.url === '/stream_output') {
    // 配置SSE响应头,保持连接并禁用缓存
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    });

    // 用spawn启动脚本,实时捕获输出
    const scriptProcess = spawn('./dispcount.sh');

    // 监听子进程标准输出
    scriptProcess.stdout.on('data', (data) => {
      const lines = data.toString().split('\n').filter(line => line.trim() !== '');
      lines.forEach(line => {
        // 按SSE格式发送数据:data: 内容\n\n
        res.write(`data: ${line}\n\n`);
      });
    });

    // 监听子进程错误输出
    scriptProcess.stderr.on('data', (data) => {
      res.write(`data: [ERROR] ${data.toString()}\n\n`);
    });

    // 子进程结束时关闭连接
    scriptProcess.on('close', (code) => {
      res.write(`data: [PROCESS ENDED] Exit code: ${code}\n\n`);
      res.end();
    });

    // 客户端断开连接时终止子进程
    req.on('close', () => {
      scriptProcess.kill();
    });
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not found');
  }
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

第二步:优化Bash脚本,消除输出缓冲

默认管道中的命令输出会被缓冲,需强制关闭缓冲确保内容实时流出。修改dispcount.sh:

#!/bin/bash

# 使用stdbuf关闭输入、输出、错误的缓冲,确保实时输出
stdbuf -i0 -o0 -e0 ./a.out 2>&1 | while IFS= read -r counter_value; do
    echo "Counter value is: $counter_value"
done

stdbuf -i0 -o0 -e0强制关闭所有流的缓冲,避免C++程序输出被管道缓存。

第三步:修改前端代码,用SSE接收数据并实现单行刷新

改用浏览器原生的EventSource接收SSE推送,实现类似终端\r的单行刷新效果(覆盖当前行而非追加)。

修改后的index.html代码:

<!DOCTYPE html>
<html>
  <head>
    <title>Bash Script Output</title>
  </head>
  <body>
    <h1>Bash Script Output:</h1>
    <pre id="output">Click "Start" to begin...</pre>
    <button id="startButton">Start</button>
    <button id="stopButton" disabled>Stop</button>

    <script>
      const outputElement = document.getElementById('output');
      const startBtn = document.getElementById('startButton');
      const stopBtn = document.getElementById('stopButton');
      let eventSource = null;

      startBtn.addEventListener('click', () => {
        startBtn.disabled = true;
        stopBtn.disabled = false;
        outputElement.textContent = "Running...";

        // 建立SSE连接
        eventSource = new EventSource('/stream_output');

        // 接收实时数据并刷新单行内容
        eventSource.onmessage = (event) => {
          outputElement.textContent = event.data;
          // 若需要追加内容而非覆盖,替换为:
          // outputElement.textContent += event.data + '\n';
        };

        // 处理连接错误
        eventSource.onerror = (error) => {
          outputElement.textContent += `\n[ERROR] Connection failed: ${error.message}`;
          eventSource.close();
          resetButtons();
        };

        // 处理连接关闭
        eventSource.onclose = () => {
          resetButtons();
        };
      });

      stopBtn.addEventListener('click', () => {
        if (eventSource) {
          eventSource.close();
        }
        outputElement.textContent += '\n[PROCESS STOPPED]';
        resetButtons();
      });

      function resetButtons() {
        startBtn.disabled = false;
        stopBtn.disabled = true;
      }
    </script>
  </body>
</html>

关键说明

  • SSE优势:适合单向实时推送,无需复杂的双向通信逻辑,浏览器原生支持。
  • Spawn vs Exec:spawn实时返回子进程输出,而exec会缓存所有输出直到进程结束。
  • 无缓冲输出:stdbuf确保C++程序和Bash脚本的输出不会被系统缓存,即时传递到Node.js。
  • 单行刷新:通过直接覆盖textContent模拟终端的行刷新效果,若需要保留历史记录可改为追加模式。

内容的提问来源于stack exchange,提问作者Chigid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:35:43