0%

pro-node 之 child_process

child_process/vm 模块的使用,执行 js 代码,执行其他程序

exec 方法

方法签名 : exec(string command[,object option],function callback)

  • 第一个参数接受一个 command,会以一个新 shell 打开,在 win 上是 cmd.exe,不是新窗口
  • 第二个参数 option,这个参数是可选的

也就是

参数名称 描述
cwd 表示新创建进程的工作目录(current working directory)
env process.env 未指定的话,从 parent process 继承
encoding stdout/stderr 的 encoding,默认 utf8
timeout 超时
maxBuffer 最大 Buffer 空间,默认 200KB
killSingal 发送给 child_process 的信号,默认为 SIGTERM
  • 第 3 个参数,callback = function(err,stdout,stderr){ //blabla... }
    • err 错误
    • stdout/stderr 不是 Stream,而是 string,因为这个 callback 是在 child_process 退出后执行的,存放的是过程中的输出

execFile

方法签名 :

1
2
3
4
5
6
execFile(
string file_name_to_be_executed,
[] args, //optional
object options,//optional
function callback //optional
);`

基本与 exec 方法相同
区别

  • 第一个参数,execFile 只能是文件名,exec 包含执行的所有参数,
    例如
    exec("git add --all")
    execFile("git",["add","--all"]) 参数由后面一个数组指定
  • execFile 不会新建 shell,exec 会,在 win 上就是 cmd.exe,execFile 不会打开新窗口,而在当前窗口执行

注意 : windows 中文系统,cmd Console 的 encoding 是 gbk/gb2312 之类的,而在 execFile 的 option 参数写上{ encoding : 'gbk'} 提示错误,不能识别此种编码

解决办法是:使用 iconv-lite 转码

  1. 写上encoding:'binary',我的理解就是一个个字节数组什么的,没有应用编码
  2. 在 callback 里面解码,也就是将字节数组组合成一个字符,例如 python2 里面的 Unicode 与 str 类型,Unicode 类型就是字节数组,没组合过的,只是取用的是 2 个
    1
    2
    3
    4
    function(err,stdout,stderr){
    var ch_str = require('iconv-lite').decode(stdout,"gbk");
    console.log(ch_str);//可以显示中文
    }

spawn

英文是vi,vt 产卵,大量生产,这个方法最为灵活,能实现很多功能
签名 : spawn(string file_mame,[] args,object options)
签名与 execFile 一致,除了没有 callback 参数

option 与(exec、execFile)的 option 不尽相同

也就是

参数名称 描述
cwd 工作目录
env process.env
-
stdio [] or string 表示 standard stream
detached bool 表示是否是一个进程组(process group)的 leader,如果是,那么父进程退出后,这个还能运行,默认 false
uid number 类型,user identity,默认 null
gid number 类型,group id,默认 null

stdio 参数 : 可以为一个 string,或者一个数组
取值[stdin,stdout,stderr]

pipe,与 parent process 之间创建管道
inherit,使用父 process 的 stream
ipc 通讯,不了解…

如果取值只有一个 string,可以扩展成 3 个

并且 string 与 stream 之间可以混用,如stdio : [null,process.stdout,"inherit"]

使用 inherit 可以很方便地对其他文件进行包装,如在程序中使用 git

1
2
var cp = require("child_process");
var git = cp.spawn("git", process.argv.slice(2), { stdio: "inherit" });

ChildProcess 类

cp.spawn 不接受 callback,返回 ChildProcess 实例
除了在调用的时候指定 stdio 的来源,也可以之后对 ChildProcess 实例进行控制

1
2
3
4
var ls = cp.spawn("ls");
ls.stdout.on("data", function(data) {
console.log(data);
});

事件

  • error,当 spawn 没有成功 or killed or IPC 通讯消息没有成功发送时
  • exit,退出时,callback(code,singal)
  • close,当标准流被关闭时,callback(code,singal)

属性 pid,child.pid表示进程 id

方法 kill(string singal),关闭 child process

fork 方法

spawn 的特殊形式,创建 node 子进程
签名 : cp.fork(modulePath, [args], [options])
option 有 cwd/env/encoding 可以配置

生成的子进程,与父进程,通过 send()/on(“message”,function(message){})通信

1
2
3
4
child.on("message", function(msg) {
console.log("child received : " + msg);
});
client.send("send by parent process ...");

VM 模块

  1. vm.runInThisContext(string code),当前上下文,不能访问局部变量
  2. vm.runInNewContext(string code,object sandbox),sandbox 沙盒,global 被重置
  3. vm.runInContext(string code,object context),context,通过 vm.createContext 创建
  4. vm.createScript(string code)

方法签名省略了一个参数,string virtual_file_name,表示此段代码的虚拟文件名,此文件名只是为了在错误的 Stack Trace 能清楚一点,不会映射到__dirname,__filename全局变量

1
2
runInThisContext("console.log(__dirname);", "/virtual/dir/file.ext");
//这个__dirname取得事当前的文件,如果在node repl中运行,这个会报错,`__dirname未定义`

runInThisContext

在当前上下文运行,可以访问全局变量 global variable,与 eval 函数的区别是,runInThisContext 不能访问局部变量 local variable,eval is evil 的原因就是这个,eval 可以访问局部变量并改写
例子

1
2
3
4
5
6
var vm = require("vm");
var code = "console.log(foo); foo = 'Goodbye'; ";

foo = "Hello vm"; //注意不是var foo,这个foo是全局变量,在node中是global.foo
vm.runInThisContext(code); //输出 "Hello vm"
console.log(foo); //输出 "Goodbye",foo在code里面被改写
1
2
3
4
5
var vm = require("vm");
var code = "console.log(foo);";

var foo = "Hello vm"; //var foo,局部变量
vm.runInThisContext(code); //运行错误foo未定义
1
2
3
4
5
6
var vm = require("vm");
var code = "console.log(foo); foo = 'Goodbye'; ";

var foo = "Hello vm"; //var foo,局部变量
eval(code); //输出 "Hello vm"
console.log(foo); //输出 "Goodbye"

可以看到 eval 可以访问 var foo 这种局部变量并改写,而 runInThisContext 只能访问全局变量,相对安全

runInNewContext

上下文是一个沙盒环境 sandbox,数据独立,看例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var vm = require("vm");
var code = "var bar = 1; console.log(foo); foo = 'Goodbye'";

var sandbox;
foo = "Hello vm";
sandbox = {
console: console,
foo: foo
};

vm.runInNewContext(code, sandbox); //打印foo,输出 "Hello vm"
console.log(foo); //输出"Hello vm" ,全局变量foo未被修改
console.log(sandbox.foo); // "Goodbye" runInNewContext修改的是沙箱中的foo
console.log(sandbox.bar); // 1

复制代码到 shell 可查看结果

runInNewContext这句输出了两个,"Hello vm"和绿色的"Goodbye",是 shell 会自动将最后一个值 inspect 出来,code 里面最后一个赋值语句问题

总结就是:runInNewContext 创造一个 sandbox 作为新的 global 变量,里面连 console 都不能访问而要自己传递给新的 global 变量,这个 sandbox 会持有 var bar = 1 这样的局部变量数据

runInContext

runInContext 与 runInNewContext 的区别 : 前者的第二个参数是一个 context 对象,后者是 sandbox

1
2
3
4
5
6
7
8
9
10
11
12
13
var vm = require("vm");
var code = "var bar = 1; console.log(foo); foo = 'Goodbye'";

foo = "Hello vm";
var context = vm.createContext({
console: console,
foo: foo
});

vm.runInContext(code, context); //打印foo,输出 "Hello vm"
console.log(foo); //输出"Hello vm" ,全局变量foo未被修改
console.log(context.foo); // "Goodbye" runInNewContext修改的是沙箱中的foo
console.log(context.bar); // 1

对于测试结果,将 runInNewContext 的 sandbox 改为 context 后,输出结果一样,runInContext 就是要手动 createContext

createSCript

编译 code 成为 vm.Script 实例
实例方法,对应于上面 vm 模块的 3 个 static 方法

  • script.runInThisContext()
  • script.runInNewContext([sandbox])
  • script.runInContext(context)