在 Node.js 中用子進程操做標準輸入/輸出

翻譯:瘋狂的技術宅
原文: http://2ality.com/2018/05/chi...

本文首發微信公衆號:jingchengyideng
歡迎關注,天天都給你推送新鮮的前端技術文章html


在本中,咱們在 Node.js 中把 shell 命令做爲子進程運行。而後異步讀取這些進程的 stdout 並寫入其 stdin。前端

在子進程中運行 shell 命令

首先從在子進程中運行 shell 命令開始:node

const {onExit} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: '+filePath);

  const childProcess = spawn('cat', [filePath],
    {stdio: [process.stdin, process.stdout, process.stderr]}); // (A)

  await onExit(childProcess); // (B)

  console.log('### DONE');
}
main();

解釋:git

  • 咱們用了 spawn(),它能夠使咱們在命令運行時訪問命令的 stdin,stdout 和 stderr。程序員

    • 在 A 行中,咱們將子進程的 stdin 鏈接到當前進程的 stdin。
    • B 行等待該過程完成。

等待子進程經過 Promise 退出

函數 onExit()以下所示。github

function onExit(childProcess: ChildProcess): Promise<void> {
  return new Promise((resolve, reject) => {
    childProcess.once('exit', (code: number, signal: string) => {
      if (code === 0) {
        resolve(undefined);
      } else {
        reject(new Error('Exit with error code: '+code));
      }
    });
    childProcess.once('error', (err: Error) => {
      reject(err);
    });
  });
}

子進程的實現

如下代碼用 @rauschma/stringio 異步寫入以 shell 命令運行的子進程的 stdin面試

const {streamWrite, streamEnd, onExit} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const sink = spawn('cat', [],
    {stdio: ['pipe', process.stdout, process.stderr]}); // (A)

  writeToWritable(sink.stdin); // (B)
  await onExit(sink);

  console.log('### DONE');
}
main();

async function writeToWritable(writable) {
  await streamWrite(writable, 'First line\n');
  await streamWrite(writable, 'Second line\n');
  await streamEnd(writable);
}

咱們爲 shell 命令生成一個名爲 sink 的獨立進程。用 writeToWritable 寫入 sink.stdin。它藉助 await 異步執行並暫停,以免緩衝區被消耗太多。
解釋:shell

  • 在A行中,咱們告訴 spawn() 經過 sink.stdin'pipe')訪問 stdin。 stdout 和 stderr 被轉發到 process.stdinprocess.stderr,如前面所述。
  • 在B行中不會 await 寫完成。而是 await 子進程 sink 完成。

接下來了解 streamWrite() 的工做原理。segmentfault

寫流操做的 promise

Node.js 寫流的操做一般涉及回調(參見文檔)。代碼以下。api

function streamWrite(
  stream: Writable,
  chunk: string|Buffer|Uint8Array,
  encoding='utf8'): Promise<void> {
    return new Promise((resolve, reject) => {
      const errListener = (err: Error) => {
        stream.removeListener('error', errListener);
        reject(err);
      };
      stream.addListener('error', errListener);
      const callback = () => {
        stream.removeListener('error', errListener);
        resolve(undefined);
      };
      stream.write(chunk, encoding, callback);
    });
}

streamEnd()的工做方式是相似的。

從子進程中讀取數據

下面的代碼使用異步迭代(C行)來讀取子進程的 stdout 中的內容:

const {chunksToLinesAsync, chomp} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: '+filePath);

  const source = spawn('cat', [filePath],
    {stdio: ['ignore', 'pipe', process.stderr]}); // (A)

  await echoReadable(source.stdout); // (B)

  console.log('### DONE');
}
main();

async function echoReadable(readable) {
  for await (const line of chunksToLinesAsync(readable)) { // (C)
    console.log('LINE: '+chomp(line))
  }
}

解釋:

  • A行:咱們忽略 stdin,但願經過流訪問 stdout 並將 stderr 轉發到process.stderr
  • B行:開始 awat 直到 echoReadable() 完成。沒有這個 awaitDONE 將會在調用 source.stdout 以前被輸出。

在子進程之間進行管道鏈接

在下面的例子中,函數transform() 將會:

  • source 子進程的 stdout 中讀取內容。

    • 將內容寫入 sink 子進程的 stdin

換句話說,咱們正在實現相似 Unix 管道的功能:

cat someFile.txt | transform() | cat

這是代碼:

const {chunksToLinesAsync, streamWrite, streamEnd, onExit}
  = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: '+filePath);

  const source = spawn('cat', [filePath],
    {stdio: ['ignore', 'pipe', process.stderr]});
  const sink = spawn('cat', [],
    {stdio: ['pipe', process.stdout, process.stderr]});

  transform(source.stdout, sink.stdin);
  await onExit(sink);

  console.log('### DONE');
}
main();

async function transform(readable, writable) {
  for await (const line of chunksToLinesAsync(readable)) {
    await streamWrite(writable, '@ '+line);
  }
  await streamEnd(writable);
}

擴展閱讀


歡迎繼續閱讀本專欄其它高贊文章:


本文首發微信公衆號:jingchengyideng

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

相關文章
相關標籤/搜索