nodejs 调用 shell 时应该注意的问题
由 source 命令引出的问题
假设我们有一个 shell 脚本,简单执行了一下 source 命令:
do.sh1
source s.sh
使用 exec.js 来调用上面这个脚本:
exec.js1
2
3const res = require('child_process').spawnSync('./do.sh', args, {
stdio: 'inherit'
});
在 linux 中测试这段 nodejs 代码,发现 source 命令没有成功执行,报错中显示 source 命令找不到。
原因是 nodejs spawnSync 默认启用 no shell 模式 (区别于 /bin/sh 或者 /bin/bash ),该环境没有注册 source 命令。
指定一下 shell 类型,修改 nodejs 调用脚本为如下方式,问题得到解决。
1 | const res = require('child_process').spawnSync('shellPath', args, { |
execFile 和 spawnSync 的区别。
思考:有没有方法能默认就指定到合理的 shell 类型,不需要调用时手动去指定呢?
查阅 nodejs 官方 api 能发现,child_process.execFile 可以满足我们的需求,只需要把执行脚本的方法由 spawnSync 替换为 execFile 就方便的解决了这个问题。
1 | const res = require('child_process').execFile('shellPath', args, { |
cp 命令相关的坑
问题1:linux 与 mac 下执行结果不一致
如下是一段特别常见的目录拷贝 shell 脚本
cp.sh1
cp -r ./a/ ./b
linux 下会把 a 目录拷贝到 b 目录中
mac 下会把 a 目录中的文件拷贝到 b 目录中,a 目录本身不会做拷贝。
为了避免上述歧义,归纳如下最佳实践:
我们在拷贝目录时,第一个参数不要带 /
结尾:
cp.sh1
cp -r ./a ./b
我们在拷贝目录内部的文件时,第一个参数结尾的 /
后面要加上 .
或者 *
:
cp.sh1
cp -r ./a/. ./b
###问题2:使用姿势导致 * 和 . 的兼容问题
还是一段 shell 脚本
cp.sh1
cp -r ./source/* ./target
可以使用 nodejs 来直接调用 cp 时,完成上面的拷贝操作。代码如下:1
2
3require('child_process').spawnSync('cp', ['-r', './source/*', './target'], {
stdio: 'inherit'
});
上述 nodejs 脚本看起来是没问题的,但是执行后我们得到下面的报错,
因为 Nodejs 在执行原生 cp 命令时,并不把 * 作为通配符,而是把它当做了目录文件路径本身的一部分。
解法1:我们把 * 换成 . 即可解决这个问题:
require(‘child_process’).spawnSync(‘cp’, [‘-r’, ‘./source/.’, ‘./target’], {
stdio: ‘inherit’
});
解法2:同上文 source 命令问题一个思路,也可以用 execSync 来解决这个问题。
require(‘child_process’).execSync(‘cp -r ./source/* ./target’], {
stdio: ‘inherit’
});