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