前语
随着node的流行,JS已经可以解决大部分问题。这对前端工程师十分友好。
相信很多同学在开发业务之余,都会写一些小脚本代替手工完成繁琐,重复的工作,从而提高工作效率。
但部分同学开发的脚本,仅局限于脚本所在路径,通过node xxx 去运行程序,这局限了脚本的使用范围和使用便捷性。
本文就给大家介绍一个简单且一劳永逸的方法,把自己开发的node脚本部署在全局环境。让脚本可以像Linux命令一样,全局便捷地使用,从此打开新世界的大门。
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
全局脚本设置的本质思路
其实原理很简单:将Linux的全局命令搜索路径,加上脚本所在文件夹的路径。
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
具体设置过程
- 终端配置文件默认路径为「/User/用户名」,笔者的mac用户名为Momo,故以下示例中用户名均为「Momo」。
- 原配终端为bash,对应配置文件为「.bash.rc」。装了zsh终端的同学,对应修改「.zsh.rc」。
- 解析:.rc文件为终端的配置文件,在重启终端,或者新开终端tab都会读取该文件。
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
修改Linux的全局命令搜索路径
- 打开文件,加上脚本所在文件夹。笔者的脚本均放在了mySHell(其实应该叫myScript)文件夹中,所以加上一句@H_502_59@export PATH=/Users/Momo/mySHell:$PATH
- 解析:在Linux中,全局命令搜索路径就是通过PATH变量保存起来的。「:」是字符串链接符的意思。类似于js中,var str = '1' + '2';中的「+」
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
- 在头部加上@H_502_59@#!/usr/bin/env node,@H_502_59@'use Strict';。
- 解析:@H_502_59@#!/usr/bin/env node是指本脚本是通过「/usr/bin/env」路径下的node软件运行。相当于文件中什么软件打开。@H_502_59@'use Strict';是指使用js的严格语法。
- 注意这两句一定要放置为js文件顶部,否则系统将不知道用什么软件执行。运行失败
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
- 终端运行@H_502_59@chmod 777 脚本文件名,如示例@H_502_59@chmod 777 mop
- 解析:chmod是linux下修改文件权限的工具,777代表所有读写权限。具体的百度chmod即可。权限设置了以后,脚本的图标将变成下面这个样子。
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
重启或新建终端,执行脚本。
- 解析:重启或新建终端是为了读取到刚修改的终端配置文件,让Linux的全局命令搜索路径生效。遍历到我们所开发的脚本。
设置不同颜色的console.log
- 介绍:不同颜色的log除了美观,还可以起到警示的作用。需呀
-
基本用法:
@H_944_126@// colors不是Node自带模块,需要事先npm install colors安装
const colors = require('colors'); // 引用colors模块,常用颜色 black,red,green,yellow,blue,magenta,cyan,white,gray,grey
console.log(colors.red('filePath or targetPath can not be empty!')); // 在控制台输出红色的文案
- 介绍:如题所示,获取终端当前所在目录,而不是脚本所在路径
- 基本用法:@H_502_59@let basePath = process.cwd(); // 其中process是node全局变量,提供当前 Node 进程的信息
携带参数运行脚本
- 介绍:平时我们使用linux命令都会伴随一些参数,那么在Node中怎么实现,怎么获取运行时携带的参数呢?通过process.argv即可,它将返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
-
基本用法:
@H_944_126@let elem1 = process.argv[2]; // 携带的参数一
let elem2 = process.argv[3]; // 携带的参数二
调用linux命令
- 介绍:有时候我们需要的功能并不是仅靠node就能实现的,还需要linux命令做支持。那怎么通过node调用Linux命令呢?
-
基本用法:
@H_944_126@const child_process = require('child_process'); // child_process是node负责子进程的模块
child_process.exec('ls -a',function (error,stdout,stderr) { // 通过child_process下的exec函数执行linux命令
error && console.log('error ' + error); // 执行出错时的错误信息
console.log('Child Process STDOUT: ' + stdout); // stdout是执行linux命令后的执行 结果。在这里即返回执行ls -a命令遍历到的文件信息
});
携带上下文执行linux命令
-
介绍:上面介绍到的调用linux方法,本质上只是直接去调用具体的Linux命令。如调用ls,相当于直接找到系统里面ls这个脚本,执行它。
这和我们平常在终端里面执行有什么区别呢?
在终端里面,我们调用命令是携带上下文的。即第二条命令会在执行完第一条命令之后的环境下执行,例如
@H_944_126@cd /
ls 这两条命令是先切换到根路径,再打印跟路径下的文件信息。
如果像上面一样,通过
@H_944_126@require('child_process').exec(`cd /`);
require('child_process').exec(`ls`); 则只是执行了两个相互独立的命令,一个是切换目录,一个是打印文件信息。ls打印的不是切换目录后的文件信息,而是运行脚本时所在的文件信息。
那怎么携带上下文执行linux命令呢?
-
基本用法:
@H_944_126@// 注意,这里使用到了colors模块,用于显示不同颜色的输出。不需要的话,也可以直接console.log()打印。
const subProcess = require('child_process').spawn("bash"); // 使用子程序去运行某个软件,在这里就是运行bash软件。相当于运行一个终端
subProcess.stdout.on('data',function(data) { console.log(colors.green(data)); }); // 监听命令执行后,bash返回的信息
subProcess.on('error',function() { console.log(colors.red('error\n' + arguments)); }); // 消息的错误监听
subProcess.on('close',(codE) => { // 关闭bash进程后触发的事件
if (code === 0) {
console.log(colors.blue(`执行成功!`));
} else {
console.log(colors.blue(`执行失败,错误码为:${CodE}`));
}
}); // 监听进程退出
//向子进程发送命令
subProcess.stdin.write(`cd / \n`); // 切换目录,\n表示回车,执行cd命令
subProcess.stdin.write(`ls -a \n`); // 写入数据
subProcess.stdin.end(); // 结束进程,相当于关闭终端
将某数据复制到剪切板
- 介绍:这是一个很常用的功能
-
基本用法:
@H_944_126@const copyProcess = require('child_process').spawn("bash"); // 用于复制密码的进程
copyProcess.stdin.write(`echo ${Config.server.passworD} | pbcopy`); // 将特定文本拷贝到剪切板中
copyProcess.stdin.end(); // 结束进程 ![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
常用脚本
上面介绍了一些基本的node功能,虽然看似很简答。但如果善于运用,也可以做出一些提高效率的小工具。
- 功能介绍:自定义常用文档,方便地查看。例如markdown语法,常用全局匹配的正则什么的。省的重复打开笔记。
- 基本思路:「colors颜色控制」+「携带参数运行脚本」
- 示例代码:
@H_
944_126@#!/usr/bin/env node
'use
Strict';
const colors =
require('colors'
); // 命令行颜色black,grey
let
HelpName = proces
s.argv[2]; // 需要查看的文档名
let
HelpInfo =
{
markdown:
{
'无需列表': '.1 xxx .1 xxx .1 xxx','有需列表': '- xxx - xxx - xxx',}
}; //
自定义帮助文档
// 设置文档name为他本身
let all
HelpName = '';
let match =
false; // 是否找到匹配项
for (let
HelpItem in
HelpInfo)
{
all
HelpName +=
HelpItem + `\n`;
if (
HelpItem ===
Help
Name) {
match = true;
for (let detailItem in
HelpInfo[
HelpItem])
{
console.log(color
s.green(detailItem + ' : ' +
HelpInfo[
HelpItem][detailItem])
); // 找不到
页面相关信息
}
return;
}
}
if (!match)
{
console.log(color
s.red('can not find matched
HelpInfo! the all
HelpName is')
); // 找不到
页面相关信息
console.log(color
s.red(all
Help
Name)); // 找不到
页面相关信息
}
mop,一键打开常用页面并复制密码到剪切板
- 功能介绍:简单,一个命令打开特定页面,而且帮你把特定文案复制到剪切板。例如?登陆密码。
- 基本思路:「复制剪切板」+「打开页面」+「colors颜色控制」
- 示例代码:
@H_
944_126@#!/usr/bin/env node
'use
Strict';
const proess =
require('child_process'
);
const copyProcess = proes
s.spawn("bash"
); // 用于复制密码的进程,为了避免同
一个进程拷贝和
上传时的冲突
const colors =
require('colors'
); // 命令行颜色black,grey
const dataInfo =
require('../con
fig').dataInfo;
const introduce =
require('../con
fig').s
HellInfo['mop'].introduce; // 脚本介绍,用在momoS
Hell中介绍
let d
ataname = proces
s.argv[2]; // 需要打开的
页面
let onlyShow = proces
s.argv
[3]; // 是否只
显示数据,不打开
页面
let dataItem = dataInfo[d
ataname]; // 遍历成功后
获取的
页面对象
// 输入脚本简介
if (proces
s.argv[2] === '-h')
{
console.log(color
s.green(introduc
E));
copyProces
s.stdin.end(
);
return;
}
// 检测数据有效性
if (!d
atanam
E) { // 参数为空
console.log(color
s.red('d
ataname can not be empty!')
);
copyProces
s.stdin.end(
);
return;
} else if (!dataItem)
{ // 找不到
页面信息
let allD
ataname = '';
for (let dataItem in dataInfo)
{
allD
ataname += `$
{dataItem}【$
{dataInfo[dataItem].info}】\n`;
}
console.log(color
s.red('can not find matched dataInfo! the all d
ataname is')
); // 找不到
页面相关信息
console.log(color
s.red(allD
atanam
E)); // 找不到
页面相关信息
copyProces
s.stdin.end(
);
return;
}
console.log(color
s.green(`【name】$
{dataIte
m.nam
E}`)
);
dataIte
m.account && console.log(color
s.green(`【account】$
{dataIte
m.account}`)
);
dataIte
m.password && console.log(color
s.green(`【
password】$
{dataIte
m.passworD}`)
);
dataIte
m.url && console.log(color
s.green(`【url】$
{dataIte
m.url}`)
);
// 将密码拷贝到剪切板中
copyProces
s.stdin.write(`echo $
{dataIte
m.passworD} | pbcopy`
); // 写入数据
copyProces
s.stdin.end(
);
!onlyShow && dataIte
m.url &&
require('child_process').exec(`open $
{dataIte
m.url}`
); // 打开特定
页面
mgulp,一键gulp打包项目,一键动态刷新
@H_
944_126@#!/usr/bin/env node
'use
Strict';
const Process =
require('child_process').spawn("bash"
); // 使用子程序去运行某个软件。
在这里就是运行bash软件。并
获取其上下文。
const colors =
require('colors'
); // 命令行颜色black,grey
const con
fig =
require('../con
fig'
); // 服务器信息
const introduce = Con
fig.s
HellInfo['mgulp'].introduce; // 脚本介绍,用在momoS
Hell中介绍
const elem = proces
s.argv; // 输入的参数
const basePath = proces
s.cwd(
);
const action = elem[2]; //
文件名
// 消息监听,监听子进程的
输出。并在主进程中打印出来。
function onData(data)
{ console.log(color
s.green(data)
); }
// 设置消息监听
Proces
s.stdout
.on('data',onData
);
Proces
s.on('error',function()
{ console.log(color
s.red('error\n' + arguments)
); }
);
Proces
s.on('close',(cod
E) =>
{
if (code === 0)
{
console.log(colors
.blue(`执行成功!`)
);
} else
{
console.log(colors
.blue(`执行成功失败,
错误码为:$
{Cod
E}`)
);
}
}
); // 监听进程
退出
if (action === '-h')
{ // 输入脚本简介
console.log(color
s.green(introduc
E));
return;
} else if (action === '-publish')
{ // 输入脚本简介
let inputPath = basePath + '/' + (elem
[3] || ''
); //
文件输入
let outputPath = basePath + '/' + elem[4]; //
文件输出
if (!elem[4])
{
outputPath = basePath + `/a-gulp-publish/$
{elem
[3]}`;
}
Proces
s.stdin.write(`cd /Users/Momo/Desktop/intruction/Node/s
Hell \n`
); // 切换pwd
Proces
s.stdin.write(`gulp default --$
{inputPath} --$
{outputPath} \n`
); // 执行gulp,通过「--」来让gulp不解析为gulp任务
Proces
s.stdin.end(
);
} else if (action === '-watch')
{ // 输入脚本简介
let watchList = elem
[3];
if (!watchList)
{ // 检测数据有效性
console.log(color
s.red('watchList can not be empty!')
);
} else
{
watchList = watchList.split(',').map((item) =>
{ // 格式化路径
item = basePath + '/' + item;
ite
m.replace(/\/\//g,'/'
); //
去除双斜杠
if (ite
m.indexOf('*') === -1)
{ // 监听所有
文件,及旗下
文件夹内的
文件
item = item + '/*.*,' + item + '/*/*.*';
}
return item;
}
);
Proces
s.stdin.write('cd /Users/Momo/Desktop/intruction/Node/s
Hell \n'
); // 切换pwd
Proces
s.stdin.write('gulp reload --$
{watchList.join(',')} \n'
); // 执行gulp
Proces
s.stdin.end(
);
}
} else
{ // 输入脚本简介
console.log(color
s.red('please input action')
);
Proces
s.stdin.end(
);
}
- 功能介绍:如题,可上传整个文件夹,不需要打开ftp软件这么麻烦(注意scp命令不支持强制覆盖文件夹功能)
- 基本思路:「复制剪切板」+「调用linux命令」+ 「scp命令」
- 示例代码:
@H_
944_126@#!/usr/bin/env node
'use
Strict';
const colors =
require('colors'
); // 命令行颜色black,grey
const con
fig =
require('../con
fig'
); // 服务器信息
const copyProcess =
require('child_process').spawn("bash"
); // 用于复制密码的进程,为了避免同
一个进程拷贝和
上传时的冲突
const subProcess =
require('child_process').spawn("bash"
); // 使用子程序去运行某个软件。
在这里就是运行bash软件。并
获取其上下文。
const introduce = Con
fig.s
HellInfo['mupload'].introduce; // 脚本介绍,用在momoS
Hell中介绍
let elem = proces
s.argv; // 输入的参数
let basePath = proces
s.cwd(
);
let filePath = basePath + '/' + elem[2]; //
文件名
let targetPath = elem
[3]; // 目标路径
// 将服务器密码拷贝到剪切板中
copyProces
s.stdin.write(`echo $
{Con
fig.server.
passworD} | pbcopy`
); // 写入数据
copyProces
s.stdin.end(
);
// 输入脚本简介
if (proces
s.argv[2] === '-h')
{
console.log(color
s.green(introduc
E));
copyProces
s.stdin.end(
);
subProces
s.stdin.end(
);
return;
}
// 检测数据有效性
if (!filePath || !targetPath)
{
console.log(color
s.red('filePath or targetPath can not be empty!')
);
subProces
s.stdin.end(
);
return;
}
// 兼容目标路径
if (targetPath[targetPath.length - 1] === '/')
{
if (elem[2].indexOf('/')
!== -1)
{
targetPath += elem[2].substr(elem[2].indexOf('/') + 1
);
} else
{
targetPath += elem[2];
}
}
// 消息监听,监听子进程的
输出。并在主进程中打印出来。
function onData(data)
{ console.log(color
s.green(data)
); }
//设置消息监听
subProces
s.stdout
.on('data',onData
);
subProces
s.on('error',function()
{ console.log(color
s.red('error\n' + arguments)
); }
);
subProces
s.on('close',(cod
E) =>
{
if (code === 0)
{
console.log(colors
.blue(`
上传成功!`)
);
console.log(color
s.red(`注意,
上传文件夹并不会覆盖原
文件,请
登录服务器查看
文件是否替换成功`)
);
} else
{
console.log(colors
.blue(`
上传失败,
错误码为:$
{Cod
E}`)
);
}
}
); // 监听进程
退出
//向子进程发送命令
subProces
s.stdin.write(`scp -C -r -p $
{filePath} root@$
{Con
fig.server.ip}:$
{targetPath} \n`
); // 写入数据
subProces
s.stdin.end(
);
还有其他的,像什么「一键git」「一键svn」,这些就根据实际需要组合开发啦。
问题求助
笔者在开发脚本时,遇到两个问题,有兴趣的大神可以指点指点
![如何设置node全局脚本 如何设置node全局脚本](http://code.js-code.com/res/2020/08-02/13/46c8b054896ebdc032eacc833610af5e.png)
复制剪切板和scp的冲突
这是「一键上传文件或文件夹到服务器」遇到的问题,复制剪切板和scp命令会冲突,如果放在同一个bash进程执行会失败,没找到原因。
linux应答式异步交互,实现ssh的一键登录。
这个功能我用linux的sHell脚本实现过。但是放在Node没能找到实现思路,