在 Node.js 中用子进程操作标准输入/输出

news/2024/7/2 23:19:39
翻译:疯狂的技术宅
原文:http://2ality.com/2018/05/chi...

本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章


在本中,我们在 Node.js 中把 shell 命令作为子进程运行。然后异步读取这些进程的 stdout 并写入其 stdin。

在子进程中运行 shell 命令

首先从在子进程中运行 shell 命令开始:

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

解释:

  • 我们用了 spawn(),它可以使我们在命令运行时访问命令的 stdin,stdout 和 stderr。

    • 在 A 行中,我们将子进程的 stdin 连接到当前进程的 stdin。
    • B 行等待该过程完成。

等待子进程通过 Promise 退出

函数 onExit()如下所示。

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 异步执行并暂停,以避免缓冲区被消耗太多。
解释:

  • 在A行中,我们告诉 spawn() 通过 sink.stdin'pipe')访问 stdin。 stdout 和 stderr 被转发到 process.stdinprocess.stderr,如前面所述。
  • 在B行中不会 await 写完成。而是 await 子进程 sink 完成。

接下来了解 streamWrite() 的工作原理。

写流操作的 promise

Node.js 写流的操作通常涉及回调(参见文档)。代码如下。

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

扩展阅读

  • 博客:“通过 Node.js 的异步迭代读取流”
  • “探索ES2018和ES2019”中的“异步迭代 一章
  • “探索ES2016和ES2017”中的“异步功能” 一章

欢迎继续阅读本专栏其它高赞文章:

  • 12个令人惊叹的CSS实验项目
  • 世界顶级公司的前端面试都问些什么
  • CSS Flexbox 可视化手册
  • 过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!
  • 从设计者的角度看 React
  • CSS粘性定位是怎样工作的
  • 一步步教你用HTML5 SVG实现动画效果
  • 程序员30岁前月薪达不到30K,该何去何从
  • 第三方CSS安全吗?
  • 谈谈super(props) 的重要性

本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章



http://lihuaxi.xjx100.cn/news/236107.html

相关文章

Python基础09-字符串格式化

字符串格式化。主要是%格式&#xff0c;format格式化方法&#xff0c;具体写在代码例子的注释里。 msg list() # %s 接收字符串 msg.append("i am %s, which is a database." % "mysql") msg.append("i am %s, which is a %s." % ("db2&q…

个人项目-小学四则运算 “软件”之初版

本次作业要求来自&#xff1a;https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/2166 我的github远程仓库的地址&#xff1a;https://github.com/yanyuluu/yanyuluu/tree/master/ruanjiangc 第一部分&#xff1a;要求 具体要求&#xff1a;任何编程语言都可以&#xf…

单例测试模式中【饿汉式】与【懒汉式】的区别

package day25.thread;/** /*** author Mr Chen* create 2018-10-09 18:37* 单例测试模式&#xff1a;保证类在内存中只有一个对象*/ public class Dome01 {public static void main(String[] args){Singleton s1 Singleton.s; //成员变量被私有&#xf…

Python基础10-函数基础

目录 函数的定义 函数的返回值 函数的参数 参数的传递 参数的默认值 可变长参数 全局变量与局部变量 函数嵌套定义 风湿理论——函数即变量 函数的定义 定义函数的关键字def。函数名&#xff0c;本例函数名是test。小括号里面是参数。冒号后缩进的代码块是函数内容。…

电脑录音软件哪个好,怎么用电脑录音

如今科技迅速发展&#xff0c;不仅唱歌的时候喜欢录音&#xff0c;就连追剧看电视都喜欢把一些经典或者搞笑的音频录制下来&#xff0c;很多喜剧给我们的休闲时光带来了欢声笑语&#xff0c;碰到经典的对话或者旁白总想录制下来&#xff0c;那电脑录音软件哪个好&#xff0c;怎…

Python基础11-函数式编程与内置函数

目录 函数即变量 lambda关键字定义匿名函数 高阶函数 内置函数map 内置函数filter 内置函数reduce 内置函数看文档 函数即变量 书接上回&#xff0c;Python里面&#xff0c;函数就是变量&#xff0c;可以被当成普通变量一样作为返回值&#xff0c;调用。 def foo():pr…

排序学习之---快速排序

一、前言 快速排序是一种交换排序&#xff0c;它由C. A. R. Hoare在1962年提出。 二、算法思想 快速排序的基本思想是&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff1a;分割点左边都是比它小的数&#xff0c;右边都是比它大的数。 然后再按此方法对这两部…

vue问题

问题一&#xff1a;我在打包完成后&#xff0c;打开index.html文件发现地址并没有携带路由。去config文件夹下的index.js中寻找问题。index.js中的build命令的配置有一个属性叫assetsPublicPath&#xff0c;它的值为‘/’。意思是根目录&#xff0c;这时会从index.html所在的硬…